🎨 Add remaining component stories to storybook (#6940)

* final storybook stories

* release notes

* spelling mistake
This commit is contained in:
Michael Clark
2026-02-12 08:46:24 +00:00
committed by GitHub
parent 67d6592333
commit 155e4df219
9 changed files with 1194 additions and 0 deletions

View File

@@ -0,0 +1,153 @@
import { useRef, useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
import { Menu } from './Menu';
import { Popover } from './Popover';
import { View } from './View';
const meta = {
title: 'Components/Popover',
component: Popover,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Popover>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
render: () => {
const triggerRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button ref={triggerRef} onPress={() => setIsOpen(!isOpen)}>
Toggle Popover
</Button>
<Popover
triggerRef={triggerRef}
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<View style={{ padding: 10 }}>Popover content</View>
</Popover>
</>
);
},
parameters: {
docs: {
description: {
story: 'A basic popover triggered by a button click.',
},
},
},
};
export const WithMenu: Story = {
render: () => {
const triggerRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button ref={triggerRef} onPress={() => setIsOpen(!isOpen)}>
Open Menu
</Button>
<Popover
triggerRef={triggerRef}
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<Menu
onMenuSelect={() => setIsOpen(false)}
items={[
{ name: 'edit', text: 'Edit' },
{ name: 'duplicate', text: 'Duplicate' },
Menu.line,
{ name: 'delete', text: 'Delete' },
]}
/>
</Popover>
</>
);
},
parameters: {
docs: {
description: {
story:
'Popover containing a menu, a common pattern for dropdown menus.',
},
},
},
};
export const CustomPlacement: Story = {
render: () => {
const triggerRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button ref={triggerRef} onPress={() => setIsOpen(!isOpen)}>
Bottom Start
</Button>
<Popover
triggerRef={triggerRef}
placement="bottom start"
isOpen={isOpen}
onOpenChange={setIsOpen}
>
<View style={{ padding: 10 }}>
This popover is placed at bottom start.
</View>
</Popover>
</>
);
},
parameters: {
docs: {
description: {
story: 'Popover with custom placement.',
},
},
},
};
export const CustomStyle: Story = {
render: () => {
const triggerRef = useRef(null);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button ref={triggerRef} onPress={() => setIsOpen(!isOpen)}>
Styled Popover
</Button>
<Popover
triggerRef={triggerRef}
isOpen={isOpen}
onOpenChange={setIsOpen}
style={{ padding: 15, maxWidth: 250 }}
>
<View>
This popover has custom padding and a constrained max width for
longer content.
</View>
</Popover>
</>
);
},
parameters: {
docs: {
description: {
story: 'Popover with custom styles applied.',
},
},
},
};

View File

@@ -0,0 +1,178 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Menu } from './Menu';
import { Select } from './Select';
import { View } from './View';
const meta = {
title: 'Components/Select',
component: Select,
parameters: {
layout: 'centered',
docs: {
description: {
component: ' ', // Remove autogenerated description (generated from JSDoc) to replace with custom description below
},
},
},
tags: ['autodocs'],
} satisfies Meta<typeof Select>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
options: [
['apple', 'Apple'],
['banana', 'Banana'],
['cherry', 'Cherry'],
],
value: 'apple',
},
parameters: {
docs: {
description: {
story: 'A basic select dropdown with simple options.',
},
},
},
};
export const WithDefaultLabel: Story = {
args: {
options: [
['small', 'Small'],
['medium', 'Medium'],
['large', 'Large'],
],
value: '',
defaultLabel: 'Select a size...',
},
parameters: {
docs: {
description: {
story:
'When the selected value is not in the options, the defaultLabel is displayed.',
},
},
},
};
export const WithSeparator: Story = {
args: {
options: [
['recent-1', 'Budget 2024'],
['recent-2', 'Budget 2025'],
Menu.line,
['all', 'View All'],
],
value: 'recent-1',
},
parameters: {
docs: {
description: {
story: 'Select options can include separators using Menu.line.',
},
},
},
};
export const WithDisabledKeys: Story = {
args: {
options: [
['draft', 'Draft'],
['pending', 'Pending'],
['approved', 'Approved'],
['archived', 'Archived'],
],
value: 'draft',
disabledKeys: ['approved', 'archived'],
},
parameters: {
docs: {
description: {
story: 'Certain options can be disabled using the disabledKeys prop.',
},
},
},
};
export const BareVariant: Story = {
args: {
bare: true,
options: [
['day', 'Day'],
['week', 'Week'],
['month', 'Month'],
],
value: 'month',
},
parameters: {
docs: {
description: {
story:
'The bare variant renders the select without a bordered button style.',
},
},
},
};
export const Disabled: Story = {
args: {
options: [
['opt1', 'Option 1'],
['opt2', 'Option 2'],
],
value: 'opt1',
disabled: true,
},
parameters: {
docs: {
description: {
story: 'A disabled select that cannot be interacted with.',
},
},
},
};
export const Controlled: Story = {
args: {
options: [
['usd', 'USD - US Dollar'],
['eur', 'EUR - Euro'],
['gbp', 'GBP - British Pound'],
['jpy', 'JPY - Japanese Yen'],
],
value: 'usd',
},
render: function Render() {
const [value, setValue] = useState('usd');
return (
<View style={{ gap: 10, alignItems: 'flex-start' }}>
<Select
options={[
['usd', 'USD - US Dollar'],
['eur', 'EUR - Euro'],
['gbp', 'GBP - British Pound'],
['jpy', 'JPY - Japanese Yen'],
]}
value={value}
onChange={setValue}
/>
<span>Selected: {value}</span>
</View>
);
},
parameters: {
docs: {
description: {
story: 'A controlled select with external state management.',
},
},
},
};

View File

@@ -0,0 +1,140 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
import { SpaceBetween } from './SpaceBetween';
import { View } from './View';
const meta = {
title: 'Components/SpaceBetween',
component: SpaceBetween,
parameters: {
layout: 'centered',
},
args: {
style: {
display: 'flex',
},
},
tags: ['autodocs'],
} satisfies Meta<typeof SpaceBetween>;
export default meta;
type Story = StoryObj<typeof meta>;
const Box = ({ children }: { children: string }) => (
<View
style={{
padding: '10px 20px',
backgroundColor: '#e0e0e0',
borderRadius: 4,
display: 'flex',
}}
>
{children}
</View>
);
export const Horizontal: Story = {
args: {
direction: 'horizontal',
children: (
<>
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</>
),
},
parameters: {
docs: {
description: {
story:
'SpaceBetween lays out children horizontally with even spacing by default.',
},
},
},
};
export const Vertical: Story = {
args: {
direction: 'vertical',
children: (
<>
<Box>Item 1</Box>
<Box>Item 2</Box>
<Box>Item 3</Box>
</>
),
},
parameters: {
docs: {
description: {
story: 'Items laid out vertically with default spacing.',
},
},
},
};
export const CustomGap: Story = {
args: {
direction: 'horizontal',
gap: 30,
children: (
<>
<Box>Gap 30</Box>
<Box>Gap 30</Box>
<Box>Gap 30</Box>
</>
),
},
parameters: {
docs: {
description: {
story: 'Custom gap between items.',
},
},
},
};
export const NoWrap: Story = {
args: {
direction: 'horizontal',
wrap: false,
children: (
<>
<Box>No Wrap</Box>
<Box>No Wrap</Box>
<Box>No Wrap</Box>
<Box>No Wrap</Box>
</>
),
},
parameters: {
docs: {
description: {
story: 'Items will not wrap to the next line when wrap is false.',
},
},
},
};
export const WithButtons: Story = {
args: {
direction: 'horizontal',
gap: 10,
children: (
<>
<Button variant="bare">Cancel</Button>
<Button variant="primary">Save</Button>
</>
),
},
parameters: {
docs: {
description: {
story: 'A common use case: spacing action buttons.',
},
},
},
};

View File

@@ -0,0 +1,112 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Text } from './Text';
import { View } from './View';
const meta = {
title: 'Components/Text',
component: Text,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Text>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'This is a text element',
},
parameters: {
docs: {
description: {
story: 'A basic Text component renders as a span element.',
},
},
},
};
export const WithStyle: Story = {
args: {
children: 'Styled text',
style: {
fontSize: 18,
fontWeight: 'bold',
color: '#1a73e8',
},
},
parameters: {
docs: {
description: {
story: 'Text can accept custom styles via the style prop.',
},
},
},
};
export const FontSizes: Story = {
render: () => (
<View style={{ gap: 8 }}>
<Text style={{ fontSize: 12 }}>Small (12px)</Text>
<Text style={{ fontSize: 14 }}>Default (14px)</Text>
<Text style={{ fontSize: 18 }}>Medium (18px)</Text>
<Text style={{ fontSize: 24 }}>Large (24px)</Text>
<Text style={{ fontSize: 32 }}>Extra Large (32px)</Text>
</View>
),
parameters: {
docs: {
description: {
story: 'Text at various font sizes.',
},
},
},
};
export const FontWeights: Story = {
render: () => (
<View style={{ gap: 8 }}>
<Text style={{ fontWeight: 300 }}>Light (300)</Text>
<Text style={{ fontWeight: 400 }}>Normal (400)</Text>
<Text style={{ fontWeight: 500 }}>Medium (500)</Text>
<Text style={{ fontWeight: 600 }}>Semi Bold (600)</Text>
<Text style={{ fontWeight: 700 }}>Bold (700)</Text>
</View>
),
parameters: {
docs: {
description: {
story: 'Text at various font weights.',
},
},
},
};
export const InlineUsage: Story = {
render: () => (
<View>
<span>
This is regular text with{' '}
<Text style={{ fontWeight: 'bold', color: '#d32f2f' }}>
highlighted
</Text>{' '}
and{' '}
<Text style={{ fontStyle: 'italic', color: '#1a73e8' }}>
emphasized
</Text>{' '}
portions.
</span>
</View>
),
parameters: {
docs: {
description: {
story:
'Text renders as a span, making it suitable for inline styling within other text.',
},
},
},
};

View File

@@ -0,0 +1,105 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { TextOneLine } from './TextOneLine';
import { View } from './View';
const meta = {
title: 'Components/TextOneLine',
component: TextOneLine,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof TextOneLine>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children:
'This is a single line of text that will be truncated with an ellipsis if it overflows its container',
style: { maxWidth: 300 },
},
parameters: {
docs: {
description: {
story:
'TextOneLine truncates overflowing text with an ellipsis, keeping content to a single line.',
},
},
},
};
export const ShortText: Story = {
args: {
children: 'Short text',
style: { maxWidth: 300 },
},
parameters: {
docs: {
description: {
story: 'When text fits within the container, no truncation occurs.',
},
},
},
};
export const NarrowContainer: Story = {
args: {
children:
'This text will be truncated because the container is very narrow',
style: { maxWidth: 120 },
},
parameters: {
docs: {
description: {
story: 'A narrow container forces earlier truncation.',
},
},
},
};
export const ComparisonWithText: Story = {
render: () => (
<View style={{ gap: 15, maxWidth: 200 }}>
<View>
<strong>TextOneLine:</strong>
<TextOneLine>
This is a long piece of text that should be truncated
</TextOneLine>
</View>
<View>
<strong>Regular span:</strong>
<span>This is a long piece of text that will wrap normally</span>
</View>
</View>
),
parameters: {
docs: {
description: {
story:
'Comparison between TextOneLine (truncated) and regular text (wrapping).',
},
},
},
};
export const WithCustomStyle: Story = {
args: {
children: 'Bold truncated text in a constrained container',
style: {
maxWidth: 200,
fontWeight: 'bold',
fontSize: 16,
},
},
parameters: {
docs: {
description: {
story: 'TextOneLine with additional custom styles applied.',
},
},
},
};

View File

@@ -0,0 +1,150 @@
import { useState } from 'react';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Text } from './Text';
import { Toggle } from './Toggle';
import { View } from './View';
const meta = {
title: 'Components/Toggle',
component: Toggle,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Toggle>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Off: Story = {
args: {
id: 'toggle-off',
isOn: false,
},
parameters: {
docs: {
description: {
story: 'Toggle in the off state.',
},
},
},
};
export const On: Story = {
args: {
id: 'toggle-on',
isOn: true,
},
parameters: {
docs: {
description: {
story: 'Toggle in the on state.',
},
},
},
};
export const Disabled: Story = {
args: {
id: 'toggle-disabled',
isOn: false,
isDisabled: true,
},
parameters: {
docs: {
description: {
story: 'A disabled toggle that cannot be interacted with.',
},
},
},
};
export const DisabledOn: Story = {
args: {
id: 'toggle-disabled-on',
isOn: true,
isDisabled: true,
},
parameters: {
docs: {
description: {
story: 'A disabled toggle in the on state.',
},
},
},
};
export const Interactive: Story = {
args: {
id: 'toggle-interactive',
isOn: false,
},
render: function Render() {
const [isOn, setIsOn] = useState(false);
return (
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Toggle id="toggle-interactive" isOn={isOn} onToggle={setIsOn} />
<Text>{isOn ? 'Enabled' : 'Disabled'}</Text>
</View>
);
},
parameters: {
docs: {
description: {
story: 'An interactive toggle with state feedback.',
},
},
},
};
export const WithLabels: Story = {
args: {
id: 'toggle-labels',
isOn: false,
},
render: function Render() {
const [notifications, setNotifications] = useState(true);
const [darkMode, setDarkMode] = useState(false);
const [autoSave, setAutoSave] = useState(true);
return (
<View style={{ gap: 12 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Toggle
id="toggle-notifications"
isOn={notifications}
onToggle={setNotifications}
/>
<Text>Notifications</Text>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Toggle
id="toggle-dark-mode"
isOn={darkMode}
onToggle={setDarkMode}
/>
<Text>Dark Mode</Text>
</View>
<View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
<Toggle
id="toggle-auto-save"
isOn={autoSave}
onToggle={setAutoSave}
/>
<Text>Auto Save</Text>
</View>
</View>
);
},
parameters: {
docs: {
description: {
story: 'Multiple toggles in a settings-style layout.',
},
},
},
};

View File

@@ -0,0 +1,135 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Button } from './Button';
import { Text } from './Text';
import { Tooltip } from './Tooltip';
import { View } from './View';
const meta = {
title: 'Components/Tooltip',
component: Tooltip,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Tooltip>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
content: 'This is a tooltip',
children: <Button>Hover me</Button>,
},
parameters: {
docs: {
description: {
story: 'A basic tooltip displayed on hover after a short delay.',
},
},
},
};
export const WithTextTrigger: Story = {
args: {
content: 'More information about this term',
children: (
<Text style={{ textDecoration: 'underline', cursor: 'help' }}>
Hover for details
</Text>
),
},
parameters: {
docs: {
description: {
story: 'A tooltip triggered by hovering over text.',
},
},
},
};
export const RichContent: Story = {
args: {
content: (
<View style={{ padding: 5, maxWidth: 200 }}>
<Text style={{ fontWeight: 'bold' }}>Tip</Text>
<Text>
You can use keyboard shortcuts to navigate faster through the
application.
</Text>
</View>
),
children: <Button>Rich Tooltip</Button>,
},
parameters: {
docs: {
description: {
story: 'Tooltip content can include rich React elements.',
},
},
},
};
export const CustomPlacement: Story = {
args: {
content: 'Tooltip',
children: <></>,
},
render: () => (
<View style={{ gap: 10, display: 'flex', flexDirection: 'row' }}>
<Tooltip content="Top placement" placement="top">
<Button>Top</Button>
</Tooltip>
<Tooltip content="Bottom placement" placement="bottom">
<Button>Bottom</Button>
</Tooltip>
<Tooltip content="Left placement" placement="left">
<Button>Left</Button>
</Tooltip>
<Tooltip content="Right placement" placement="right">
<Button>Right</Button>
</Tooltip>
</View>
),
parameters: {
docs: {
description: {
story:
'Tooltips can be placed in different positions around the trigger.',
},
},
},
};
export const DisabledTooltip: Story = {
args: {
content: 'You should not see this',
children: <Button>Hover me (disabled)</Button>,
triggerProps: { isDisabled: true },
},
parameters: {
docs: {
description: {
story:
'A tooltip can be disabled via triggerProps, preventing it from appearing.',
},
},
},
};
export const CustomDelay: Story = {
args: {
content: 'This tooltip appears after 1 second',
children: <Button>Slow Tooltip</Button>,
triggerProps: { delay: 1000 },
},
parameters: {
docs: {
description: {
story: 'The delay before the tooltip appears can be customized.',
},
},
},
};

View File

@@ -0,0 +1,215 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Text } from './Text';
import { View } from './View';
const meta = {
title: 'Components/View',
component: View,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof View>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'A basic View container',
style: { padding: 20, backgroundColor: '#f5f5f5', borderRadius: 4 },
},
parameters: {
docs: {
description: {
story:
'View is the fundamental layout building block, rendering a styled div element.',
},
},
},
};
export const FlexRow: Story = {
render: () => (
<View
style={{
display: 'flex',
flexDirection: 'row',
gap: 10,
padding: 10,
backgroundColor: '#f5f5f5',
}}
>
<View
style={{
padding: 15,
backgroundColor: '#e3f2fd',
borderRadius: 4,
}}
>
Item 1
</View>
<View
style={{
padding: 15,
backgroundColor: '#e8f5e9',
borderRadius: 4,
}}
>
Item 2
</View>
<View
style={{
padding: 15,
backgroundColor: '#fff3e0',
borderRadius: 4,
}}
>
Item 3
</View>
</View>
),
parameters: {
docs: {
description: {
story: 'Views arranged in a horizontal row using flexDirection.',
},
},
},
};
export const FlexColumn: Story = {
render: () => (
<View
style={{
display: 'flex',
flexDirection: 'column',
gap: 10,
padding: 10,
backgroundColor: '#f5f5f5',
}}
>
<View
style={{ padding: 15, backgroundColor: '#e3f2fd', borderRadius: 4 }}
>
Row 1
</View>
<View
style={{ padding: 15, backgroundColor: '#e8f5e9', borderRadius: 4 }}
>
Row 2
</View>
<View
style={{ padding: 15, backgroundColor: '#fff3e0', borderRadius: 4 }}
>
Row 3
</View>
</View>
),
parameters: {
docs: {
description: {
story: 'Views stacked vertically in a column layout.',
},
},
},
};
export const Nested: Story = {
render: () => (
<View
style={{
padding: 15,
backgroundColor: '#f5f5f5',
borderRadius: 4,
display: 'flex',
flexDirection: 'column',
width: '100%',
}}
>
<Text style={{ fontWeight: 'bold', marginBottom: 10 }}>Parent View</Text>
<View
style={{
display: 'flex',
flexDirection: 'row',
gap: 10,
}}
>
<View
style={{
flex: 1,
padding: 10,
backgroundColor: '#e3f2fd',
borderRadius: 4,
}}
>
Child 1 (flex: 1)
</View>
<View
style={{
flex: 2,
padding: 10,
backgroundColor: '#e8f5e9',
borderRadius: 4,
}}
>
Child 2 (flex: 2)
</View>
</View>
</View>
),
parameters: {
docs: {
description: {
story: 'Nested Views demonstrating flex layout composition.',
},
},
},
};
export const WithNativeStyle: Story = {
args: {
children: 'View with nativeStyle',
nativeStyle: {
padding: '20px',
border: '2px dashed #999',
borderRadius: '8px',
},
},
parameters: {
docs: {
description: {
story:
'The nativeStyle prop applies styles directly via the style attribute instead of using Emotion CSS.',
},
},
},
};
export const CenteredContent: Story = {
render: () => (
<View
style={{
width: 300,
height: 200,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
backgroundColor: '#f5f5f5',
borderRadius: 8,
border: '1px solid #ddd',
}}
>
<Text>Centered Content</Text>
</View>
),
parameters: {
docs: {
description: {
story: 'View used to center content both horizontally and vertically.',
},
},
},
};

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Add remaining component-library components to design system docs