From 0603c13db9a65b44e362a550caa7321ceb68fe1b Mon Sep 17 00:00:00 2001 From: Brian McGee Date: Tue, 10 Jun 2025 11:28:11 +0100 Subject: [PATCH] feat(ui): Sidebar nav --- pkgs/clan-app/ui/.storybook/main.ts | 1 - pkgs/clan-app/ui/icons/ai.svg | 4 +- pkgs/clan-app/ui/icons/cursor.svg | 48 +++--- pkgs/clan-app/ui/icons/heart.svg | 4 +- pkgs/clan-app/ui/icons/machine.svg | 4 +- pkgs/clan-app/ui/icons/modules.svg | 4 +- pkgs/clan-app/ui/icons/new-machine.svg | 4 +- pkgs/clan-app/ui/icons/offline.svg | 24 +-- pkgs/clan-app/ui/icons/search-filled.svg | 4 +- pkgs/clan-app/ui/icons/switch.svg | 14 +- pkgs/clan-app/ui/icons/tag.svg | 4 +- pkgs/clan-app/ui/icons/user.svg | 4 +- pkgs/clan-app/ui/package-lock.json | 35 +++-- pkgs/clan-app/ui/package.json | 1 + .../components/v2/Button/Button.stories.tsx | 23 +-- .../ui/src/components/v2/Button/Button.tsx | 2 +- .../ui/src/components/v2/Divider/Divider.css | 15 ++ .../components/v2/Divider/Divider.stories.tsx | 47 ++++++ .../ui/src/components/v2/Divider/Divider.tsx | 16 ++ .../src/components/v2/Icon/Icon.stories.tsx | 68 ++++++++- .../ui/src/components/v2/Icon/Icon.tsx | 34 ++++- .../v2/MachineStatus/MachineStatus.css | 19 +++ .../MachineStatus/MachineStatus.stories.tsx | 73 +++++++++ .../v2/MachineStatus/MachineStatus.tsx | 42 ++++++ .../src/components/v2/Sidebar/SidebarNav.css | 10 ++ .../v2/Sidebar/SidebarNav.stories.tsx | 109 ++++++++++++++ .../src/components/v2/Sidebar/SidebarNav.tsx | 47 ++++++ .../components/v2/Sidebar/SidebarNavBody.css | 127 ++++++++++++++++ .../components/v2/Sidebar/SidebarNavBody.tsx | 138 ++++++++++++++++++ .../v2/Sidebar/SidebarNavHeader.css | 91 ++++++++++++ .../v2/Sidebar/SidebarNavHeader.tsx | 99 +++++++++++++ .../components/v2/Typography/Typography.css | 76 +++++----- .../components/v2/Typography/Typography.mdx | 4 +- .../v2/Typography/Typography.stories.tsx | 20 +-- .../components/v2/Typography/Typography.tsx | 91 ++++-------- pkgs/clan-app/ui/src/components/v2/colors.ts | 41 ++++++ pkgs/clan-app/ui/src/components/v2/index.css | 10 ++ pkgs/clan-app/ui/tailwind.config.ts | 3 +- pkgs/clan-app/ui/tailwind/core-plugin.ts | 6 +- pkgs/clan-app/ui/tailwind/typography.ts | 22 --- 40 files changed, 1150 insertions(+), 238 deletions(-) create mode 100644 pkgs/clan-app/ui/src/components/v2/Divider/Divider.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Divider/Divider.stories.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.css create mode 100644 pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.stories.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.stories.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.css create mode 100644 pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.tsx create mode 100644 pkgs/clan-app/ui/src/components/v2/colors.ts delete mode 100644 pkgs/clan-app/ui/tailwind/typography.ts diff --git a/pkgs/clan-app/ui/.storybook/main.ts b/pkgs/clan-app/ui/.storybook/main.ts index fa637ff8d..22123300c 100644 --- a/pkgs/clan-app/ui/.storybook/main.ts +++ b/pkgs/clan-app/ui/.storybook/main.ts @@ -8,7 +8,6 @@ const config: StorybookConfig = { "@storybook/addon-links", "@storybook/addon-docs", "@storybook/addon-a11y", - "@storybook/addon-onboarding", ], async viteFinal(config) { return mergeConfig(config, { diff --git a/pkgs/clan-app/ui/icons/ai.svg b/pkgs/clan-app/ui/icons/ai.svg index da09904db..bdc65decf 100644 --- a/pkgs/clan-app/ui/icons/ai.svg +++ b/pkgs/clan-app/ui/icons/ai.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/cursor.svg b/pkgs/clan-app/ui/icons/cursor.svg index 2d9de47d2..2d10ca437 100644 --- a/pkgs/clan-app/ui/icons/cursor.svg +++ b/pkgs/clan-app/ui/icons/cursor.svg @@ -1,25 +1,25 @@ - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/heart.svg b/pkgs/clan-app/ui/icons/heart.svg index 65a61bfab..9a3b9d123 100644 --- a/pkgs/clan-app/ui/icons/heart.svg +++ b/pkgs/clan-app/ui/icons/heart.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/machine.svg b/pkgs/clan-app/ui/icons/machine.svg index 908827dfd..845a739be 100644 --- a/pkgs/clan-app/ui/icons/machine.svg +++ b/pkgs/clan-app/ui/icons/machine.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/modules.svg b/pkgs/clan-app/ui/icons/modules.svg index e43a47dad..97544aa18 100644 --- a/pkgs/clan-app/ui/icons/modules.svg +++ b/pkgs/clan-app/ui/icons/modules.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/new-machine.svg b/pkgs/clan-app/ui/icons/new-machine.svg index 178100e11..fdc5fea29 100644 --- a/pkgs/clan-app/ui/icons/new-machine.svg +++ b/pkgs/clan-app/ui/icons/new-machine.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/offline.svg b/pkgs/clan-app/ui/icons/offline.svg index 7e68c551d..501c3fa8c 100644 --- a/pkgs/clan-app/ui/icons/offline.svg +++ b/pkgs/clan-app/ui/icons/offline.svg @@ -1,13 +1,13 @@ - - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/search-filled.svg b/pkgs/clan-app/ui/icons/search-filled.svg index 394ffd039..e72ad2986 100644 --- a/pkgs/clan-app/ui/icons/search-filled.svg +++ b/pkgs/clan-app/ui/icons/search-filled.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/switch.svg b/pkgs/clan-app/ui/icons/switch.svg index da7093b58..c2658d961 100644 --- a/pkgs/clan-app/ui/icons/switch.svg +++ b/pkgs/clan-app/ui/icons/switch.svg @@ -1,8 +1,8 @@ - - - - - - - + + + + + + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/tag.svg b/pkgs/clan-app/ui/icons/tag.svg index d9446d26a..d03b459d7 100644 --- a/pkgs/clan-app/ui/icons/tag.svg +++ b/pkgs/clan-app/ui/icons/tag.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/icons/user.svg b/pkgs/clan-app/ui/icons/user.svg index 5fc3eff5b..0b69f2358 100644 --- a/pkgs/clan-app/ui/icons/user.svg +++ b/pkgs/clan-app/ui/icons/user.svg @@ -1,3 +1,3 @@ - - + + \ No newline at end of file diff --git a/pkgs/clan-app/ui/package-lock.json b/pkgs/clan-app/ui/package-lock.json index 8cc139410..abfe640c0 100644 --- a/pkgs/clan-app/ui/package-lock.json +++ b/pkgs/clan-app/ui/package-lock.json @@ -15,6 +15,7 @@ "@modular-forms/solid": "^0.25.1", "@solid-primitives/storage": "^4.3.2", "@solidjs/router": "^0.15.3", + "@solidjs/testing-library": "^0.8.10", "@tanstack/eslint-plugin-query": "^5.51.12", "@tanstack/solid-query": "^5.76.0", "solid-js": "^1.9.7", @@ -126,7 +127,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", - "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", @@ -268,7 +268,6 @@ "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -366,7 +365,6 @@ "version": "7.27.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.6.tgz", "integrity": "sha512-vbavdySgbTTrmFE+EsiqUTzlOr5bzlnJtUv9PynGCAKvfQqjIXbvFdumPM/GxMDfyuGMJaJAU6TO4zc1Jf1i8Q==", - "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2037,6 +2035,27 @@ "solid-js": "^1.8.6" } }, + "node_modules/@solidjs/testing-library": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@solidjs/testing-library/-/testing-library-0.8.10.tgz", + "integrity": "sha512-qdeuIerwyq7oQTIrrKvV0aL9aFeuwTd86VYD3afdq5HYEwoox1OBTJy4y8A3TFZr8oAR0nujYgCzY/8wgHGfeQ==", + "license": "MIT", + "dependencies": { + "@testing-library/dom": "^10.4.0" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "@solidjs/router": ">=0.9.0", + "solid-js": ">=1.0.0" + }, + "peerDependenciesMeta": { + "@solidjs/router": { + "optional": true + } + } + }, "node_modules/@storybook/addon-a11y": { "version": "9.0.12", "resolved": "https://registry.npmjs.org/@storybook/addon-a11y/-/addon-a11y-9.0.12.tgz", @@ -2305,7 +2324,6 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", "integrity": "sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==", - "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.10.4", @@ -2409,7 +2427,6 @@ "version": "5.0.4", "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, "license": "MIT" }, "node_modules/@types/babel__core": { @@ -3161,7 +3178,6 @@ "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, "license": "Apache-2.0", "dependencies": { "dequal": "^2.0.3" @@ -3989,7 +4005,6 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4011,7 +4026,6 @@ "version": "0.5.16", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, "license": "MIT" }, "node_modules/dom-serializer": { @@ -5323,7 +5337,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -5644,7 +5657,6 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, "license": "MIT", "bin": { "lz-string": "bin/bin.js" @@ -6505,7 +6517,6 @@ "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, "license": "MIT", "dependencies": { "ansi-regex": "^5.0.1", @@ -6520,7 +6531,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -6622,7 +6632,6 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, "license": "MIT" }, "node_modules/read-cache": { diff --git a/pkgs/clan-app/ui/package.json b/pkgs/clan-app/ui/package.json index 569e98f64..64b82c1f3 100644 --- a/pkgs/clan-app/ui/package.json +++ b/pkgs/clan-app/ui/package.json @@ -67,6 +67,7 @@ "@modular-forms/solid": "^0.25.1", "@solid-primitives/storage": "^4.3.2", "@solidjs/router": "^0.15.3", + "@solidjs/testing-library": "^0.8.10", "@tanstack/eslint-plugin-query": "^5.51.12", "@tanstack/solid-query": "^5.76.0", "solid-js": "^1.9.7", diff --git a/pkgs/clan-app/ui/src/components/v2/Button/Button.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Button/Button.stories.tsx index 32584adb3..43513727e 100644 --- a/pkgs/clan-app/ui/src/components/v2/Button/Button.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Button/Button.stories.tsx @@ -2,7 +2,6 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid"; import { Button, ButtonProps } from "./Button"; import { Component } from "solid-js"; import { expect, fn, waitFor } from "storybook/test"; -import { PlayFunctionContext } from "storybook/internal/csf"; import { StoryContext } from "@kachurun/storybook-solid-vite"; const getCursorStyle = (el: Element) => window.getComputedStyle(el).cursor; @@ -150,7 +149,7 @@ export const Primary: Story = { hierarchy: "primary", onAction: fn(async () => { // wait 500 ms to simulate an action - await new Promise((resolve) => setTimeout(resolve, 500)); + await new Promise((resolve) => setTimeout(resolve, 1000)); // randomly fail to check that the loading state still returns to normal if (Math.random() > 0.5) { throw new Error("Action failure"); @@ -159,6 +158,7 @@ export const Primary: Story = { }, parameters: { test: { + // increase test timeout to allow for the loading action mockTimers: true, }, }, @@ -205,14 +205,17 @@ export const Primary: Story = { }); // wait for the action handler to finish - await waitFor(async () => { - // the loading class should be removed - await expect(button).not.toHaveClass("loading"); - // the loader should be hidden - await expect(loader.clientWidth).toEqual(0); - // the pointer should be normal - await expect(getCursorStyle(button)).toEqual("pointer"); - }); + await waitFor( + async () => { + // the loading class should be removed + await expect(button).not.toHaveClass("loading"); + // the loader should be hidden + await expect(loader.clientWidth).toEqual(0); + // the pointer should be normal + await expect(getCursorStyle(button)).toEqual("pointer"); + }, + { timeout: 1500 }, + ); }); } }, diff --git a/pkgs/clan-app/ui/src/components/v2/Button/Button.tsx b/pkgs/clan-app/ui/src/components/v2/Button/Button.tsx index 3416f525b..1a1a031a8 100644 --- a/pkgs/clan-app/ui/src/components/v2/Button/Button.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Button/Button.tsx @@ -1,4 +1,4 @@ -import { splitProps, type JSX, createSignal, Show } from "solid-js"; +import { splitProps, type JSX, createSignal } from "solid-js"; import cx from "classnames"; import { Typography } from "../Typography/Typography"; import { Button as KobalteButton } from "@kobalte/core/button"; diff --git a/pkgs/clan-app/ui/src/components/v2/Divider/Divider.css b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.css new file mode 100644 index 000000000..ee1f04f0c --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.css @@ -0,0 +1,15 @@ +div.divider { + @apply bg-inv-2; + + &.inverted { + @apply bg-def-3; + } + + &.horizontal { + @apply w-full h-px; + } + + &.vertical { + @apply h-full w-px; + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Divider/Divider.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.stories.tsx new file mode 100644 index 000000000..24cac1525 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.stories.tsx @@ -0,0 +1,47 @@ +import { Meta, StoryObj } from "@kachurun/storybook-solid"; +import { Divider, DividerProps } from "@/src/components/v2/Divider/Divider"; + +const meta: Meta = { + title: "Components/Divider", + component: Divider, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = {}; + +export const Horizontal: Story = { + args: { + orientation: "horizontal", + }, +}; + +export const HorizontalInverted: Story = { + args: { + inverted: true, + ...Horizontal.args, + }, +}; + +export const Vertical: Story = { + args: { + orientation: "vertical", + }, + decorators: [ + (Story: Story) => ( +
+ +
+ ), + ], +}; + +export const VerticalInverted: Story = { + args: { + inverted: true, + ...Vertical.args, + }, + decorators: [...Vertical.decorators], +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx new file mode 100644 index 000000000..c25fa7c6d --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx @@ -0,0 +1,16 @@ +import "./Divider.css"; +import cx from "classnames"; + +export type Orientation = "horizontal" | "vertical"; + +export interface DividerProps { + inverted?: boolean; + orientation?: Orientation; +} + +export const Divider = (props: DividerProps) => { + const inverted = props.inverted || false; + const orientation = props.orientation || "horizontal"; + + return
; +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Icon/Icon.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Icon/Icon.stories.tsx index 62bd72311..f65e4a655 100644 --- a/pkgs/clan-app/ui/src/components/v2/Icon/Icon.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Icon/Icon.stories.tsx @@ -1,6 +1,7 @@ -import type { Meta, StoryObj } from "@kachurun/storybook-solid"; +import type { Meta, StoryObj, StoryContext } from "@kachurun/storybook-solid"; import { Component, For } from "solid-js"; import Icon, { IconProps, IconVariant } from "./Icon"; +import cx from "classnames"; const iconVariants: IconVariant[] = [ "ClanIcon", @@ -59,6 +60,13 @@ const IconExamples: Component = (props) => ( const meta: Meta = { title: "Components/Icon", component: IconExamples, + decorators: [ + (Story: StoryObj, context: StoryContext) => ( +
+ +
+ ), + ], }; export default meta; @@ -67,6 +75,64 @@ type Story = StoryObj; export const Default: Story = {}; +export const Primary: Story = { + args: { + color: "primary", + }, +}; + +export const Secondary: Story = { + args: { + color: "secondary", + }, +}; + +export const Tertiary: Story = { + args: { + color: "tertiary", + }, +}; + +export const Quaternary: Story = { + args: { + color: "quaternary", + }, +}; + +export const PrimaryInverted: Story = { + args: { + ...Primary.args, + inverted: true, + }, +}; + +export const SecondaryInverted: Story = { + args: { + ...Secondary.args, + inverted: true, + }, +}; + +export const TertiaryInverted: Story = { + args: { + ...Tertiary.args, + inverted: true, + }, +}; + +export const QuaternaryInverted: Story = { + args: { + ...Quaternary.args, + inverted: true, + }, +}; + +export const Inverted: Story = { + args: { + inverted: true, + }, +}; + export const Large: Story = { args: { width: "2rem", diff --git a/pkgs/clan-app/ui/src/components/v2/Icon/Icon.tsx b/pkgs/clan-app/ui/src/components/v2/Icon/Icon.tsx index 6b8081cf5..0dfa24738 100644 --- a/pkgs/clan-app/ui/src/components/v2/Icon/Icon.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Icon/Icon.tsx @@ -1,5 +1,5 @@ import cx from "classnames"; -import { Component, JSX, Show, splitProps } from "solid-js"; +import { Component, JSX, splitProps } from "solid-js"; import ArrowBottom from "@/icons/arrow-bottom.svg"; import ArrowLeft from "@/icons/arrow-left.svg"; import ArrowRight from "@/icons/arrow-right.svg"; @@ -45,9 +45,10 @@ import Offline from "@/icons/offline.svg"; import Switch from "@/icons/switch.svg"; import Tag from "@/icons/tag.svg"; import Machine from "@/icons/machine.svg"; -import Loader from "@/icons/loader.svg"; import { Dynamic } from "solid-js/web"; +import { Color, fgClass } from "../colors"; + const icons = { AI, ArrowBottom, @@ -98,24 +99,43 @@ const icons = { export type IconVariant = keyof typeof icons; +const viewBoxes: Partial> = { + ClanIcon: "0 0 72 89", + Offline: "0 0 38 27", +}; + export interface IconProps extends JSX.SvgSVGAttributes { icon: IconVariant; class?: string; size?: number | string; + color?: Color; + inverted?: boolean; } const Icon: Component = (props) => { - const [local, iconProps] = splitProps(props, ["icon", "class"]); + const [local, iconProps] = splitProps(props, [ + "icon", + "color", + "class", + "size", + "inverted", + ]); const IconComponent = () => icons[local.icon]; + // we need to adjust the view box for certain icons + const viewBox = () => viewBoxes[local.icon] ?? "0 0 48 48"; + return IconComponent() ? ( diff --git a/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.css b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.css new file mode 100644 index 000000000..b5cb5b34e --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.css @@ -0,0 +1,19 @@ +span.tag-status { + @apply flex items-center gap-1; + + .indicator { + @apply w-1.5 h-1.5 rounded-full m-1.5; + } + + &.online > .indicator { + background-color: #0ae856; /* todo get from theme */ + } + + &.offline > .indicator { + background-color: #ff2c78; /* todo get from theme */ + } + + &.installed > .indicator { + background-color: var(--clr-fg-inv-3); + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.stories.tsx b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.stories.tsx new file mode 100644 index 000000000..0c8a597ca --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.stories.tsx @@ -0,0 +1,73 @@ +import { + MachineStatus, + TagStatusProps, +} from "@/src/components/v2/MachineStatus/MachineStatus"; +import { Meta, StoryObj } from "@kachurun/storybook-solid"; + +const meta: Meta = { + title: "Components/MachineStatus", + component: MachineStatus, + decorators: [ + (Story: StoryObj) => ( +
+ +
+ ), + ], +}; + +export default meta; + +type Story = StoryObj; + +export const Online: Story = { + args: { + status: "Online", + }, +}; + +export const Offline: Story = { + args: { + status: "Offline", + }, +}; + +export const Installed: Story = { + args: { + status: "Installed", + }, +}; + +export const NotInstalled: Story = { + args: { + status: "Not Installed", + }, +}; + +export const OnlineWithLabel: Story = { + args: { + ...Online.args, + label: true, + }, +}; + +export const OfflineWithLabel: Story = { + args: { + ...Offline.args, + label: true, + }, +}; + +export const InstalledWithLabel: Story = { + args: { + ...Installed.args, + label: true, + }, +}; + +export const NotInstalledWithLabel: Story = { + args: { + ...NotInstalled.args, + label: true, + }, +}; diff --git a/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.tsx b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.tsx new file mode 100644 index 000000000..1fcb523d7 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/MachineStatus/MachineStatus.tsx @@ -0,0 +1,42 @@ +import "./MachineStatus.css"; + +import { Badge } from "@kobalte/core/badge"; +import cx from "classnames"; +import { Show } from "solid-js"; +import Icon from "../Icon/Icon"; +import { Typography } from "@/src/components/v2/Typography/Typography"; + +export type MachineStatus = + | "Online" + | "Offline" + | "Installed" + | "Not Installed"; + +export interface TagStatusProps { + label?: boolean; + status: MachineStatus; +} + +export const MachineStatus = (props: TagStatusProps) => ( + + {props.label && ( + + {props.status} + + )} + } + > + + + +); diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.css b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.css new file mode 100644 index 000000000..f448138ab --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.css @@ -0,0 +1,10 @@ +div.sidebar { + @apply h-full w-auto max-w-60 border-none; + + & > div.header { + } + + & > div.body { + @apply pt-4 pb-3 px-2; + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.stories.tsx new file mode 100644 index 000000000..2f775745f --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.stories.tsx @@ -0,0 +1,109 @@ +import type { Meta, StoryObj } from "@kachurun/storybook-solid"; +import { + createMemoryHistory, + MemoryRouter, + Route, + RouteSectionProps, +} from "@solidjs/router"; +import { + SidebarNav, + SidebarNavProps, +} from "@/src/components/v2/Sidebar/SidebarNav"; +import { Suspense } from "solid-js"; +import { StoryContext } from "@kachurun/storybook-solid-vite"; + +const sidebarNavProps: SidebarNavProps = { + clanLinks: [ + { label: "Brian's Clan", path: "/clan/1" }, + { label: "Dave's Clan", path: "/clan/2" }, + { label: "Mic92's Clan", path: "/clan/3" }, + ], + clanDetail: { + label: "Brian's Clan", + settingsPath: "/clan/1/settings", + machines: [ + { + label: "Backup & Home", + path: "/clan/1/machine/backup", + serviceCount: 3, + status: "Online", + }, + { + label: "Raspberry Pi", + path: "/clan/1/machine/pi", + serviceCount: 1, + status: "Offline", + }, + { + label: "Mom's Laptop", + path: "/clan/1/machine/moms-laptop", + serviceCount: 2, + status: "Installed", + }, + { + label: "Dad's Laptop", + path: "/clan/1/machine/dads-laptop", + serviceCount: 4, + status: "Not Installed", + }, + ], + }, + extraSections: [ + { + label: "Tools", + links: [ + { label: "Borgbackup", path: "/clan/1/service/borgbackup" }, + { label: "Syncthing", path: "/clan/1/service/syncthing" }, + { label: "Mumble", path: "/clan/1/service/mumble" }, + { label: "Minecraft", path: "/clan/1/service/minecraft" }, + ], + }, + { + label: "Links", + links: [ + { label: "GitHub", path: "https://github.com/brian-the-dev" }, + { label: "Twitter", path: "https://twitter.com/brian_the_dev" }, + { + label: "LinkedIn", + path: "https://www.linkedin.com/in/brian-the-dev/", + }, + { + label: "Instagram", + path: "https://www.instagram.com/brian_the_dev/", + }, + ], + }, + ], +}; + +const meta: Meta = { + title: "Components/Sidebar/Nav", + component: SidebarNav, + render: (_: never, context: StoryContext) => { + const history = createMemoryHistory(); + history.set({ value: "/clan/1/machine/backup" }); + + return ( +
+ ( + + + + )} + > + <>} /> + +
+ ); + }, +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: {}, +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.tsx b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.tsx new file mode 100644 index 000000000..f5d4ab1dc --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNav.tsx @@ -0,0 +1,47 @@ +import "./SidebarNav.css"; +import { SidebarNavHeader } from "@/src/components/v2/Sidebar/SidebarNavHeader"; +import { SidebarNavBody } from "@/src/components/v2/Sidebar/SidebarNavBody"; +import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus"; + +export interface LinkProps { + path: string; + label?: string; +} + +export interface SectionProps { + label: string; + links: LinkProps[]; +} + +export interface MachineProps { + label: string; + path: string; + status: MachineStatus; + serviceCount: number; +} + +export interface ClanLinkProps { + label: string; + path: string; +} + +export interface ClanProps { + label: string; + settingsPath: string; + machines: MachineProps[]; +} + +export interface SidebarNavProps { + clanDetail: ClanProps; + clanLinks: ClanLinkProps[]; + extraSections: SectionProps[]; +} + +export const SidebarNav = (props: SidebarNavProps) => { + return ( + + ); +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.css b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.css new file mode 100644 index 000000000..b201b3f29 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.css @@ -0,0 +1,127 @@ +div.sidebar-body { + @apply py-4 px-2 h-full; + @apply border border-inv-3 rounded-bl-md rounded-br-md; + + &::-webkit-scrollbar { + display: none; + } + + overflow-y: auto; + scrollbar-width: none; + + scrollbar-color: theme(colors.primary.700) theme(colors.primary.600); + + background: linear-gradient( + 180deg, + var(--clr-bg-inv-1) 0%, + var(--clr-bg-inv-3) 100% + ); + + @apply backdrop-blur-sm; + + .accordion { + @apply w-full mb-4; + + &:last-child { + @apply mb-0; + } + + & > .item { + @apply py-3 px-1.5 bg-inv-3 rounded-md mb-4; + + &:last-child { + @apply mb-0; + } + + & > .header { + @apply flex mb-4 px-2; + + & > .trigger { + @apply inline-flex items-center justify-between w-full; + + &:focus-visible { + @apply z-10; + outline: 2px solid hsl(200 98% 39%); + outline-offset: 2px; + } + + & > .icon { + transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1); + } + + &[data-expanded] > .icon { + transform: rotate(180deg); + } + + .section-title { + @apply uppercase; + } + } + } + + & > .content { + @apply overflow-hidden flex flex-col; + animation: slideAccordionUp 300ms cubic-bezier(0.87, 0, 0.13, 1); + + &[data-expanded] { + animation: slideAccordionDown 300ms cubic-bezier(0.87, 0, 0.13, 1); + } + + nav * { + @apply outline-none; + } + + nav > a { + @apply block w-full px-2 py-1.5 min-h-7 my-2 rounded-md; + + &:first-child { + @apply mt-0; + } + + &:last-child { + @apply mb-0; + } + + &:focus-visible { + background: linear-gradient( + 90deg, + theme(colors.secondary.900), + 60%, + theme(colors.secondary.600) 100% + ); + } + + &:hover { + @apply bg-inv-acc-2; + } + + &:active { + @apply bg-inv-acc-3; + } + + &.active { + @apply bg-inv-acc-2; + } + } + } + } + } +} + +@keyframes slideAccordionDown { + from { + height: 0; + } + to { + height: var(--kb-accordion-content-height); + } +} + +@keyframes slideAccordionUp { + from { + height: var(--kb-accordion-content-height); + } + to { + height: 0; + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.tsx b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.tsx new file mode 100644 index 000000000..73d7e8503 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavBody.tsx @@ -0,0 +1,138 @@ +import "./SidebarNavBody.css"; +import { A } from "@solidjs/router"; +import { Accordion } from "@kobalte/core/accordion"; +import Icon from "../Icon/Icon"; +import { Typography } from "@/src/components/v2/Typography/Typography"; +import { + MachineProps, + SidebarNavProps, +} from "@/src/components/v2/Sidebar/SidebarNav"; +import { For } from "solid-js"; +import { MachineStatus } from "@/src/components/v2/MachineStatus/MachineStatus"; + +const MachineRoute = (props: MachineProps) => ( +
+
+ + {props.label} + + +
+
+ + + {props.serviceCount} + +
+
+); + +export const SidebarNavBody = (props: SidebarNavProps) => { + const sectionLabels = props.extraSections.map((section) => section.label); + + // controls which sections are open by default + // we want them all to be open by default + const defaultAccordionValues = ["your-machines", ...sectionLabels]; + + return ( + + ); +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.css b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.css new file mode 100644 index 000000000..712630654 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.css @@ -0,0 +1,91 @@ +div.sidebar-header { + @apply flex items-center justify-center w-full px-1 py-1; + @apply border border-inv-3 rounded-md rounded-bl-none rounded-br-none; + + background: linear-gradient( + 90deg, + var(--clr-bg-inv-3) 0%, + var(--clr-bg-inv-4) 100% + ); + + & > .dropdown-trigger { + @apply flex items-center justify-between flex-grow px-1 py-1; + @apply rounded-tl-md rounded-tr-md; + @apply border border-transparent border-b-0; + + transition: all 250ms ease-in-out; + + div.title { + @apply flex items-center gap-2 justify-start; + + & > .clan-icon { + @apply rounded-full bg-inv-4 w-7 h-7; + } + } + + .icon[data-icon-name="CaretDown"] { + transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1); + } + + &[data-expanded] { + @apply bg-def-1 border-def-2; + + .icon[data-icon-name="CaretDown"] { + transform: rotate(180deg); + } + } + } +} + +.sidebar-dropdown-content { + @apply flex flex-col w-full px-2 py-1.5; + @apply bg-def-1 rounded-bl-md rounded-br-md; + @apply border border-def-2; + + animation: sidebarNavContentHide 250ms ease-in forwards; + + .dropdown-item { + @apply flex items-center justify-start w-full px-1.5 py-2 gap-2 rounded; + + &:hover { + @apply bg-def-acc-2 cursor-pointer; + } + } + + .dropdown-group { + @apply flex flex-col gap-2; + @apply px-1; + + .dropdown-group-label { + } + + .dropdown-group-items { + @apply rounded px-1 py-1.5 bg-def-2; + } + } +} + +.sidebar-dropdown-content[data-expanded] { + animation: sidebarNavContentShow 250ms ease-out; +} + +@keyframes sidebarNavContentShow { + from { + opacity: 0; + transform: scale(0.96); + } + to { + opacity: 1; + transform: scale(1); + } +} +@keyframes sidebarNavContentHide { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.96); + } +} diff --git a/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.tsx b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.tsx new file mode 100644 index 000000000..278a67195 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/Sidebar/SidebarNavHeader.tsx @@ -0,0 +1,99 @@ +import "./SidebarNavHeader.css"; +import Icon from "@/src/components/v2/Icon/Icon"; +import { DropdownMenu } from "@kobalte/core/dropdown-menu"; +import { useNavigate } from "@solidjs/router"; +import { Typography } from "../Typography/Typography"; +import { createSignal, For } from "solid-js"; +import { + ClanLinkProps, + ClanProps, +} from "@/src/components/v2/Sidebar/SidebarNav"; + +export interface SidebarHeaderProps { + clanDetail: ClanProps; + clanLinks: ClanLinkProps[]; +} + +export const SidebarNavHeader = (props: SidebarHeaderProps) => { + const navigate = useNavigate(); + + const [open, setOpen] = createSignal(false); + + const firstChar = props.clanDetail.label.charAt(0); + + return ( + + ); +}; diff --git a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.css b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.css index accb73569..3e8fb94bd 100644 --- a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.css +++ b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.css @@ -1,151 +1,151 @@ /* Body */ .typography { - &.font-weight-normal { + &.weight-normal { font-weight: 400; } - &.font-weight-medium { + &.weight-medium { font-weight: 500; } - &.font-weight-bold { + &.weight-bold { font-weight: 600; } - &.font-body { - &.font-family-regular { + &.body { + &.family-regular { font-family: "Archivo", sans-serif; } - &.font-family-condensed { + &.family-condensed { font-family: "Archivo SemiCondensed", sans-serif; } - &.font-size-default { + &.size-default { font-size: 1rem; line-height: 1.32; letter-spacing: 0.02rem; } - &.font-size-s { + &.size-s { font-size: 0.875rem; line-height: 1.32; letter-spacing: 0.0175rem; } - &.font-size-xs { + &.size-xs { font-size: 0.75rem; line-height: 1.32; letter-spacing: 0.0225rem; } - &.font-size-xxs { + &.size-xxs { font-size: 0.6875rem; line-height: 1.32; letter-spacing: 0.00688rem; } } - &.font-label { - &.font-family-condensed { + &.label { + &.family-condensed { font-family: "Archivo SemiCondensed", sans-serif; - &.font-size-default { + &.size-default { font-size: 0.875rem; - line-height: 1.32; + line-height: 1; letter-spacing: 0.0175rem; } - &.font-size-s { + &.size-s { font-size: 0.8125rem; - line-height: 1.32; + line-height: 1; letter-spacing: 0.0175rem; } - &.font-size-xs { + &.size-xs { font-size: 0.75rem; - line-height: 1.32; + line-height: 1; letter-spacing: 0.0075rem; } } - &.font-family-mono { + &.family-mono { font-family: "Commit Mono", monospace; - &.font-size-default { + &.size-default { font-size: 0.8125rem; - line-height: 0; + line-height: 1; letter-spacing: normal; } - &.font-size-s { + &.size-s { font-size: 0.75rem; - line-height: 0; + line-height: 1; letter-spacing: normal; } - &.font-size-xs { + &.size-xs { font-size: 0.6875rem; - line-height: 0; + line-height: 1; letter-spacing: normal; } } } - &.font-title { - &.font-family-regular { + &.title { + &.family-regular { font-family: "Archivo", sans-serif; } - &.font-size-default { + &.size-default { font-size: 1.125rem; line-height: 124%; letter-spacing: 0.03375rem; } - &.font-size-m { + &.size-m { font-size: 1.25rem; line-height: 124%; letter-spacing: 0.0375rem; } - &.font-size-l { + &.size-l { font-size: 1.375rem; line-height: 124%; letter-spacing: 0.04125rem; } } - &.font-headline { - &.font-family-regular { + &.headline { + &.family-regular { font-family: "Archivo", sans-serif; } - &.font-size-default { + &.size-default { font-size: 1.5rem; line-height: 116%; letter-spacing: 0.015rem; } - &.font-size-m { + &.size-m { font-size: 1.75rem; line-height: 116%; letter-spacing: 0.0175rem; } - &.font-size-l { + &.size-l { font-size: 2rem; line-height: 116%; letter-spacing: 0.06rem; } } - &.font-teaser { - &.font-family-regular { + &.teaser { + &.family-regular { font-family: "Archivo", sans-serif; } - &.font-size-default { + &.size-default { font-size: 3rem; line-height: normal; letter-spacing: -0.06rem; diff --git a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.mdx b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.mdx index f85ba41d1..cab31a00d 100644 --- a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.mdx +++ b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.mdx @@ -14,9 +14,7 @@ There are two fonts being used within our typography system: ## UI Components When creating UI components that a user will interact with, -you must use the condensed form of `Body`, `Label` and `Label Mono`. - - +you **must use** `Label` or `Label Mono`. diff --git a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.stories.tsx b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.stories.tsx index b096f56c1..99b67fe1c 100644 --- a/pkgs/clan-app/ui/src/components/v2/Typography/Typography.stories.tsx +++ b/pkgs/clan-app/ui/src/components/v2/Typography/Typography.stories.tsx @@ -1,14 +1,8 @@ import type { Meta, StoryObj } from "@kachurun/storybook-solid"; -import { - AllowedSizes, - Color, - Family, - Hierarchy, - Typography, - Weight, -} from "./Typography"; +import { Family, Hierarchy, Typography, Weight } from "./Typography"; import { Component, For, Show } from "solid-js"; +import { AllColors } from "@/src/components/v2/colors"; interface TypographyExamplesProps { weights: Weight[]; @@ -19,14 +13,6 @@ interface TypographyExamplesProps { inverted?: boolean; } -const colors: (Color | "inherit")[] = [ - "inherit", - "primary", - "secondary", - "tertiary", - "quaternary", -]; - const TypographyExamples: Component = (props) => ( = (props) => ( - + {(color) => ( <> = { - primary: cx("fg-def-1"), - secondary: cx("fg-def-2"), - tertiary: cx("fg-def-3"), - quaternary: cx("fg-def-4"), -}; - -const invertedColorMap: Record = { - primary: cx("fg-inv-1"), - secondary: cx("fg-inv-2"), - tertiary: cx("fg-inv-3"), - quaternary: cx("fg-inv-4"), -}; - -const colorFor = (color: Color | "inherit" = "primary", inverted = false) => { - if (color === "inherit") { - return "text-inherit"; - } - - return inverted ? invertedColorMap[color] : colorMap[color]; -}; - // type Size = "default" | "xs" | "s" | "m" | "l"; interface SizeForHierarchy { body: { @@ -63,30 +41,30 @@ export type AllowedSizes = keyof SizeForHierarchy[H]; const sizeHierarchyMap: SizeForHierarchy = { body: { - default: cx("font-size-default"), - s: cx("font-size-s"), - xs: cx("font-size-xs"), - xxs: cx("font-size-xxs"), + default: cx("size-default"), + s: cx("size-s"), + xs: cx("size-xs"), + xxs: cx("size-xxs"), }, headline: { - default: cx("font-size-default"), - m: cx("font-size-m"), - l: cx("font-size-l"), + default: cx("size-default"), + m: cx("size-m"), + l: cx("size-l"), }, title: { - default: cx("font-size-default"), - // xs: cx("font-size-xs"), - // s: cx("font-size-s"), - m: cx("font-size-m"), - l: cx("font-size-l"), + default: cx("size-default"), + // xs: cx("size-xs"), + // s: cx("size-s"), + m: cx("size-m"), + l: cx("size-l"), }, label: { - default: cx("font-size-default"), - s: cx("font-size-s"), - xs: cx("font-size-xs"), + default: cx("size-default"), + s: cx("size-s"), + xs: cx("size-xs"), }, teaser: { - default: cx("font-size-default"), + default: cx("size-default"), }, }; @@ -99,50 +77,43 @@ const defaultFamilyMap: Record = { }; const weightMap: Record = { - normal: cx("font-weight-normal"), - medium: cx("font-weight-medium"), - bold: cx("font-weight-bold"), + normal: cx("weight-normal"), + medium: cx("weight-medium"), + bold: cx("weight-bold"), }; interface _TypographyProps { hierarchy: H; size: AllowedSizes; - color?: Color | "inherit"; + color?: Color; children: JSX.Element; weight?: Weight; family?: Family; inverted?: boolean; tag?: Tag; class?: string; - classList?: Record; } export const Typography = (props: _TypographyProps) => { const family = () => - `font-family-${props.family || defaultFamilyMap[props.hierarchy]}`; - - const color = () => colorFor(props.color, props.inverted); - - const classList = mergeProps(props.classList, { - "font-body": props.hierarchy === "body" || !props.hierarchy, - "font-label": props.hierarchy === "label", - "font-title": props.hierarchy === "title", - "font-headline": props.hierarchy === "headline", - "font-teaser": props.hierarchy === "teaser", - }); + `family-${props.family || defaultFamilyMap[props.hierarchy]}`; + const hierarchy = () => props.hierarchy || "body"; + const size = () => sizeHierarchyMap[props.hierarchy][props.size] as string; + const weight = () => weightMap[props.weight || "normal"]; + const color = () => fgClass(props.color, props.inverted); return ( {props.children} diff --git a/pkgs/clan-app/ui/src/components/v2/colors.ts b/pkgs/clan-app/ui/src/components/v2/colors.ts new file mode 100644 index 000000000..ca01cedc2 --- /dev/null +++ b/pkgs/clan-app/ui/src/components/v2/colors.ts @@ -0,0 +1,41 @@ +export type Color = + | "primary" + | "secondary" + | "tertiary" + | "quaternary" + | "inherit"; + +export const AllColors: Color[] = [ + "primary", + "secondary", + "tertiary", + "quaternary", + "inherit", +]; + +const colorMap: Record = { + primary: "fg-def-1", + secondary: "fg-def-2", + tertiary: "fg-def-3", + quaternary: "fg-def-4", + inherit: "text-inherit", +}; + +const invertedColorMap: Record = { + primary: "fg-inv-1", + secondary: "fg-inv-2", + tertiary: "fg-inv-3", + quaternary: "fg-inv-4", + inherit: "text-inherit", +}; + +export const fgClass = ( + color: Color | "inherit" = "primary", + inverted = false, +) => { + if (color === "inherit") { + return "text-inherit"; + } + + return inverted ? invertedColorMap[color] : colorMap[color]; +}; diff --git a/pkgs/clan-app/ui/src/components/v2/index.css b/pkgs/clan-app/ui/src/components/v2/index.css index c1e6f9ddc..3bb290896 100644 --- a/pkgs/clan-app/ui/src/components/v2/index.css +++ b/pkgs/clan-app/ui/src/components/v2/index.css @@ -102,3 +102,13 @@ html { user-select: none; /* Standard */ } + +@layer utilities { + .no-scrollbar::-webkit-scrollbar { + display: none; + } + .no-scrollbar { + -ms-overflow-style: none; + scrollbar-width: none; + } +} diff --git a/pkgs/clan-app/ui/tailwind.config.ts b/pkgs/clan-app/ui/tailwind.config.ts index a540896b4..28490b700 100644 --- a/pkgs/clan-app/ui/tailwind.config.ts +++ b/pkgs/clan-app/ui/tailwind.config.ts @@ -1,4 +1,3 @@ -import typography from "@tailwindcss/typography"; import kobalte from "@kobalte/tailwindcss"; import core from "./tailwind/core-plugin"; @@ -6,7 +5,7 @@ import core from "./tailwind/core-plugin"; const config = { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: {}, - plugins: [typography, core, kobalte], + plugins: [core, kobalte], }; export default config; diff --git a/pkgs/clan-app/ui/tailwind/core-plugin.ts b/pkgs/clan-app/ui/tailwind/core-plugin.ts index 3c1fbe6c9..6ce8f44fe 100644 --- a/pkgs/clan-app/ui/tailwind/core-plugin.ts +++ b/pkgs/clan-app/ui/tailwind/core-plugin.ts @@ -1,5 +1,4 @@ import plugin from "tailwindcss/plugin"; -import { typography } from "./typography"; // @ts-expect-error: lib of tailwind has no types import { parseColor } from "tailwindcss/lib/util/color"; @@ -154,7 +153,7 @@ export default plugin.withOptions( backgroundColor: theme("colors.secondary.700"), }, ".bg-inv-acc-4": { - backgroundColor: theme("colors.secondary.900"), + backgroundColor: theme("colors.primary.950"), }, // bg inverse accent @@ -252,7 +251,7 @@ export default plugin.withOptions( 500: toRGB("#526f6f"), 600: toRGB("#4b6767"), 700: toRGB("#345253"), - 800: toRGB("#2b4647"), + 800: toRGB("#2e4a4b"), 900: toRGB("#203637"), 950: toRGB("#162324"), }, @@ -316,7 +315,6 @@ export default plugin.withOptions( "0px 0px 0px 1px white, 0px 0px 0px 2px var(--clr-bg-inv-acc-4, #203637), 2px 2px 0px 0px var(--clr-bg-inv-acc-2, #4F747A) inset", }, }, - ...typography, }, }), ); diff --git a/pkgs/clan-app/ui/tailwind/typography.ts b/pkgs/clan-app/ui/tailwind/typography.ts deleted file mode 100644 index a21e8a6ce..000000000 --- a/pkgs/clan-app/ui/tailwind/typography.ts +++ /dev/null @@ -1,22 +0,0 @@ -import defaultTheme from "tailwindcss/defaultTheme"; -import type { Config } from "tailwindcss"; - -export const typography: Partial = { - fontFamily: { - sans: ["Archivo SemiCondensed", ...defaultTheme.fontFamily.sans], - }, - fontSize: { - ...defaultTheme.fontSize, - title: ["1.125rem", { lineHeight: "124%" }], - "title-m": ["1.25rem", { lineHeight: "124%" }], - "title-l": ["1.375rem", { lineHeight: "124%" }], - label: ["0.8125rem", { lineHeight: "100%" }], - "label-s": ["0.75rem", { lineHeight: "100%" }], - "label-xs": ["0.6875rem", { lineHeight: "124%" }], - }, - // textColor: { - // ...defaultTheme.textColor, - // primary: "#0D1416", - // secondary: "#2C4347", - // }, -} as const;