feat: storybook
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
import type { StorybookConfig } from 'storybook-react-rsbuild';
|
||||||
|
|
||||||
|
/* Dev-only playground. Never shipped — package `files` is ["dist"]. */
|
||||||
|
const config: StorybookConfig = {
|
||||||
|
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(ts|tsx)'],
|
||||||
|
addons: ['@storybook/addon-docs'],
|
||||||
|
framework: {
|
||||||
|
name: 'storybook-react-rsbuild',
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
// Prop tables in autodocs come from the components' TS types.
|
||||||
|
reactDocgen: 'react-docgen-typescript',
|
||||||
|
reactDocgenTypescriptOptions: {
|
||||||
|
shouldExtractLiteralValuesFromEnum: true,
|
||||||
|
// Keep our own props; drop the noise inherited from node_modules.
|
||||||
|
propFilter: (prop) =>
|
||||||
|
prop.parent ? !/node_modules/.test(prop.parent.fileName) : true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import { addons } from 'storybook/manager-api';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
addons.setConfig({ theme });
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* Calm dark canvas. Uses the felt's subtle radial glow (no heavy fixed
|
||||||
|
grain) so the story sits on-brand without flooding the frame. The
|
||||||
|
--bg-glow / --ink tokens come from the imported stylesheet and react
|
||||||
|
to the data-theme toggle automatically. */
|
||||||
|
.sb-show-main {
|
||||||
|
background-color: var(--ink);
|
||||||
|
background-image: var(--bg-glow);
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-attachment: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Autodocs preview blocks: same dark surface as the canvas. */
|
||||||
|
.docs-story,
|
||||||
|
.sbdocs-preview {
|
||||||
|
background-color: var(--ink) !important;
|
||||||
|
background-image: var(--bg-glow);
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { useEffect, type ReactNode } from 'react';
|
||||||
|
import type { Preview, Decorator } from 'storybook-react-rsbuild';
|
||||||
|
import { Tooltip } from 'radix-ui';
|
||||||
|
|
||||||
|
/* The shipped library surface, exactly as a consumer would load it. */
|
||||||
|
import '../src/styles/fonts.css';
|
||||||
|
import '../src/styles/index.css';
|
||||||
|
/* Storybook-only canvas styling (background, docs blocks). */
|
||||||
|
import './preview.css';
|
||||||
|
import theme from './theme';
|
||||||
|
|
||||||
|
/* Toolbar theme switch → drives `data-theme` on <html>, same lever as the
|
||||||
|
library's ThemeProvider. The frame stays content-sized; the dark canvas
|
||||||
|
comes from preview.css, so stories never balloon to full-viewport. */
|
||||||
|
function ThemeFrame({ theme, children }: { theme: string; children: ReactNode }) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.setAttribute('data-theme', theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
return <Tooltip.Provider delayDuration={200}>{children}</Tooltip.Provider>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const withModernSk: Decorator = (Story, context) => (
|
||||||
|
<ThemeFrame theme={(context.globals.theme as string) ?? 'dark'}>
|
||||||
|
<Story />
|
||||||
|
</ThemeFrame>
|
||||||
|
);
|
||||||
|
|
||||||
|
const preview: Preview = {
|
||||||
|
tags: ['autodocs'],
|
||||||
|
decorators: [withModernSk],
|
||||||
|
parameters: {
|
||||||
|
layout: 'centered',
|
||||||
|
backgrounds: { disable: true },
|
||||||
|
docs: { theme },
|
||||||
|
controls: {
|
||||||
|
matchers: { color: /(background|color)$/i, date: /Date$/i },
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
storySort: { order: ['Getting Started', 'Inputs', 'Selection', 'Feedback', 'Overlays', 'Data Display', 'Layout'] },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
globalTypes: {
|
||||||
|
theme: {
|
||||||
|
description: 'ModernSK theme',
|
||||||
|
defaultValue: 'dark',
|
||||||
|
toolbar: {
|
||||||
|
title: 'Theme',
|
||||||
|
icon: 'contrast',
|
||||||
|
items: [
|
||||||
|
{ value: 'dark', title: 'Dark', icon: 'moon' },
|
||||||
|
{ value: 'light', title: 'Light', icon: 'sun' },
|
||||||
|
],
|
||||||
|
dynamicTitle: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default preview;
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { create } from 'storybook/theming';
|
||||||
|
|
||||||
|
/* Dark, lime-accented chrome so the Storybook UI + autodocs match the
|
||||||
|
components instead of framing them in stock white. */
|
||||||
|
export default create({
|
||||||
|
base: 'dark',
|
||||||
|
|
||||||
|
brandTitle: 'ModernSK',
|
||||||
|
colorPrimary: '#bef264',
|
||||||
|
colorSecondary: '#bef264',
|
||||||
|
|
||||||
|
// App
|
||||||
|
appBg: '#0f100d',
|
||||||
|
appContentBg: '#0f100d',
|
||||||
|
appPreviewBg: '#0f100d',
|
||||||
|
appBorderColor: '#2a2c22',
|
||||||
|
appBorderRadius: 10,
|
||||||
|
|
||||||
|
// Typography
|
||||||
|
fontBase: "'Onest', system-ui, -apple-system, 'Segoe UI', sans-serif",
|
||||||
|
fontCode: "'Geist Mono', ui-monospace, 'SF Mono', Menlo, monospace",
|
||||||
|
|
||||||
|
// Text
|
||||||
|
textColor: '#f3f4ee',
|
||||||
|
textInverseColor: '#0f100d',
|
||||||
|
textMutedColor: '#9a9c8c',
|
||||||
|
|
||||||
|
// Toolbar / sidebar bars
|
||||||
|
barBg: '#16170f',
|
||||||
|
barTextColor: '#9a9c8c',
|
||||||
|
barSelectedColor: '#bef264',
|
||||||
|
barHoverColor: '#bef264',
|
||||||
|
|
||||||
|
// Inputs
|
||||||
|
inputBg: '#1c1d16',
|
||||||
|
inputBorder: '#2a2c22',
|
||||||
|
inputTextColor: '#f3f4ee',
|
||||||
|
inputBorderRadius: 8,
|
||||||
|
});
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Button } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Inputs/Button',
|
||||||
|
component: Button,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Tactile push button. Four variants and an optional small size; everything else is a native `<button>`, so all standard button props pass through.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
variant: {
|
||||||
|
control: 'inline-radio',
|
||||||
|
options: ['key', 'primary', 'ember', 'ghost'],
|
||||||
|
description: 'Visual emphasis. `key` is the default neutral button.',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
control: 'inline-radio',
|
||||||
|
options: [undefined, 'sm'],
|
||||||
|
description: 'Omit for default; `sm` for the compact size.',
|
||||||
|
},
|
||||||
|
iconOnly: { control: 'boolean', description: 'Square padding for a single glyph.' },
|
||||||
|
disabled: { control: 'boolean' },
|
||||||
|
children: { control: 'text' },
|
||||||
|
},
|
||||||
|
args: { children: 'Button', variant: 'key' },
|
||||||
|
} satisfies Meta<typeof Button>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {};
|
||||||
|
|
||||||
|
export const Variants: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 12, flexWrap: 'wrap' }}>
|
||||||
|
<Button variant="key">Key</Button>
|
||||||
|
<Button variant="primary">Primary</Button>
|
||||||
|
<Button variant="ember">Ember</Button>
|
||||||
|
<Button variant="ghost">Ghost</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Sizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||||
|
<Button variant="primary">Default</Button>
|
||||||
|
<Button variant="primary" size="sm">
|
||||||
|
Small
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Disabled: Story = {
|
||||||
|
args: { disabled: true, variant: 'primary', children: 'Disabled' },
|
||||||
|
};
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
List,
|
||||||
|
Row,
|
||||||
|
Table,
|
||||||
|
THead,
|
||||||
|
TBody,
|
||||||
|
Tr,
|
||||||
|
Th,
|
||||||
|
Td,
|
||||||
|
Badge,
|
||||||
|
} from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Data Display/Surfaces',
|
||||||
|
component: Card,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component: 'Cards, selectable lists/rows, and the bordered table.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Card>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const CardSurface: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Card style={{ maxWidth: 320, padding: 20 }}>
|
||||||
|
<h3 className="modern-sk-h3">Storage</h3>
|
||||||
|
<p className="modern-sk-body">128 GB of 256 GB used.</p>
|
||||||
|
</Card>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ListRows: Story = {
|
||||||
|
render: () => (
|
||||||
|
<List style={{ width: 320 }}>
|
||||||
|
<Row selected>General</Row>
|
||||||
|
<Row>Appearance</Row>
|
||||||
|
<Row>Notifications</Row>
|
||||||
|
<Row>Privacy</Row>
|
||||||
|
</List>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DataTable: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Table>
|
||||||
|
<THead>
|
||||||
|
<Tr>
|
||||||
|
<Th>Device</Th>
|
||||||
|
<Th>Status</Th>
|
||||||
|
<Th>Battery</Th>
|
||||||
|
</Tr>
|
||||||
|
</THead>
|
||||||
|
<TBody>
|
||||||
|
<Tr selected>
|
||||||
|
<Td>MacBook Pro</Td>
|
||||||
|
<Td><Badge variant="lime" dot>Online</Badge></Td>
|
||||||
|
<Td>82%</Td>
|
||||||
|
</Tr>
|
||||||
|
<Tr>
|
||||||
|
<Td>iPhone 16</Td>
|
||||||
|
<Td><Badge variant="neutral">Idle</Badge></Td>
|
||||||
|
<Td>54%</Td>
|
||||||
|
</Tr>
|
||||||
|
</TBody>
|
||||||
|
</Table>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Info, CheckCircle, Warning, XCircle } from '@phosphor-icons/react';
|
||||||
|
import { Progress, Spinner, Callout, Badge, Chip, Button } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Feedback/Status',
|
||||||
|
component: Progress,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Progress bar, spinner, callouts, badges and chips — the status + signalling family.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Progress>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
function ProgressDemo() {
|
||||||
|
const [v, setV] = useState(40);
|
||||||
|
return (
|
||||||
|
<div style={{ width: 320, display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
<Progress value={v} />
|
||||||
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
|
<Button size="sm" onClick={() => setV((x) => Math.max(0, x - 10))}>−10</Button>
|
||||||
|
<Button size="sm" variant="primary" onClick={() => setV((x) => Math.min(100, x + 10))}>+10</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ProgressBar: Story = { render: () => <ProgressDemo /> };
|
||||||
|
|
||||||
|
export const Spinners: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 16, alignItems: 'center' }}>
|
||||||
|
<Spinner size="sm" />
|
||||||
|
<Spinner />
|
||||||
|
<Spinner size="lg" />
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Callouts: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12, maxWidth: 460 }}>
|
||||||
|
<Callout variant="info" icon={<Info size={18} />}>Sync runs in the background.</Callout>
|
||||||
|
<Callout variant="success" icon={<CheckCircle size={18} />}>All changes saved.</Callout>
|
||||||
|
<Callout variant="warning" icon={<Warning size={18} />}>Storage is almost full.</Callout>
|
||||||
|
<Callout variant="danger" icon={<XCircle size={18} />}>Failed to reach the server.</Callout>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Badges: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Badge variant="lime">Lime</Badge>
|
||||||
|
<Badge variant="ember">Ember</Badge>
|
||||||
|
<Badge variant="neutral">Neutral</Badge>
|
||||||
|
<Badge variant="outline">Outline</Badge>
|
||||||
|
<Badge variant="lime" dot>Online</Badge>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Chips: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 10, flexWrap: 'wrap' }}>
|
||||||
|
<Chip>Design</Chip>
|
||||||
|
<Chip onRemove={() => {}}>Removable</Chip>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Gear, Plus, Trash } from '@phosphor-icons/react';
|
||||||
|
import { IconButton } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Inputs/IconButton',
|
||||||
|
component: IconButton,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Square button for a single icon. Shares the Button variants; sizes are `sm` / default / `lg`.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
argTypes: {
|
||||||
|
variant: { control: 'inline-radio', options: ['key', 'primary', 'ember', 'ghost'] },
|
||||||
|
size: { control: 'inline-radio', options: ['sm', undefined, 'lg'] },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof IconButton>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {
|
||||||
|
args: { variant: 'key', children: <Gear size={18} /> },
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Variants: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 12 }}>
|
||||||
|
<IconButton variant="key" aria-label="settings"><Gear size={18} /></IconButton>
|
||||||
|
<IconButton variant="primary" aria-label="add"><Plus size={18} weight="bold" /></IconButton>
|
||||||
|
<IconButton variant="ember" aria-label="delete"><Trash size={18} /></IconButton>
|
||||||
|
<IconButton variant="ghost" aria-label="settings"><Gear size={18} /></IconButton>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Sizes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 12, alignItems: 'center' }}>
|
||||||
|
<IconButton size="sm" variant="primary" aria-label="add"><Plus size={14} weight="bold" /></IconButton>
|
||||||
|
<IconButton variant="primary" aria-label="add"><Plus size={18} weight="bold" /></IconButton>
|
||||||
|
<IconButton size="lg" variant="primary" aria-label="add"><Plus size={22} weight="bold" /></IconButton>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Meta } from '@storybook/addon-docs/blocks';
|
||||||
|
|
||||||
|
<Meta title="Getting Started/Introduction" />
|
||||||
|
|
||||||
|
# ModernSK
|
||||||
|
|
||||||
|
Tactile, dark-first React components built on [Radix](https://www.radix-ui.com/) primitives.
|
||||||
|
Old-iOS skeuomorphism × macOS Sequoia neatness × Ubuntu warmth.
|
||||||
|
|
||||||
|
This Storybook is the **development playground** — it is never published with the
|
||||||
|
package. Use it to browse every component, read its prop table (the **Docs** tab on
|
||||||
|
each story), and try props live in the **Controls** panel.
|
||||||
|
|
||||||
|
## Using the library in an app
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import 'modern-sk/styles.css'; // required — tokens + components
|
||||||
|
import 'modern-sk/fonts.css'; // optional — branded faces
|
||||||
|
import { ThemeProvider, TooltipProvider, Button } from 'modern-sk';
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
return (
|
||||||
|
<ThemeProvider>
|
||||||
|
<TooltipProvider delayDuration={200}>
|
||||||
|
<Button variant="primary">Click</Button>
|
||||||
|
</TooltipProvider>
|
||||||
|
</ThemeProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Theme
|
||||||
|
|
||||||
|
Use the **Theme** toggle in the toolbar above to flip every story between dark and
|
||||||
|
light. In an app the same lever is `data-theme` on `<html>`, managed by
|
||||||
|
`ThemeProvider` / `useTheme()`.
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
Components read the `--font-display`, `--font-sans` and `--font-mono` tokens. Import
|
||||||
|
`modern-sk/fonts.css` for the branded faces, or override those tokens to map the
|
||||||
|
components onto fonts your app already loads.
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Copy, Scissors, Trash, ArrowCounterClockwise } from '@phosphor-icons/react';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
Menu,
|
||||||
|
MenuTrigger,
|
||||||
|
MenuContent,
|
||||||
|
MenuItem,
|
||||||
|
MenuSeparator,
|
||||||
|
Dialog,
|
||||||
|
AlertDialog,
|
||||||
|
Button,
|
||||||
|
} from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Overlays/Floating',
|
||||||
|
component: Tooltip,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Floating surfaces — Tooltip, dropdown Menu, Dialog and AlertDialog. All are Radix-backed and portal-rendered.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Tooltip>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const TooltipStory: Story = {
|
||||||
|
name: 'Tooltip',
|
||||||
|
render: () => (
|
||||||
|
<Tooltip content="Saved to iCloud">
|
||||||
|
<Button variant="ghost">Hover me</Button>
|
||||||
|
</Tooltip>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DropdownMenu: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Menu>
|
||||||
|
<MenuTrigger asChild>
|
||||||
|
<Button>Open menu</Button>
|
||||||
|
</MenuTrigger>
|
||||||
|
<MenuContent>
|
||||||
|
<MenuItem icon={<Copy size={16} />} shortcut="⌘C">Copy</MenuItem>
|
||||||
|
<MenuItem icon={<Scissors size={16} />} shortcut="⌘X">Cut</MenuItem>
|
||||||
|
<MenuItem icon={<ArrowCounterClockwise size={16} />} shortcut="⌘Z">Undo</MenuItem>
|
||||||
|
<MenuSeparator />
|
||||||
|
<MenuItem icon={<Trash size={16} />}>Delete</MenuItem>
|
||||||
|
</MenuContent>
|
||||||
|
</Menu>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ModalDialog: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Dialog
|
||||||
|
trigger={<Button variant="primary">Open dialog</Button>}
|
||||||
|
title="Rename project"
|
||||||
|
description="Choose a new name for this project."
|
||||||
|
footer={<Button variant="primary">Save</Button>}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Confirm: Story = {
|
||||||
|
render: () => (
|
||||||
|
<AlertDialog
|
||||||
|
trigger={<Button variant="ember">Delete…</Button>}
|
||||||
|
title="Delete this project?"
|
||||||
|
description="This action cannot be undone."
|
||||||
|
actionLabel="Delete"
|
||||||
|
destructive
|
||||||
|
onAction={() => {}}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Select } from '../components/ui';
|
||||||
|
|
||||||
|
const items = [
|
||||||
|
{ value: 'sequoia', label: 'Sequoia' },
|
||||||
|
{ value: 'sonoma', label: 'Sonoma' },
|
||||||
|
{ value: 'ventura', label: 'Ventura' },
|
||||||
|
{ value: 'monterey', label: 'Monterey' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Inputs/Select',
|
||||||
|
component: Select,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Radix Select in the ModernSK skin. Pass `items` plus an optional `placeholder`; control it with `value` / `onValueChange` or leave it uncontrolled with `defaultValue`.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: { items, placeholder: 'Pick a release…', 'aria-label': 'macOS release' },
|
||||||
|
} satisfies Meta<typeof Select>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {};
|
||||||
|
|
||||||
|
export const WithDefault: Story = { args: { defaultValue: 'sonoma' } };
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import {
|
||||||
|
Switch,
|
||||||
|
Checkbox,
|
||||||
|
RadioGroup,
|
||||||
|
RadioItem,
|
||||||
|
SegmentedControl,
|
||||||
|
Control,
|
||||||
|
} from '../components/ui';
|
||||||
|
|
||||||
|
/* Grouped selection controls. Anchored on Switch for the docgen table;
|
||||||
|
the stories below showcase each control. */
|
||||||
|
const meta = {
|
||||||
|
title: 'Selection/Controls',
|
||||||
|
component: Switch,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Toggle, checkbox, radio group and segmented control. The `Control` helper pairs any of them with a clickable label.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Switch>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Switches: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', gap: 20, alignItems: 'center' }}>
|
||||||
|
<Switch defaultChecked />
|
||||||
|
<Switch />
|
||||||
|
<Control control={<Switch defaultChecked />}>Wi-Fi</Control>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Checkboxes: Story = {
|
||||||
|
render: () => (
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
<Control control={<Checkbox defaultChecked />}>Sync to iCloud</Control>
|
||||||
|
<Control control={<Checkbox />}>Share analytics</Control>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Radios: Story = {
|
||||||
|
render: () => (
|
||||||
|
<RadioGroup defaultValue="comfortable" style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
<Control control={<RadioItem value="compact" />}>Compact</Control>
|
||||||
|
<Control control={<RadioItem value="comfortable" />}>Comfortable</Control>
|
||||||
|
<Control control={<RadioItem value="spacious" />}>Spacious</Control>
|
||||||
|
</RadioGroup>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
function SegmentedDemo() {
|
||||||
|
const [v, setV] = useState('day');
|
||||||
|
return (
|
||||||
|
<SegmentedControl
|
||||||
|
value={v}
|
||||||
|
onValueChange={setV}
|
||||||
|
items={[
|
||||||
|
{ value: 'day', label: 'Day' },
|
||||||
|
{ value: 'week', label: 'Week' },
|
||||||
|
{ value: 'month', label: 'Month' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Segmented: Story = { render: () => <SegmentedDemo /> };
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Slider } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Inputs/Slider',
|
||||||
|
component: Slider,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Radix Slider in the carved-track skin. All Radix Slider props pass through (`defaultValue`, `min`, `max`, `step`, `onValueChange`).',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
decorators: [(Story) => <div style={{ width: 280 }}><Story /></div>],
|
||||||
|
} satisfies Meta<typeof Slider>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = { args: { defaultValue: [60], max: 100, step: 1 } };
|
||||||
|
|
||||||
|
export const Stepped: Story = { args: { defaultValue: [40], max: 100, step: 10 } };
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Tabs, TabsList, TabsContent } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Navigation/Tabs',
|
||||||
|
component: Tabs,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Radix Tabs. `Tabs` is the root, `TabsList` takes `items`, and `TabsContent` matches each `value`.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof Tabs>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {
|
||||||
|
render: () => (
|
||||||
|
<Tabs defaultValue="overview" style={{ width: 420 }}>
|
||||||
|
<TabsList
|
||||||
|
items={[
|
||||||
|
{ value: 'overview', label: 'Overview' },
|
||||||
|
{ value: 'activity', label: 'Activity' },
|
||||||
|
{ value: 'settings', label: 'Settings' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<TabsContent value="overview" style={{ paddingTop: 16 }} className="modern-sk-body">
|
||||||
|
Project at a glance.
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="activity" style={{ paddingTop: 16 }} className="modern-sk-body">
|
||||||
|
Recent activity feed.
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent value="settings" style={{ paddingTop: 16 }} className="modern-sk-body">
|
||||||
|
Preferences and configuration.
|
||||||
|
</TabsContent>
|
||||||
|
</Tabs>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { MagnifyingGlass } from '@phosphor-icons/react';
|
||||||
|
import { TextField, TextArea, SearchField } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Inputs/TextField',
|
||||||
|
component: TextField,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'Sunken text input. `TextField`, `TextArea`, and `SearchField` (icon + input) share the `modern-sk-field` look and forward all native props.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: { placeholder: 'Type here…' },
|
||||||
|
} satisfies Meta<typeof TextField>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {};
|
||||||
|
|
||||||
|
export const Disabled: Story = { args: { disabled: true, value: 'Read only' } };
|
||||||
|
|
||||||
|
export const Multiline: Story = {
|
||||||
|
render: () => <TextArea rows={4} placeholder="Multiple lines…" style={{ width: 320 }} />,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Search: Story = {
|
||||||
|
render: () => (
|
||||||
|
<SearchField
|
||||||
|
icon={<MagnifyingGlass size={16} />}
|
||||||
|
placeholder="Search…"
|
||||||
|
style={{ width: 280 }}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
};
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import type { Meta, StoryObj } from 'storybook-react-rsbuild';
|
||||||
|
import { Window, Badge, List, Row } from '../components/ui';
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: 'Layout/Window',
|
||||||
|
component: Window,
|
||||||
|
parameters: {
|
||||||
|
docs: {
|
||||||
|
description: {
|
||||||
|
component:
|
||||||
|
'macOS-style window chrome: traffic lights, a title, an optional `badge` slot, and arbitrary children for the body.',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
args: { title: 'Finder' },
|
||||||
|
} satisfies Meta<typeof Window>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
export const Playground: Story = {
|
||||||
|
render: (args) => (
|
||||||
|
<Window {...args} style={{ width: 380 }} badge={<Badge variant="lime" dot>Synced</Badge>}>
|
||||||
|
<List style={{ padding: 12 }}>
|
||||||
|
<Row selected>Documents</Row>
|
||||||
|
<Row>Downloads</Row>
|
||||||
|
<Row>Pictures</Row>
|
||||||
|
</List>
|
||||||
|
</Window>
|
||||||
|
),
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user