From aefa1db2bdb034a4f98a11346351a3d860aa9fac Mon Sep 17 00:00:00 2001 From: Andrii Arsenin Date: Thu, 9 Apr 2026 09:56:59 +0300 Subject: [PATCH] feat(client): add ThemeSwitcher and relocate stories to .storybook/ - add lucide-react for Sun/Moon icons - add useTheme hook persisting choice to localStorage with prefers-color-scheme fallback - add ThemeSwitcher button component wired into SidePanel header - add blocking inline script to index.html to prevent flash-of-wrong-theme - move all *.stories.tsx from src/ into .storybook/stories/ and update imports - update main.ts stories glob to .storybook/stories/** --- client/.storybook/main.ts | 4 +-- .../stories}/Badge.stories.tsx | 2 +- .../stories}/Breadcrumbs.stories.tsx | 2 +- .../stories}/Button.stories.tsx | 2 +- .../stories}/Card.stories.tsx | 6 ++--- .../stories}/Colors.stories.tsx | 2 +- .../stories}/Input.stories.tsx | 2 +- .../stories}/Select.stories.tsx | 2 +- .../stories}/SidePanel.stories.tsx | 26 +------------------ .../stories}/Table.stories.tsx | 8 +++--- .../stories/ThemeSwitcher.stories.tsx | 13 ++++++++++ client/index.html | 9 +++++++ client/package.json | 1 + client/src/App.module.css | 7 +++++ client/src/App.tsx | 9 +++++-- client/src/hooks/useTheme.ts | 26 +++++++++++++++++++ .../ui/ThemeSwitcher/ThemeSwitcher.module.css | 26 +++++++++++++++++++ client/src/ui/ThemeSwitcher/ThemeSwitcher.tsx | 18 +++++++++++++ client/src/ui/index.ts | 2 ++ package-lock.json | 10 +++++++ 20 files changed, 134 insertions(+), 43 deletions(-) rename client/{src/ui/Badge => .storybook/stories}/Badge.stories.tsx (95%) rename client/{src/ui/Breadcrumbs => .storybook/stories}/Breadcrumbs.stories.tsx (93%) rename client/{src/ui/Button => .storybook/stories}/Button.stories.tsx (95%) rename client/{src/ui/Card => .storybook/stories}/Card.stories.tsx (89%) rename client/{src/ui => .storybook/stories}/Colors.stories.tsx (98%) rename client/{src/ui/Input => .storybook/stories}/Input.stories.tsx (94%) rename client/{src/ui/Select => .storybook/stories}/Select.stories.tsx (96%) rename client/{src/ui/SidePanel => .storybook/stories}/SidePanel.stories.tsx (80%) rename client/{src/ui/Table => .storybook/stories}/Table.stories.tsx (82%) create mode 100644 client/.storybook/stories/ThemeSwitcher.stories.tsx create mode 100644 client/src/hooks/useTheme.ts create mode 100644 client/src/ui/ThemeSwitcher/ThemeSwitcher.module.css create mode 100644 client/src/ui/ThemeSwitcher/ThemeSwitcher.tsx diff --git a/client/.storybook/main.ts b/client/.storybook/main.ts index 82a430d..03f32f9 100644 --- a/client/.storybook/main.ts +++ b/client/.storybook/main.ts @@ -13,8 +13,8 @@ function getAbsolutePath(value: string) { } const config: StorybookConfig = { "stories": [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)" + "./stories/**/*.mdx", + "./stories/**/*.stories.@(js|jsx|mjs|ts|tsx)" ], "addons": [ getAbsolutePath('@storybook/addon-vitest'), diff --git a/client/src/ui/Badge/Badge.stories.tsx b/client/.storybook/stories/Badge.stories.tsx similarity index 95% rename from client/src/ui/Badge/Badge.stories.tsx rename to client/.storybook/stories/Badge.stories.tsx index 959872b..f0ccbf1 100644 --- a/client/src/ui/Badge/Badge.stories.tsx +++ b/client/.storybook/stories/Badge.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Badge } from './Badge'; +import { Badge } from '../../src/ui/Badge/Badge'; const meta: Meta = { title: 'UI/Badge', diff --git a/client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx b/client/.storybook/stories/Breadcrumbs.stories.tsx similarity index 93% rename from client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx rename to client/.storybook/stories/Breadcrumbs.stories.tsx index 6c34c7c..cce2a08 100644 --- a/client/src/ui/Breadcrumbs/Breadcrumbs.stories.tsx +++ b/client/.storybook/stories/Breadcrumbs.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Breadcrumbs } from './Breadcrumbs'; +import { Breadcrumbs } from '../../src/ui/Breadcrumbs/Breadcrumbs'; const meta: Meta = { title: 'UI/Breadcrumbs', diff --git a/client/src/ui/Button/Button.stories.tsx b/client/.storybook/stories/Button.stories.tsx similarity index 95% rename from client/src/ui/Button/Button.stories.tsx rename to client/.storybook/stories/Button.stories.tsx index d5b5160..0c8229c 100644 --- a/client/src/ui/Button/Button.stories.tsx +++ b/client/.storybook/stories/Button.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Button } from './Button'; +import { Button } from '../../src/ui/Button/Button'; const meta: Meta = { title: 'UI/Button', diff --git a/client/src/ui/Card/Card.stories.tsx b/client/.storybook/stories/Card.stories.tsx similarity index 89% rename from client/src/ui/Card/Card.stories.tsx rename to client/.storybook/stories/Card.stories.tsx index fc4ca3d..350f386 100644 --- a/client/src/ui/Card/Card.stories.tsx +++ b/client/.storybook/stories/Card.stories.tsx @@ -1,8 +1,8 @@ import React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Card } from './Card'; -import { Badge } from '../Badge/Badge'; -import { Button } from '../Button/Button'; +import { Card } from '../../src/ui/Card/Card'; +import { Badge } from '../../src/ui/Badge/Badge'; +import { Button } from '../../src/ui/Button/Button'; const meta: Meta = { title: 'UI/Card', diff --git a/client/src/ui/Colors.stories.tsx b/client/.storybook/stories/Colors.stories.tsx similarity index 98% rename from client/src/ui/Colors.stories.tsx rename to client/.storybook/stories/Colors.stories.tsx index 0aec90c..7e36793 100644 --- a/client/src/ui/Colors.stories.tsx +++ b/client/.storybook/stories/Colors.stories.tsx @@ -32,7 +32,7 @@ const surfaceColors = [ { name: 'Primary fg', token: '--color-primary-fg', hex: '#2D3339' }, ]; -function Swatch({ name, token, hex, role }: { name: string; token: string; hex: string; role?: string }) {; +function Swatch({ name, token, hex, role }: { name: string; token: string; hex: string; role?: string }) { return (
= { title: 'UI/Input', diff --git a/client/src/ui/Select/Select.stories.tsx b/client/.storybook/stories/Select.stories.tsx similarity index 96% rename from client/src/ui/Select/Select.stories.tsx rename to client/.storybook/stories/Select.stories.tsx index 868c235..511b7f7 100644 --- a/client/src/ui/Select/Select.stories.tsx +++ b/client/.storybook/stories/Select.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { Select } from './Select'; +import { Select } from '../../src/ui/Select/Select'; const ENVIRONMENTS = [ { value: 'dev', label: 'Development' }, diff --git a/client/src/ui/SidePanel/SidePanel.stories.tsx b/client/.storybook/stories/SidePanel.stories.tsx similarity index 80% rename from client/src/ui/SidePanel/SidePanel.stories.tsx rename to client/.storybook/stories/SidePanel.stories.tsx index bd84d90..0a03f52 100644 --- a/client/src/ui/SidePanel/SidePanel.stories.tsx +++ b/client/.storybook/stories/SidePanel.stories.tsx @@ -1,5 +1,5 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; -import { SidePanel } from './SidePanel'; +import { SidePanel } from '../../src/ui/SidePanel/SidePanel'; const NAV_ITEMS = ['Dashboard', 'Scenarios', 'Environments', 'Reports', 'Settings']; @@ -57,27 +57,3 @@ export const Default: Story = { children: , }, }; - -export const DefaultCollapsed: Story = { - args: { - header: 'Navigation', - children: , - defaultCollapsed: true, - }, -}; - -export const NarrowWidth: Story = { - args: { - header: 'Filters', - width: 180, - children: , - }, -}; - -export const WideWidth: Story = { - args: { - header: 'Scenarios', - width: 320, - children: , - }, -}; diff --git a/client/src/ui/Table/Table.stories.tsx b/client/.storybook/stories/Table.stories.tsx similarity index 82% rename from client/src/ui/Table/Table.stories.tsx rename to client/.storybook/stories/Table.stories.tsx index ae00bcd..3469eb5 100644 --- a/client/src/ui/Table/Table.stories.tsx +++ b/client/.storybook/stories/Table.stories.tsx @@ -1,6 +1,6 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import { Table } from './Table'; -import { Badge } from '../Badge/Badge'; +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { Table } from '../../src/ui/Table/Table'; +import { Badge } from '../../src/ui/Badge/Badge'; const meta: Meta = { title: 'UI/Table', @@ -10,8 +10,6 @@ const meta: Meta = { }; export default meta; -// ── Simple string rows ──────────────────────────────────────────────────────── - interface User { id: number; name: string; diff --git a/client/.storybook/stories/ThemeSwitcher.stories.tsx b/client/.storybook/stories/ThemeSwitcher.stories.tsx new file mode 100644 index 0000000..55be3fb --- /dev/null +++ b/client/.storybook/stories/ThemeSwitcher.stories.tsx @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react-vite'; +import { ThemeSwitcher } from '../../src/ui/ThemeSwitcher/ThemeSwitcher'; + +const meta: Meta = { + title: 'UI/ThemeSwitcher', + component: ThemeSwitcher, + parameters: { layout: 'centered' }, + tags: ['autodocs'], +}; +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/client/index.html b/client/index.html index ce6e9e8..ad8df9e 100644 --- a/client/index.html +++ b/client/index.html @@ -4,6 +4,15 @@ Liquio QA Bot +
diff --git a/client/package.json b/client/package.json index d05d64b..e1f0373 100644 --- a/client/package.json +++ b/client/package.json @@ -11,6 +11,7 @@ "build-storybook": "storybook build" }, "dependencies": { + "lucide-react": "^1.7.0", "react": "^19.1.0", "react-dom": "^19.1.0" }, diff --git a/client/src/App.module.css b/client/src/App.module.css index 7387948..6edddbe 100644 --- a/client/src/App.module.css +++ b/client/src/App.module.css @@ -7,6 +7,13 @@ overflow: hidden; } +.header { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; +} + .brand { font-weight: 700; font-size: 1rem; diff --git a/client/src/App.tsx b/client/src/App.tsx index 892b958..179877e 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,5 +1,5 @@ import styles from './App.module.css'; -import { SidePanel } from './ui'; +import { SidePanel, ThemeSwitcher } from './ui'; import { EnvironmentsPage } from './pages/EnvironmentsPage'; import { KeysPage } from './pages/KeysPage'; import { SessionsPage } from './pages/SessionsPage'; @@ -21,7 +21,12 @@ export default function App() { return (
QA Bot} + header={ +
+ QA Bot + +
+ } width={220} >