mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-09 06:03:01 -05:00
🎨 Add remaining component stories to storybook (#6940)
* final storybook stories * release notes * spelling mistake
This commit is contained in:
153
packages/component-library/src/Popover.stories.tsx
Normal file
153
packages/component-library/src/Popover.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
178
packages/component-library/src/Select.stories.tsx
Normal file
178
packages/component-library/src/Select.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
140
packages/component-library/src/SpaceBetween.stories.tsx
Normal file
140
packages/component-library/src/SpaceBetween.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
112
packages/component-library/src/Text.stories.tsx
Normal file
112
packages/component-library/src/Text.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
105
packages/component-library/src/TextOneLine.stories.tsx
Normal file
105
packages/component-library/src/TextOneLine.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
150
packages/component-library/src/Toggle.stories.tsx
Normal file
150
packages/component-library/src/Toggle.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
135
packages/component-library/src/Tooltip.stories.tsx
Normal file
135
packages/component-library/src/Tooltip.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
215
packages/component-library/src/View.stories.tsx
Normal file
215
packages/component-library/src/View.stories.tsx
Normal 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.',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
6
upcoming-release-notes/6940.md
Normal file
6
upcoming-release-notes/6940.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [MikesGlitch]
|
||||
---
|
||||
|
||||
Add remaining component-library components to design system docs
|
||||
Reference in New Issue
Block a user