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