🎨 Storybook docs for block, card, colorpicker, formerror (#6874)

* storybook docs for block, card, colorpicker, formerror

* release notes
This commit is contained in:
Michael Clark
2026-02-07 09:40:30 +00:00
committed by GitHub
parent 1a26253457
commit e951e21fe1
9 changed files with 546 additions and 1 deletions

View File

@@ -26,6 +26,7 @@ const config: StorybookConfig = {
core: {
disableTelemetry: true,
},
staticDirs: ['./public'],
async viteFinal(config) {
const { mergeConfig } = await import('vite');

View File

@@ -0,0 +1,9 @@
# /assets folder contain processed assets with a file hash
# They are safe for immutable caching, as filename change when content change
/assets/*
Cache-Control: public
Cache-Control: max-age=365000000
Cache-Control: immutable

View File

@@ -0,0 +1,139 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { AlignedText } from './AlignedText';
const meta = {
title: 'AlignedText',
component: AlignedText,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof AlignedText>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
left: 'Label',
right: 'Value',
style: { width: 300, display: 'flex' },
},
parameters: {
docs: {
description: {
story:
'AlignedText displays two pieces of content aligned on opposite sides.',
},
},
},
};
export const TruncateLeft: Story = {
args: {
left: 'This is a very long label that should be truncated on the left side',
right: '$100.00',
truncate: 'left',
style: { width: 250, display: 'flex' },
},
parameters: {
docs: {
description: {
story:
'When `truncate="left"`, the left content is truncated with ellipsis.',
},
},
},
};
export const TruncateRight: Story = {
args: {
left: 'Short Label',
right:
'This is a very long value that should be truncated on the right side',
truncate: 'right',
style: { width: 250, display: 'flex' },
},
parameters: {
docs: {
description: {
story:
'When `truncate="right"`, the right content is truncated with ellipsis.',
},
},
},
};
export const FinancialAmount: Story = {
args: {
left: 'Groceries',
right: '$1,234.56',
style: { width: 300, display: 'flex' },
rightStyle: { fontWeight: 'bold' },
},
parameters: {
docs: {
description: {
story:
'Example showing AlignedText used for displaying financial data.',
},
},
},
};
export const WithCustomStyles: Story = {
args: {
left: 'Category',
right: 'Amount',
style: {
width: 300,
padding: 10,
backgroundColor: '#f5f5f5',
borderRadius: 4,
display: 'flex',
},
leftStyle: { color: '#666', fontStyle: 'italic' },
rightStyle: { color: '#333', fontWeight: 'bold' },
},
};
export const MultipleRows: Story = {
args: {
left: 'Income',
right: '$5,000.00',
},
render: () => (
<div
style={{ width: 300, display: 'flex', flexDirection: 'column', gap: 8 }}
>
<AlignedText
left="Income"
right="$5,000.00"
rightStyle={{ color: 'green' }}
style={{ display: 'flex' }}
/>
<AlignedText
left="Expenses"
right="-$3,200.00"
rightStyle={{ color: 'red' }}
style={{ display: 'flex' }}
/>
<AlignedText
left="Balance"
right="$1,800.00"
style={{ borderTop: '1px solid #ccc', paddingTop: 8, display: 'flex' }}
rightStyle={{ fontWeight: 'bold' }}
/>
</div>
),
parameters: {
docs: {
description: {
story:
'Multiple AlignedText components stacked to create a summary view.',
},
},
},
};

View File

@@ -0,0 +1,111 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Block } from './Block';
import { theme } from './theme';
const meta = {
title: 'Block',
component: Block,
parameters: {
layout: 'centered',
},
} satisfies Meta<typeof Block>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'This is a Block component',
},
parameters: {
docs: {
description: {
story:
'Block is a basic div wrapper that accepts Emotion CSS styles via the `style` prop.',
},
},
},
tags: ['autodocs'],
};
export const WithStyles: Story = {
args: {
children: 'Styled Block',
style: {
padding: 20,
backgroundColor: theme.cardBackground,
borderRadius: 8,
border: `1px solid ${theme.cardBorder}`,
color: theme.pageText,
},
},
};
export const WithFlexLayout: Story = {
render: () => (
<Block
style={{
display: 'flex',
gap: 10,
padding: 15,
borderRadius: 4,
color: theme.pageText,
}}
>
<Block
style={{
padding: 10,
backgroundColor: theme.cardBackground,
borderRadius: 4,
border: `1px solid ${theme.cardBorder}`,
}}
>
Item 1
</Block>
<Block
style={{
padding: 10,
backgroundColor: theme.cardBackground,
borderRadius: 4,
border: `1px solid ${theme.cardBorder}`,
}}
>
Item 2
</Block>
<Block
style={{
padding: 10,
backgroundColor: theme.cardBackground,
borderRadius: 4,
border: `1px solid ${theme.cardBorder}`,
}}
>
Item 3
</Block>
</Block>
),
parameters: {
docs: {
description: {
story: 'Block components can be nested and styled with flexbox.',
},
},
},
};
export const AsContainer: Story = {
args: {
children: 'Container Block',
style: {
width: 300,
padding: 25,
textAlign: 'center',
backgroundColor: theme.cardBackground,
border: `2px dashed ${theme.cardBorder}`,
borderRadius: 8,
color: theme.pageText,
},
},
};

View File

@@ -20,7 +20,6 @@ export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const Primary: Story = {
args: {
variant: 'primary',

View File

@@ -0,0 +1,82 @@
import { styles } from '@actual-app/components/styles';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { Card } from './Card';
import { Paragraph } from './Paragraph';
import { theme } from './theme';
const meta = {
title: 'Card',
component: Card,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof Card>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'Card content goes here',
style: {
padding: 20,
width: 300,
color: theme.pageText,
},
},
parameters: {
docs: {
description: {
story: `
Default Card component uses the following theme CSS variables:
- \`--color-cardBackground\`
- \`--color-cardBorder\`
`,
},
},
},
};
export const WithCustomContent: Story = {
args: {
style: {
padding: 20,
width: 300,
color: theme.pageText,
},
},
render: args => (
<Card {...args}>
<h3 style={{ ...styles.largeText }}>Card Title</h3>
<Paragraph style={{ margin: 0 }}>
This is a card with more complex content including a title and
paragraph.
</Paragraph>
</Card>
),
};
export const Narrow: Story = {
args: {
children: 'Narrow card',
style: {
padding: 15,
width: 150,
color: theme.pageText,
},
},
};
export const Wide: Story = {
args: {
children: 'Wide card with more content space',
style: {
padding: 25,
width: 500,
color: theme.pageText,
},
},
};

View File

@@ -0,0 +1,108 @@
import { useState } from 'react';
import { ColorSwatch } from 'react-aria-components';
import type { Meta, StoryObj } from '@storybook/react-vite';
import { fn } from 'storybook/test';
import { Button } from './Button';
import { ColorPicker } from './ColorPicker';
const meta = {
title: 'ColorPicker',
component: ColorPicker,
parameters: {
layout: 'centered',
},
args: {
onChange: fn(),
children: <Button>Pick a color</Button>,
},
tags: ['autodocs'],
} satisfies Meta<typeof ColorPicker>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
defaultValue: '#690CB0',
children: <Button>Pick a color</Button>,
},
};
export const WithColorSwatch: Story = {
args: {
defaultValue: '#1976D2',
children: (
<Button style={{ padding: 4 }}>
<ColorSwatch
style={{
width: 24,
height: 24,
borderRadius: 4,
boxShadow: 'inset 0 0 0 1px rgba(0, 0, 0, 0.1)',
}}
/>
</Button>
),
},
};
export const CustomColorSet: Story = {
args: {
defaultValue: '#FF0000',
columns: 4,
colorset: [
'#FF0000',
'#00FF00',
'#0000FF',
'#FFFF00',
'#FF00FF',
'#00FFFF',
'#FFA500',
'#800080',
],
children: <Button>Custom Colors</Button>,
},
parameters: {
docs: {
description: {
story:
'ColorPicker with a custom color set and different column layout.',
},
},
},
};
export const Controlled: Story = {
args: {
children: <Button>Pick a color</Button>,
},
render: () => {
const [color, setColor] = useState('#388E3C');
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
<ColorPicker value={color} onChange={c => setColor(c.toString('hex'))}>
<Button style={{ padding: 4 }}>
<ColorSwatch
style={{
width: 24,
height: 24,
borderRadius: 4,
}}
/>
</Button>
</ColorPicker>
<span>Selected: {color}</span>
</div>
);
},
parameters: {
docs: {
description: {
story: 'Controlled ColorPicker with external state management.',
},
},
},
};

View File

@@ -0,0 +1,90 @@
import type { Meta, StoryObj } from '@storybook/react-vite';
import { FormError } from './FormError';
import { Input } from './Input';
import { View } from './View';
const meta = {
title: 'FormError',
component: FormError,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
} satisfies Meta<typeof FormError>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Default: Story = {
args: {
children: 'This field is required',
},
parameters: {
docs: {
description: {
story: 'FormError displays validation error messages in red text.',
},
},
},
};
export const InFormContext: Story = {
render: () => (
<View
style={{ display: 'flex', flexDirection: 'column', gap: 5, width: 250 }}
>
<Input placeholder="Email address" style={{ borderColor: 'red' }} />
<FormError>Please enter a valid email address</FormError>
</View>
),
parameters: {
docs: {
description: {
story:
'FormError displayed below an input field with validation error.',
},
},
},
};
export const MultipleErrors: Story = {
render: () => (
<View style={{ display: 'flex', flexDirection: 'column', gap: 3 }}>
<FormError>Password must be at least 8 characters</FormError>
<FormError>Password must contain a number</FormError>
<FormError>Password must contain a special character</FormError>
</View>
),
parameters: {
docs: {
description: {
story:
'Multiple FormError components for displaying several validation errors.',
},
},
},
};
export const CustomStyle: Story = {
args: {
children: 'Custom styled error message',
style: {
fontSize: 14,
fontWeight: 'bold',
padding: 10,
backgroundColor: '#ffebee',
borderRadius: 4,
border: '1px solid red',
},
},
};
export const LongErrorMessage: Story = {
args: {
children:
'This is a longer error message that explains the validation issue in more detail. Please correct the input and try again.',
style: { maxWidth: 300 },
},
};

View File

@@ -0,0 +1,6 @@
---
category: Maintenance
authors: [MikesGlitch]
---
Adding more components to the Storybook docs & improving storybook caching