From c8650c8492c1f9bece1de63dd8c06cb16997fa1b Mon Sep 17 00:00:00 2001 From: junkisai Date: Fri, 3 Oct 2025 19:33:26 +0900 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=93=9A=20Add=20comprehensive=20Storyb?= =?UTF-8?q?ook=20stories=20for=20UI=20library=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Storybook stories for 20 components, icons, logos, and markers to improve component documentation and development experience: Components: - Avatar (size and user variants) - Callout (5 variants: default, danger, success, info, warning) - Collapsible (Radix UI wrapper) - ContextMenu (right-click menu) - CookieConsent (banner with accept/deny) - Drawer (Vaul wrapper) - DropdownMenu (with icons and radio items) - GridTable (semantic definition list) - Input (sizes, icons, error states) - Modal (Radix Dialog wrapper) - Popover (Radix UI wrapper) - RadioGroup (Radix UI wrapper) - RemoveButton (transparent/solid variants) - Resizable (horizontal/vertical panels) - RoundBadge (4 color variants) - Select (Radix UI wrapper with groups) - Sidebar (complex navigation component) - Spinner (3 sizes) - Switch (Radix UI wrapper) - Tabs (Radix UI wrapper) Showcases: - Icons: All 80+ icons (custom + Lucide) in 2D grid layout - Logos: 6 logos with proper sizing - Markers: 3 SVG markers with visual examples All stories use Object.entries() pattern for DRY code and automatic name generation. --- .../src/components/Avatar/Avatar.stories.tsx | 135 ++++++++++ .../components/Callout/Callout.stories.tsx | 76 ++++++ .../Collapsible/Collapsible.stories.tsx | 47 ++++ .../ContextMenu/ContextMenu.stories.tsx | 60 +++++ .../CookieConsent/CookieConsent.stories.tsx | 31 +++ .../src/components/Drawer/Drawer.stories.tsx | 56 ++++ .../DropdownMenu/DropdownMenu.stories.tsx | 105 ++++++++ .../GridTable/GridTable.stories.tsx | 52 ++++ .../ui/src/components/Input/Input.stories.tsx | 115 +++++++++ .../ui/src/components/Modal/Modal.stories.tsx | 61 +++++ .../components/Popover/Popover.stories.tsx | 52 ++++ .../RadioGroup/RadioGroup.stories.tsx | 34 +++ .../RemoveButton/RemoveButton.stories.tsx | 63 +++++ .../Resizable/Resizable.stories.tsx | 84 ++++++ .../RoundBadge/RoundBadge.stories.tsx | 74 ++++++ .../src/components/Select/Select.stories.tsx | 80 ++++++ .../components/Sidebar/Sidebar.stories.tsx | 76 ++++++ .../components/Spinner/Spinner.stories.tsx | 40 +++ .../src/components/Switch/Switch.stories.tsx | 35 +++ .../ui/src/components/Tabs/Tabs.stories.tsx | 48 ++++ .../packages/ui/src/icons/index.stories.tsx | 239 ++++++++++++++++++ .../packages/ui/src/logos/index.stories.tsx | 105 ++++++++ .../packages/ui/src/markers/index.stories.tsx | 107 ++++++++ 23 files changed, 1775 insertions(+) create mode 100644 frontend/packages/ui/src/components/Avatar/Avatar.stories.tsx create mode 100644 frontend/packages/ui/src/components/Callout/Callout.stories.tsx create mode 100644 frontend/packages/ui/src/components/Collapsible/Collapsible.stories.tsx create mode 100644 frontend/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx create mode 100644 frontend/packages/ui/src/components/CookieConsent/CookieConsent.stories.tsx create mode 100644 frontend/packages/ui/src/components/Drawer/Drawer.stories.tsx create mode 100644 frontend/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx create mode 100644 frontend/packages/ui/src/components/GridTable/GridTable.stories.tsx create mode 100644 frontend/packages/ui/src/components/Input/Input.stories.tsx create mode 100644 frontend/packages/ui/src/components/Modal/Modal.stories.tsx create mode 100644 frontend/packages/ui/src/components/Popover/Popover.stories.tsx create mode 100644 frontend/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx create mode 100644 frontend/packages/ui/src/components/RemoveButton/RemoveButton.stories.tsx create mode 100644 frontend/packages/ui/src/components/Resizable/Resizable.stories.tsx create mode 100644 frontend/packages/ui/src/components/RoundBadge/RoundBadge.stories.tsx create mode 100644 frontend/packages/ui/src/components/Select/Select.stories.tsx create mode 100644 frontend/packages/ui/src/components/Sidebar/Sidebar.stories.tsx create mode 100644 frontend/packages/ui/src/components/Spinner/Spinner.stories.tsx create mode 100644 frontend/packages/ui/src/components/Switch/Switch.stories.tsx create mode 100644 frontend/packages/ui/src/components/Tabs/Tabs.stories.tsx create mode 100644 frontend/packages/ui/src/icons/index.stories.tsx create mode 100644 frontend/packages/ui/src/logos/index.stories.tsx create mode 100644 frontend/packages/ui/src/markers/index.stories.tsx diff --git a/frontend/packages/ui/src/components/Avatar/Avatar.stories.tsx b/frontend/packages/ui/src/components/Avatar/Avatar.stories.tsx new file mode 100644 index 0000000000..2b7b02e9fa --- /dev/null +++ b/frontend/packages/ui/src/components/Avatar/Avatar.stories.tsx @@ -0,0 +1,135 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Avatar } from './Avatar' + +const meta = { + component: Avatar, + argTypes: { + initial: { + control: 'text', + description: 'Initial character displayed in avatar', + }, + size: { + control: 'select', + options: ['xxs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'], + description: 'Size of the avatar', + }, + user: { + control: 'select', + options: [ + 'you', + 'collaborator-1', + 'collaborator-2', + 'collaborator-3', + 'collaborator-4', + 'collaborator-5', + 'collaborator-6', + 'collaborator-7', + 'collaborator-8', + 'collaborator-9', + 'collaborator-10', + 'collaborator-11', + 'jack', + ], + description: 'User type to determine background color', + }, + color: { + control: 'color', + description: 'Custom background color', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + initial: 'A', + size: 'md', + user: 'you', + }, +} + +export const ExtraExtraSmall: Story = { + args: { + initial: 'A', + size: 'xxs', + }, +} + +export const ExtraSmall: Story = { + args: { + initial: 'A', + size: 'xs', + }, +} + +export const Small: Story = { + args: { + initial: 'A', + size: 'sm', + }, +} + +export const Medium: Story = { + args: { + initial: 'A', + size: 'md', + }, +} + +export const Large: Story = { + args: { + initial: 'A', + size: 'lg', + }, +} + +export const ExtraLarge: Story = { + args: { + initial: 'A', + size: 'xl', + }, +} + +export const TwoExtraLarge: Story = { + args: { + initial: 'A', + size: '2xl', + }, +} + +export const Collaborator1: Story = { + args: { + initial: 'B', + user: 'collaborator-1', + }, +} + +export const Collaborator2: Story = { + args: { + initial: 'C', + user: 'collaborator-2', + }, +} + +export const Collaborator3: Story = { + args: { + initial: 'D', + user: 'collaborator-3', + }, +} + +export const Jack: Story = { + args: { + initial: 'J', + user: 'jack', + }, +} + +export const CustomColor: Story = { + args: { + initial: 'X', + color: '#ff6b6b', + }, +} diff --git a/frontend/packages/ui/src/components/Callout/Callout.stories.tsx b/frontend/packages/ui/src/components/Callout/Callout.stories.tsx new file mode 100644 index 0000000000..7473007264 --- /dev/null +++ b/frontend/packages/ui/src/components/Callout/Callout.stories.tsx @@ -0,0 +1,76 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Callout } from './Callout' + +const meta = { + component: Callout, + argTypes: { + variant: { + control: 'select', + options: ['default', 'danger', 'success', 'info', 'warning'], + description: 'The visual style of the callout', + }, + device: { + control: 'select', + options: ['default', 'mobile'], + description: 'Device-specific styling', + }, + children: { + control: 'text', + description: 'Content to display in the callout', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + variant: 'default', + children: 'This is a default callout message.', + }, +} + +export const Danger: Story = { + args: { + variant: 'danger', + children: 'This is a danger callout message.', + }, +} + +export const Success: Story = { + args: { + variant: 'success', + children: 'This is a success callout message.', + }, +} + +export const Info: Story = { + args: { + variant: 'info', + children: 'This is an info callout message.', + }, +} + +export const Warning: Story = { + args: { + variant: 'warning', + children: 'This is a warning callout message.', + }, +} + +export const Mobile: Story = { + args: { + variant: 'info', + device: 'mobile', + children: 'This callout is optimized for mobile devices.', + }, +} + +export const LongContent: Story = { + args: { + variant: 'info', + children: + 'This is a callout with a longer message to demonstrate how the component handles multiple lines of text. The content should wrap properly and maintain good readability.', + }, +} diff --git a/frontend/packages/ui/src/components/Collapsible/Collapsible.stories.tsx b/frontend/packages/ui/src/components/Collapsible/Collapsible.stories.tsx new file mode 100644 index 0000000000..ff5fc858ed --- /dev/null +++ b/frontend/packages/ui/src/components/Collapsible/Collapsible.stories.tsx @@ -0,0 +1,47 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { Button } from '../Button' +import { + CollapsibleContent, + CollapsibleRoot, + CollapsibleTrigger, +} from './Collapsible' + +const CollapsibleExample = () => { + const [open, setOpen] = useState(false) + + return ( + +
+ + + + +
+ This is the collapsible content. It can contain any React elements. + You can add more content here as needed. +
+
+
+
+ ) +} + +const meta = { + component: CollapsibleRoot, + parameters: { + docs: { + description: { + component: + 'A Radix UI Collapsible wrapper. Use CollapsibleRoot, CollapsibleTrigger, and CollapsibleContent together to create collapsible sections.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx b/frontend/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx new file mode 100644 index 0000000000..35bc2b9cfa --- /dev/null +++ b/frontend/packages/ui/src/components/ContextMenu/ContextMenu.stories.tsx @@ -0,0 +1,60 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { ContextMenu } from './ContextMenu' + +const meta = { + component: ContextMenu, + parameters: { + docs: { + description: { + component: + 'Right-click on the trigger element to open the context menu.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + TriggerElement: ( +
+ Right-click here +
+ ), + ContextMenuElement: Menu Item, + onClick: () => {}, + }, +} + +export const WithIcon: Story = { + args: { + TriggerElement: ( +
+ Right-click to see icon menu +
+ ), + ContextMenuElement: ( +
+ 🔧 + Settings +
+ ), + onClick: () => {}, + }, +} diff --git a/frontend/packages/ui/src/components/CookieConsent/CookieConsent.stories.tsx b/frontend/packages/ui/src/components/CookieConsent/CookieConsent.stories.tsx new file mode 100644 index 0000000000..7b9d74595b --- /dev/null +++ b/frontend/packages/ui/src/components/CookieConsent/CookieConsent.stories.tsx @@ -0,0 +1,31 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { CookieConsent } from './CookieConsent' + +const meta = { + component: CookieConsent, + argTypes: { + open: { + control: 'boolean', + description: 'Whether the cookie consent banner is visible', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Open: Story = { + args: { + open: true, + onClickAccept: () => {}, + onClickDeny: () => {}, + }, +} + +export const Closed: Story = { + args: { + open: false, + onClickAccept: () => {}, + onClickDeny: () => {}, + }, +} diff --git a/frontend/packages/ui/src/components/Drawer/Drawer.stories.tsx b/frontend/packages/ui/src/components/Drawer/Drawer.stories.tsx new file mode 100644 index 0000000000..31d6b69844 --- /dev/null +++ b/frontend/packages/ui/src/components/Drawer/Drawer.stories.tsx @@ -0,0 +1,56 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Button } from '../Button' +import { + DrawerClose, + DrawerContent, + DrawerDescription, + DrawerPortal, + DrawerRoot, + DrawerTitle, + DrawerTrigger, +} from './Drawer' + +const DrawerExample = () => { + return ( + + + + + + +
+ Drawer Title + + This is a drawer component built with Vaul. You can add any + content here. + +
+ + + +
+
+
+
+
+ ) +} + +const meta = { + component: DrawerRoot, + parameters: { + docs: { + description: { + component: + 'A Vaul Drawer wrapper. Use DrawerRoot, DrawerTrigger, DrawerPortal, DrawerContent, DrawerTitle, DrawerDescription, and DrawerClose together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx b/frontend/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx new file mode 100644 index 0000000000..05cec9ec7a --- /dev/null +++ b/frontend/packages/ui/src/components/DropdownMenu/DropdownMenu.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { Button } from '../Button' +import { + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuPortal, + DropdownMenuRadioGroup, + DropdownMenuRadioItem, + DropdownMenuRoot, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from './DropdownMenu' + +const DropdownMenuExample = () => { + return ( + + + + + + + Item 1 + Item 2 + + Delete + + + + ) +} + +const DropdownMenuWithIconsExample = () => { + return ( + + + + + + + 📄}> + New File + + 📁}> + New Folder + + + 🗑️}> + Delete + + + + + ) +} + +const DropdownMenuRadioExample = () => { + const [value, setValue] = useState('option1') + + return ( + + + + + + + Select an option + + + + + + + + + + ) +} + +const meta = { + component: DropdownMenuRoot, + parameters: { + docs: { + description: { + component: 'A Radix UI DropdownMenu wrapper with various compositions.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} + +export const WithIcons: Story = { + render: () => , +} + +export const RadioItems: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/GridTable/GridTable.stories.tsx b/frontend/packages/ui/src/components/GridTable/GridTable.stories.tsx new file mode 100644 index 0000000000..5e7d6d2a77 --- /dev/null +++ b/frontend/packages/ui/src/components/GridTable/GridTable.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { + GridTableDd, + GridTableDt, + GridTableHeader, + GridTableItem, + GridTableRoot, + GridTableRow, +} from './GridTable' + +const GridTableExample = () => { + return ( + + Product Details + + Name + Example Product + + + Price + $99.99 + + + Stock + Available + + Category + + Electronics + + + ) +} + +const meta = { + component: GridTableRoot, + parameters: { + docs: { + description: { + component: + 'A semantic HTML definition list styled as a grid table. Use GridTableRoot, GridTableHeader, GridTableItem, GridTableDt, GridTableDd, and GridTableRow together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/Input/Input.stories.tsx b/frontend/packages/ui/src/components/Input/Input.stories.tsx new file mode 100644 index 0000000000..bee00922f0 --- /dev/null +++ b/frontend/packages/ui/src/components/Input/Input.stories.tsx @@ -0,0 +1,115 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Input } from './Input' + +const meta = { + component: Input, + argTypes: { + size: { + control: 'select', + options: ['md', 'sm', 'xs'], + description: 'Size of the input', + }, + align: { + control: 'select', + options: ['left', 'right'], + description: 'Text alignment', + }, + disabled: { + control: 'boolean', + description: 'Whether the input is disabled', + }, + readOnly: { + control: 'boolean', + description: 'Whether the input is read-only', + }, + error: { + control: 'boolean', + description: 'Whether the input has an error state', + }, + placeholder: { + control: 'text', + description: 'Placeholder text', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + placeholder: 'Enter text...', + size: 'md', + }, +} + +export const Medium: Story = { + args: { + placeholder: 'Medium input', + size: 'md', + }, +} + +export const Small: Story = { + args: { + placeholder: 'Small input', + size: 'sm', + }, +} + +export const ExtraSmall: Story = { + args: { + placeholder: 'Extra small input', + size: 'xs', + }, +} + +export const RightAligned: Story = { + args: { + placeholder: '100', + align: 'right', + }, +} + +export const Disabled: Story = { + args: { + placeholder: 'Disabled input', + disabled: true, + }, +} + +export const ReadOnly: Story = { + args: { + value: 'Read-only value', + readOnly: true, + }, +} + +export const WithError: Story = { + args: { + placeholder: 'Input with error', + error: true, + }, +} + +export const WithLeftIcon: Story = { + args: { + placeholder: 'Search...', + leftIcon: 🔍, + }, +} + +export const WithRightIcon: Story = { + args: { + placeholder: 'Email', + rightIcon: ✉️, + }, +} + +export const WithBothIcons: Story = { + args: { + placeholder: 'Username', + leftIcon: 👤, + rightIcon: , + }, +} diff --git a/frontend/packages/ui/src/components/Modal/Modal.stories.tsx b/frontend/packages/ui/src/components/Modal/Modal.stories.tsx new file mode 100644 index 0000000000..dd2314901f --- /dev/null +++ b/frontend/packages/ui/src/components/Modal/Modal.stories.tsx @@ -0,0 +1,61 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Button } from '../Button' +import { + ModalActions, + ModalClose, + ModalConfirm, + ModalContent, + ModalDescription, + ModalOverlay, + ModalPortal, + ModalRoot, + ModalTitle, + ModalTrigger, +} from './Modal' + +const ModalExample = () => { + return ( + + + + + + + + Modal Title + + This is a modal dialog built with Radix UI. You can add any content + here. + + + + + + + + + + + + + ) +} + +const meta = { + component: ModalRoot, + parameters: { + docs: { + description: { + component: + 'A Radix UI Dialog wrapper. Use ModalRoot, ModalTrigger, ModalPortal, ModalOverlay, ModalContent, ModalTitle, ModalDescription, ModalActions, ModalClose, and ModalConfirm together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/Popover/Popover.stories.tsx b/frontend/packages/ui/src/components/Popover/Popover.stories.tsx new file mode 100644 index 0000000000..2893596ff6 --- /dev/null +++ b/frontend/packages/ui/src/components/Popover/Popover.stories.tsx @@ -0,0 +1,52 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Button } from '../Button' +import { + PopoverClose, + PopoverContent, + PopoverPortal, + PopoverRoot, + PopoverTrigger, +} from './Popover' + +const PopoverExample = () => { + return ( + + + + + + +
+

Popover Title

+

+ This is a popover component built with Radix UI. You can add any + content here. +

+ + + +
+
+
+
+ ) +} + +const meta = { + component: PopoverRoot, + parameters: { + docs: { + description: { + component: + 'A Radix UI Popover wrapper. Use PopoverRoot, PopoverTrigger, PopoverPortal, PopoverContent, and PopoverClose together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx b/frontend/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx new file mode 100644 index 0000000000..e26d128637 --- /dev/null +++ b/frontend/packages/ui/src/components/RadioGroup/RadioGroup.stories.tsx @@ -0,0 +1,34 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { RadioGroup, RadioGroupItem } from './RadioGroup' + +const RadioGroupExample = () => { + const [value, setValue] = useState('option1') + + return ( + + + + + + ) +} + +const meta = { + component: RadioGroup, + parameters: { + docs: { + description: { + component: + 'A Radix UI RadioGroup wrapper. Use RadioGroup and RadioGroupItem together to create radio button groups.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/RemoveButton/RemoveButton.stories.tsx b/frontend/packages/ui/src/components/RemoveButton/RemoveButton.stories.tsx new file mode 100644 index 0000000000..e2d78fdafc --- /dev/null +++ b/frontend/packages/ui/src/components/RemoveButton/RemoveButton.stories.tsx @@ -0,0 +1,63 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { RemoveButton } from './RemoveButton' + +const meta = { + component: RemoveButton, + argTypes: { + variant: { + control: 'select', + options: ['transparent', 'solid'], + description: 'The visual style of the remove button', + }, + size: { + control: 'select', + options: ['sm', 'md'], + description: 'The size of the remove button', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + variant: 'transparent', + size: 'sm', + }, +} + +export const Transparent: Story = { + args: { + variant: 'transparent', + size: 'sm', + }, +} + +export const Solid: Story = { + args: { + variant: 'solid', + size: 'sm', + }, +} + +export const Small: Story = { + args: { + variant: 'transparent', + size: 'sm', + }, +} + +export const Medium: Story = { + args: { + variant: 'transparent', + size: 'md', + }, +} + +export const SolidMedium: Story = { + args: { + variant: 'solid', + size: 'md', + }, +} diff --git a/frontend/packages/ui/src/components/Resizable/Resizable.stories.tsx b/frontend/packages/ui/src/components/Resizable/Resizable.stories.tsx new file mode 100644 index 0000000000..b31cd0144e --- /dev/null +++ b/frontend/packages/ui/src/components/Resizable/Resizable.stories.tsx @@ -0,0 +1,84 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, +} from './Resizable' + +const ResizableExample = () => { + const [isResizing, setIsResizing] = useState(false) + + return ( + setIsResizing(false)} + style={{ height: '400px', border: '1px solid #ccc' }} + > + +
+ Panel 1 - Resize me +
+
+ setIsResizing(isDragging)} + /> + +
+ Panel 2 +
+
+
+ ) +} + +const ResizableVerticalExample = () => { + const [isResizing, setIsResizing] = useState(false) + + return ( + setIsResizing(false)} + style={{ height: '400px', border: '1px solid #ccc' }} + > + +
+ Top Panel +
+
+ setIsResizing(isDragging)} + /> + +
+ Bottom Panel +
+
+
+ ) +} + +const meta = { + component: ResizablePanelGroup, + parameters: { + docs: { + description: { + component: + 'A react-resizable-panels wrapper. Use ResizablePanelGroup, ResizablePanel, and ResizableHandle together to create resizable layouts.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Horizontal: Story = { + render: () => , +} + +export const Vertical: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/RoundBadge/RoundBadge.stories.tsx b/frontend/packages/ui/src/components/RoundBadge/RoundBadge.stories.tsx new file mode 100644 index 0000000000..e695e4f09f --- /dev/null +++ b/frontend/packages/ui/src/components/RoundBadge/RoundBadge.stories.tsx @@ -0,0 +1,74 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { RoundBadge } from './RoundBadge' + +const meta = { + component: RoundBadge, + argTypes: { + variant: { + control: 'select', + options: ['default', 'yellow', 'green', 'purple'], + description: 'Visual style variant', + }, + showCap: { + control: 'boolean', + description: 'Show cap for large numbers', + }, + maxValue: { + control: 'number', + description: 'Maximum value before showing "+"', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + children: '5', + variant: 'default', + }, +} + +export const Yellow: Story = { + args: { + children: '3', + variant: 'yellow', + }, +} + +export const Green: Story = { + args: { + children: '7', + variant: 'green', + }, +} + +export const Purple: Story = { + args: { + children: '2', + variant: 'purple', + }, +} + +export const WithCap: Story = { + args: { + children: 150, + showCap: true, + maxValue: 99, + }, +} + +export const LargeNumber: Story = { + args: { + children: 1000, + showCap: true, + }, +} + +export const TextContent: Story = { + args: { + children: 'NEW', + variant: 'yellow', + }, +} diff --git a/frontend/packages/ui/src/components/Select/Select.stories.tsx b/frontend/packages/ui/src/components/Select/Select.stories.tsx new file mode 100644 index 0000000000..0a30e70b4f --- /dev/null +++ b/frontend/packages/ui/src/components/Select/Select.stories.tsx @@ -0,0 +1,80 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectLabel, + SelectSeparator, + SelectTrigger, + SelectValue, +} from './Select' + +const SelectExample = () => { + const [value, setValue] = useState('apple') + + return ( + + ) +} + +const SelectWithSeparatorExample = () => { + const [value, setValue] = useState('') + + return ( + + ) +} + +const meta = { + component: Select, + parameters: { + docs: { + description: { + component: + 'A Radix UI Select wrapper. Use Select, SelectTrigger, SelectValue, SelectContent, SelectGroup, SelectLabel, SelectItem, and SelectSeparator together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} + +export const WithGroups: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/Sidebar/Sidebar.stories.tsx b/frontend/packages/ui/src/components/Sidebar/Sidebar.stories.tsx new file mode 100644 index 0000000000..4e9412ba04 --- /dev/null +++ b/frontend/packages/ui/src/components/Sidebar/Sidebar.stories.tsx @@ -0,0 +1,76 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { + Sidebar, + SidebarContent, + SidebarFooter, + SidebarGroup, + SidebarGroupContent, + SidebarGroupLabel, + SidebarHeader, + SidebarMenu, + SidebarMenuButton, + SidebarMenuItem, + SidebarProvider, + SidebarTrigger, +} from './Sidebar' + +const SidebarExample = () => { + const [open, setOpen] = useState(true) + + return ( + + + +
+ +
+
+ + + Navigation + + + + Dashboard + + + Projects + + + Settings + + + + + + +
Footer Content
+
+
+
+ ) +} + +const meta = { + component: SidebarProvider, + parameters: { + docs: { + description: { + component: + 'A comprehensive sidebar component with collapsible functionality. Use SidebarProvider, Sidebar, SidebarHeader, SidebarContent, SidebarFooter, SidebarTrigger, and menu components together.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/Spinner/Spinner.stories.tsx b/frontend/packages/ui/src/components/Spinner/Spinner.stories.tsx new file mode 100644 index 0000000000..b14a8ba25a --- /dev/null +++ b/frontend/packages/ui/src/components/Spinner/Spinner.stories.tsx @@ -0,0 +1,40 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { Spinner } from './Spinner' + +const meta = { + component: Spinner, + argTypes: { + size: { + control: 'select', + options: ['12', '14', '16'], + description: 'Size of the spinner', + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + size: '16', + }, +} + +export const Size12: Story = { + args: { + size: '12', + }, +} + +export const Size14: Story = { + args: { + size: '14', + }, +} + +export const Size16: Story = { + args: { + size: '16', + }, +} diff --git a/frontend/packages/ui/src/components/Switch/Switch.stories.tsx b/frontend/packages/ui/src/components/Switch/Switch.stories.tsx new file mode 100644 index 0000000000..3ecc3617b5 --- /dev/null +++ b/frontend/packages/ui/src/components/Switch/Switch.stories.tsx @@ -0,0 +1,35 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { useState } from 'react' +import { SwitchRoot, SwitchThumb } from './Switch' + +const SwitchExample = () => { + const [checked, setChecked] = useState(false) + + return ( +
+ + + + {checked ? 'ON' : 'OFF'} +
+ ) +} + +const meta = { + component: SwitchRoot, + parameters: { + docs: { + description: { + component: + 'A Radix UI Switch wrapper. Use SwitchRoot and SwitchThumb together to create a toggle switch.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/components/Tabs/Tabs.stories.tsx b/frontend/packages/ui/src/components/Tabs/Tabs.stories.tsx new file mode 100644 index 0000000000..fec1b1b466 --- /dev/null +++ b/frontend/packages/ui/src/components/Tabs/Tabs.stories.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { TabsContent, TabsList, TabsRoot, TabsTrigger } from './Tabs' + +const TabsExample = () => { + return ( + + + Tab 1 + Tab 2 + Tab 3 + + +
+ Content for Tab 1 +
+
+ +
+ Content for Tab 2 +
+
+ +
+ Content for Tab 3 +
+
+
+ ) +} + +const meta = { + component: TabsRoot, + parameters: { + docs: { + description: { + component: + 'A Radix UI Tabs wrapper. Use TabsRoot, TabsList, TabsTrigger, and TabsContent together to create tabbed interfaces.', + }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/icons/index.stories.tsx b/frontend/packages/ui/src/icons/index.stories.tsx new file mode 100644 index 0000000000..1ec1c247cb --- /dev/null +++ b/frontend/packages/ui/src/icons/index.stories.tsx @@ -0,0 +1,239 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { + AlertTriangle, + ArrowRight, + ArrowUpRight, + BookMarked, + BookText, + Building, + Building2, + CardinalityZeroOrManyLeftIcon, + CardinalityZeroOrOneLeftIcon, + CardinalityZeroOrOneRightIcon, + Check, + ChevronDown, + ChevronLeft, + ChevronRight, + ChevronsUpDown, + ChevronUp, + CircleHelp, + ClipboardList, + CodeXml, + Copy, + CornerDownLeft, + DiamondFillIcon, + DiamondIcon, + Dot, + Download, + Ellipsis, + ErdIcon, + Eye, + EyeClosed, + EyeOff, + FacebookIcon, + FileCode, + FileJson, + FileText, + Fingerprint, + FoldVertical, + GitBranch, + GitPullRequestArrow, + Globe, + GotoIcon, + Group, + Hash, + Info, + InfoIcon, + KeyRound, + LayoutGrid, + Library, + Link, + Link2, + List, + Lock, + LogOut, + Megaphone, + Menu, + MessageSquareText, + MessagesSquare, + Mic, + Minus, + PanelLeft, + PanelTop, + Pause, + Plus, + RectangleHorizontal, + Rows3, + Scan, + Search, + Settings, + Sparkle, + Table2, + TidyUpIcon, + UnfoldVertical, + Ungroup, + Upload, + Users, + Waypoints, + Wrench, + X, + XCircle, + XIcon, +} from './index' + +const IconShowcase = () => { + const iconsMap = { + AlertTriangle, + ArrowRight, + ArrowUpRight, + BookMarked, + BookText, + Building, + Building2, + CardinalityZeroOrManyLeftIcon, + CardinalityZeroOrOneLeftIcon, + CardinalityZeroOrOneRightIcon, + Check, + ChevronDown, + ChevronLeft, + ChevronRight, + ChevronsUpDown, + ChevronUp, + CircleHelp, + ClipboardList, + CodeXml, + Copy, + CornerDownLeft, + DiamondFillIcon, + DiamondIcon, + Dot, + Download, + Ellipsis, + ErdIcon, + Eye, + EyeClosed, + EyeOff, + FacebookIcon, + FileCode, + FileJson, + FileText, + Fingerprint, + FoldVertical, + GitBranch, + GitPullRequestArrow, + Globe, + GotoIcon, + Group, + Hash, + Info, + InfoIcon, + KeyRound, + LayoutGrid, + Library, + Link, + Link2, + List, + Lock, + LogOut, + Megaphone, + Menu, + MessageSquareText, + MessagesSquare, + Mic, + Minus, + PanelLeft, + PanelTop, + Pause, + Plus, + RectangleHorizontal, + Rows3, + Scan, + Search, + Settings, + Sparkle, + Table2, + TidyUpIcon, + UnfoldVertical, + Ungroup, // cspell:disable-line + Upload, + Users, + Waypoints, + Wrench, + X, + XCircle, + XIcon, + } + + const _icons = Object.entries(iconsMap).map(([name, Icon]) => ({ + name, + Icon, + })) + + const iconItemStyle = { + display: 'flex', + flexDirection: 'column' as const, + alignItems: 'center', + gap: '8px', + padding: '16px', + border: '1px solid #e0e0e0', + borderRadius: '8px', + minWidth: '120px', + } + + const iconNameStyle = { + fontSize: '12px', + textAlign: 'center' as const, + wordBreak: 'break-word' as const, + } + + const headingStyle = { + fontSize: '20px', + fontWeight: 'bold', + marginBottom: '16px', + } + + const gridStyle = { + display: 'flex', + flexWrap: 'wrap' as const, + gap: '16px', + } + + return ( +
+

All Icons

+
+ {_icons.map(({ name, Icon }) => ( +
+ {name.endsWith('Icon') ? ( + + ) : ( + + )} + {name} +
+ ))} +
+
+ ) +} + +const meta = { + parameters: { + docs: { + description: { + component: + 'A comprehensive showcase of all available icons in the UI library, including both custom icons and Lucide React icons.', + }, + }, + initialGlobals: { + backgrounds: { value: 'light' }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const AllIcons: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/logos/index.stories.tsx b/frontend/packages/ui/src/logos/index.stories.tsx new file mode 100644 index 0000000000..c837662150 --- /dev/null +++ b/frontend/packages/ui/src/logos/index.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { + GithubLogo, + LiamDbLogo, + LiamLogo, + LiamLogoMark, + LinkedInLogo, + XLogo, +} from './index' + +const LogoShowcase = () => { + const logosMap = { + GithubLogo, + LiamDbLogo, + LiamLogo, + LiamLogoMark, + LinkedInLogo, + XLogo, + } + + const logos = Object.entries(logosMap).map(([name, Logo]) => ({ + name, + Logo, + })) + + return ( +
+

+ All Logos +

+
+ {logos.map(({ name, Logo }) => ( +
+
+ +
+ + {name} + +
+ ))} +
+
+ ) +} + +const meta = { + parameters: { + docs: { + description: { + component: + 'A comprehensive showcase of all available logos in the UI library.', + }, + }, + initialGlobals: { + backgrounds: { value: 'light' }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const AllLogos: Story = { + render: () => , +} diff --git a/frontend/packages/ui/src/markers/index.stories.tsx b/frontend/packages/ui/src/markers/index.stories.tsx new file mode 100644 index 0000000000..61944d3fbd --- /dev/null +++ b/frontend/packages/ui/src/markers/index.stories.tsx @@ -0,0 +1,107 @@ +import type { Meta, StoryObj } from '@storybook/nextjs' +import { + CardinalityZeroOrManyLeftMarker, + CardinalityZeroOrOneLeftMarker, + CardinalityZeroOrOneRightMarker, +} from './index' + +const MarkerShowcase = () => { + const markersMap = { + CardinalityZeroOrManyLeftMarker, + CardinalityZeroOrOneLeftMarker, + CardinalityZeroOrOneRightMarker, + } + + const markers = Object.entries(markersMap).map(([name, Marker]) => ({ + name, + Marker, + })) + + return ( +
+

+ All Markers +

+
+ {markers.map(({ name, Marker }) => ( +
+ + + {name} example + + + + {name} + +
+ ))} +
+
+ ) +} + +const meta = { + parameters: { + docs: { + description: { + component: + 'A comprehensive showcase of all available SVG markers in the UI library. These markers are used for ERD relationship lines.', + }, + }, + initialGlobals: { + backgrounds: { value: 'light' }, + }, + }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const AllMarkers: Story = { + render: () => , +} From dcb19ac9ac86d55e8bd336cb56c515c09b768140 Mon Sep 17 00:00:00 2001 From: junkisai Date: Fri, 3 Oct 2025 19:33:48 +0900 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=A7=B9=20Remove=20unnecessary=20biome?= =?UTF-8?q?-ignore=20comments=20from=20Select=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Delete false positive ref reassignment ignore comments across SelectTrigger, SelectScrollUpButton, SelectScrollDownButton, SelectContent, SelectLabel, SelectItem, and SelectSeparator - These comments were incorrectly flagging ref prop passing as parameter reassignment - Refs are correctly passed as props to Radix UI primitives without any reassignment Improves code cleanliness by removing unnecessary linter suppression comments. --- frontend/packages/ui/src/components/Select/Select.tsx | 7 ------- 1 file changed, 7 deletions(-) diff --git a/frontend/packages/ui/src/components/Select/Select.tsx b/frontend/packages/ui/src/components/Select/Select.tsx index f6f99fa32b..d0d850d33a 100644 --- a/frontend/packages/ui/src/components/Select/Select.tsx +++ b/frontend/packages/ui/src/components/Select/Select.tsx @@ -21,7 +21,6 @@ export const SelectTrigger = forwardRef< ComponentPropsWithoutRef >(({ className, children, ...props }, ref) => ( >(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => ( (({ className, children, position = 'popper', ...props }, ref) => ( >(({ className, ...props }, ref) => ( >(({ className, children, ...props }, ref) => ( >(({ className, ...props }, ref) => ( Date: Mon, 6 Oct 2025 13:59:41 +0900 Subject: [PATCH 3/5] Remove unnecessary mobile story --- .../ui/src/components/Callout/Callout.stories.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/packages/ui/src/components/Callout/Callout.stories.tsx b/frontend/packages/ui/src/components/Callout/Callout.stories.tsx index 7473007264..1316d9227d 100644 --- a/frontend/packages/ui/src/components/Callout/Callout.stories.tsx +++ b/frontend/packages/ui/src/components/Callout/Callout.stories.tsx @@ -59,14 +59,6 @@ export const Warning: Story = { }, } -export const Mobile: Story = { - args: { - variant: 'info', - device: 'mobile', - children: 'This callout is optimized for mobile devices.', - }, -} - export const LongContent: Story = { args: { variant: 'info', From 597ecf59228ee8016f86b83a0e712aff16d5a06a Mon Sep 17 00:00:00 2001 From: "claude[bot]" <41898282+claude[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 05:04:53 +0000 Subject: [PATCH 4/5] fix: remove button-in-button nesting in Modal stories Replace Button components with text content in ModalClose, ModalConfirm, and ModalTrigger to prevent invalid HTML structure. Co-authored-by: Hirotaka Miyagi --- .../ui/src/components/Modal/Modal.stories.tsx | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/frontend/packages/ui/src/components/Modal/Modal.stories.tsx b/frontend/packages/ui/src/components/Modal/Modal.stories.tsx index dd2314901f..fc3b5a2d1f 100644 --- a/frontend/packages/ui/src/components/Modal/Modal.stories.tsx +++ b/frontend/packages/ui/src/components/Modal/Modal.stories.tsx @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from '@storybook/nextjs' -import { Button } from '../Button' import { ModalActions, ModalClose, @@ -16,9 +15,7 @@ import { const ModalExample = () => { return ( - - - + Open Modal @@ -28,12 +25,8 @@ const ModalExample = () => { here. - - - - - - + Cancel + Confirm From 5b62b857a7f6128e62f8ef09287d79982d9e93f9 Mon Sep 17 00:00:00 2001 From: junkisai Date: Wed, 8 Oct 2025 14:25:53 +0900 Subject: [PATCH 5/5] =?UTF-8?q?=E2=99=BF=20Enhance=20Switch=20component=20?= =?UTF-8?q?story=20with=20accessibility=20and=20styling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add proper label with useId() for switch-label association - Include aria-label for screen reader support - Implement visual styling with CSS variables for consistent theming - Add smooth transitions for state changes (checked/unchecked) - Style SwitchThumb with proper dimensions, shadows, and transform animations Improves component documentation and demonstrates best practices for accessible switch implementation. --- .../src/components/Switch/Switch.stories.tsx | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/frontend/packages/ui/src/components/Switch/Switch.stories.tsx b/frontend/packages/ui/src/components/Switch/Switch.stories.tsx index 3ecc3617b5..1bcf6e55dd 100644 --- a/frontend/packages/ui/src/components/Switch/Switch.stories.tsx +++ b/frontend/packages/ui/src/components/Switch/Switch.stories.tsx @@ -1,14 +1,46 @@ import type { Meta, StoryObj } from '@storybook/nextjs' -import { useState } from 'react' +import { useId, useState } from 'react' import { SwitchRoot, SwitchThumb } from './Switch' const SwitchExample = () => { const [checked, setChecked] = useState(false) + const switchId = useId() return (
- - + + + {checked ? 'ON' : 'OFF'}