Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/commands/component-stories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
description: Create or update Storybook stories for a CDS component.
model: claude-sonnet-4-5
disable-model-invocation: true
---

@.cursor/commands/component-stories.md
116 changes: 116 additions & 0 deletions .cursor/commands/component-stories.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# Component Stories

Create or update Storybook stories for a CDS component.

**Usage:** `/component-stories <ComponentName> [task description]`

Examples:

- `/component-stories Button`
- `/component-stories LineChart update formatting to match current setup`
- `/component-stories Avatar add dark mode examples`

## Story Locations

- **Web:** `packages/web/src/**/__stories__/[ComponentName].stories.tsx`
- **Web Visualization:** `packages/web-visualization/src/**/__stories__/[ComponentName].stories.tsx`
- **Mobile:** `packages/mobile/src/**/__stories__/[ComponentName].stories.tsx`
- **Mobile Visualization:** `packages/mobile-visualization/src/**/__stories__/[ComponentName].stories.tsx`

## Web Story Format

```tsx
import { Example, ExampleScreen } from '@coinbase/cds-web/__stories__/storybook';
// ... other imports

export default {
component: ComponentName,
title: 'Components/[Category]/[ComponentName]',
};

export const All = () => {
return (
<ExampleScreen>
<Example title="Basic">
<ComponentName />
</Example>
<Example title="With Props">
<ComponentName someProp="value" />
</Example>
</ExampleScreen>
);
};
```

### Multiple Exports (Web Only)

Use additional exports for stories that should be excluded from visual regression (e.g., random/animated data):

```tsx
// Primary story
export const All = () => {
/* ... */
};

// Excluded from Percy - add to .percy.js excludeStories
export const Transitions = () => {
/* ... */
};
```

## Mobile Story Format

```tsx
import { Example, ExampleScreen } from '../../examples';
// ... other imports

const ComponentNameScreen = () => {
return (
<ExampleScreen>
<Example title="Basic">
<ComponentName />
</Example>
</ExampleScreen>
);
};

export default ComponentNameScreen;
```

## Workflow

1. **Find component source** to understand available props
2. **Check for existing stories** - preserve all existing examples, add new ones at the bottom
3. **Check doc site examples** (`apps/docs/docs/components/**/_webExamples.mdx`) - include these in stories
4. **Create/update stories** using the format above
5. **Update webMetadata.json** (web only) with storybook link:

```json
"storybook": "https://cds-storybook.coinbase.com/?path=/story/components-[category]-[componentname]--all"
```

- All lowercase, hyphens between words in title
- Story name in URL: `--all`, `--transitions`, etc.

6. **Update .percy.js** if adding stories with random/animated data that should be excluded
7. **Run checks:**
```bash
yarn nx format:write && yarn nx run <project>:typecheck && yarn nx run <project>:lint --fix
```
Projects: `web`, `mobile`, `web-visualization`, `mobile-visualization`

## Storybook Link Format

The link is derived from the `title` and export name:

- `title: 'Components/Chart/LineChart'` + `export const All` → `components-chart-linechart--all`
- `title: 'Components/Buttons/Button'` + `export const All` → `components-buttons-button--all`

## Notes

- Always use `<ExampleScreen>` and `<Example title="...">` wrappers
- Extract complex examples into named functions above the story
- Use `memo` for components defined inside the story file
- Use `useCallback`/`useMemo` for handlers and computed values
- Keep examples focused on visual states for regression testing
- New examples go at the bottom to avoid breaking existing Percy baselines
2 changes: 1 addition & 1 deletion apps/docs/docs/components/inputs/Combobox/webMetadata.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"import": "import { Combobox } from '@coinbase/cds-web/alpha/combobox'",
"source": "https://github.com/coinbase/cds/blob/master/packages/web/src/alpha/combobox/Combobox.tsx",
"storybook": "https://cds-storybook.coinbase.com/?path=/story/components-alpha-combobox--basic-usage",
"storybook": "https://cds-storybook.coinbase.com/?path=/story/components-alpha-combobox--all-basics",
"description": "A flexible combobox component for both single and multi-selection, built for web applications with comprehensive accessibility support.",
"alpha": true,
"relatedComponents": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { memo, useCallback, useId, useMemo, useState } from 'react';
import { assets } from '@coinbase/cds-common/internal/data/assets';
import { candles as btcCandles } from '@coinbase/cds-common/internal/data/candles';
import type { TabValue } from '@coinbase/cds-common/tabs/useTabs';
import { Example, ExampleScreen } from '@coinbase/cds-web/__stories__/storybook';
import { Radio } from '@coinbase/cds-web/controls/Radio';
import { Box, type BoxBaseProps, Divider, HStack, VStack } from '@coinbase/cds-web/layout';
import { RemoteImage } from '@coinbase/cds-web/media';
Expand Down Expand Up @@ -552,21 +553,9 @@ function TradingTrends() {
);
}

const Example: React.FC<
React.PropsWithChildren<{ title: string; description?: string | React.ReactNode }>
> = ({ children, title, description }) => {
return (
<VStack gap={2}>
<Text font="headline">{title}</Text>
{description}
{children}
</VStack>
);
};

export const Miscellaneous = () => {
return (
<VStack gap={2}>
<ExampleScreen>
<Example title="Multiple Types">
<MultipleChart />
</Example>
Expand All @@ -582,6 +571,6 @@ export const Miscellaneous = () => {
<Example title="Trading Trends">
<TradingTrends />
</Example>
</VStack>
</ExampleScreen>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { forwardRef, memo, useMemo, useState } from 'react';
import { assets } from '@coinbase/cds-common/internal/data/assets';
import { useTabsContext } from '@coinbase/cds-common/tabs/TabsContext';
import type { TabValue } from '@coinbase/cds-common/tabs/useTabs';
import { Example, ExampleScreen } from '@coinbase/cds-web/__stories__/storybook';
import { IconButton } from '@coinbase/cds-web/buttons';
import { useTheme } from '@coinbase/cds-web/hooks/useTheme';
import { Box, HStack, VStack } from '@coinbase/cds-web/layout';
Expand All @@ -21,18 +22,6 @@ export default {
title: 'Components/Chart/PeriodSelector',
};

const Example: React.FC<
React.PropsWithChildren<{ title: string; description?: string | React.ReactNode }>
> = ({ children, title, description }) => {
return (
<VStack gap={2}>
<Text font="headline">{title}</Text>
{description}
{children}
</VStack>
);
};

const PeriodSelectorExample = () => {
const tabs = [
{ id: '1H', label: '1H' },
Expand Down Expand Up @@ -281,7 +270,7 @@ const ColoredExcludingLivePeriodSelectorExample = () => {

export const All = () => {
return (
<VStack gap={2}>
<ExampleScreen>
<Example title="Basic Example">
<PeriodSelectorExample />
</Example>
Expand All @@ -300,6 +289,6 @@ export const All = () => {
<Example title="Colored Excluding Live Period Selector">
<ColoredExcludingLivePeriodSelectorExample />
</Example>
</VStack>
</ExampleScreen>
);
};
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Example, ExampleScreen } from '@coinbase/cds-web/__stories__/storybook';
import { VStack } from '@coinbase/cds-web/layout';
import { Text } from '@coinbase/cds-web/typography';

Expand All @@ -10,23 +11,9 @@ export default {
component: AreaChart,
};

const Example: React.FC<
React.PropsWithChildren<{ title: string; description?: string | React.ReactNode }>
> = ({ children, title, description }) => {
return (
<VStack gap={2}>
<Text as="h2" display="block" font="title3">
{title}
</Text>
{description}
{children}
</VStack>
);
};

export const All = () => {
return (
<VStack gap={2}>
<ExampleScreen>
<Example title="Basic">
<AreaChart
enableScrubbing
Expand Down Expand Up @@ -126,6 +113,6 @@ export const All = () => {
]}
/>
</Example>
</VStack>
</ExampleScreen>
);
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { memo, useCallback, useMemo } from 'react';
import { Example, ExampleScreen } from '@coinbase/cds-web/__stories__/storybook';
import { Examples } from '@coinbase/cds-web/dates/__stories__/Calendar.stories';
import { HStack, VStack } from '@coinbase/cds-web/layout';
import { Text } from '@coinbase/cds-web/typography';
Expand All @@ -15,20 +16,6 @@ export default {
title: 'Components/Chart/Axis',
};

const Example: React.FC<
React.PropsWithChildren<{ title: string; description?: string | React.ReactNode }>
> = ({ children, title, description }) => {
return (
<VStack gap={2}>
<Text as="h3" display="block" font="title3">
{title}
</Text>
{description}
{children}
</VStack>
);
};

const ThinSolidLine = memo((props: SolidLineProps) => <SolidLine {...props} strokeWidth={1} />);

const Simple = () => {
Expand Down Expand Up @@ -501,7 +488,7 @@ const DomainLimitType = ({ limit }: { limit: 'nice' | 'strict' }) => {

export const All = () => {
return (
<VStack gap={3}>
<ExampleScreen>
<Example title="Basic">
<Simple />
</Example>
Expand All @@ -520,14 +507,7 @@ export const All = () => {
<Example title="Band Axis Grid Alignment">
<BandAxisGridAlignment />
</Example>
<Example
description={
<Text color="fgMuted" font="body">
Using a function to filter which ticks are shown on a band scale.
</Text>
}
title="Band Scale - Tick Filtering"
>
<Example title="Band Scale - Tick Filtering">
<BandScaleTickFiltering />
</Example>
<Example title="Band Scale - Explicit Ticks">
Expand All @@ -547,6 +527,6 @@ export const All = () => {
<Example title="Custom Tick Mark Sizes">
<CustomTickMarkSizes />
</Example>
</VStack>
</ExampleScreen>
);
};
Loading
Loading