diff --git a/.cursor/rules/stories-documentation.mdc b/.cursor/rules/stories-documentation.mdc new file mode 100644 index 00000000000..69041d2bd5a --- /dev/null +++ b/.cursor/rules/stories-documentation.mdc @@ -0,0 +1,875 @@ +--- +globs: 2nd-gen/packages/swc/components/*/stories/** +alwaysApply: true +--- +# Component stories documentation standards + +Comprehensive guide for creating consistent, accurate, and accessible component documentation in Storybook stories. + +This guide integrates Shoelace's documentation patterns with Spectrum Web Components' structure, providing both visual and technical breakdowns while using TOC anchor linking. + +## Scope + +Apply to all component stories in: + +- `2nd-gen/packages/swc/components/*/stories/*.stories.ts` +- `2nd-gen/packages/swc/components/*/stories/*.usage.mdx` + +## Documentation structure + +Organize documentation into these sections (skip sections that don't apply): + +1. **Overview** - Introduction and primary use case +2. **Anatomy** - Both visual and technical structure +3. **Options** - Configuration, variants, sizes, styles +4. **States** - Different component states +5. **Behaviors** - Methods, events, automatic behaviors +6. **Accessibility** - Features and best practices + +**Note**: Installation instructions are programmatically generated by `DocumentTemplate.mdx` and should not be manually added to usage.mdx files. + +## Helpers section + +**Purpose**: Organize shared code, label mappings, and utilities used across multiple stories. + +**Location**: Between `export default meta` and first story (Playground/Autodocs). + +**When to use:** + +- Component has multiple variants, sizes, or options that need consistent labels +- Stories require reusable label mappings or data structures +- Complex rendering logic needs to be shared across stories + +**Pattern**: + +```typescript +// ──────────────────── +// HELPERS +// ──────────────────── + +// Label mappings for sizes +const sizeLabels = { + s: 'Small', + m: 'Medium', + l: 'Large', + xl: 'Extra-large', +} as const satisfies Record; + +// Label mappings for semantic variants +const semanticLabels = { + positive: 'Approved', + negative: 'Rejected', + informative: 'Active', + neutral: 'Archived', + notice: 'Pending', +} as const satisfies Record; + +// Combined label objects for convenience +const allLabels = { ...semanticLabels, ...colorLabels }; +``` + +**Key principles:** + +- Use TypeScript `as const satisfies` for type-safe label mappings +- Group related helpers together (size labels, variant labels, etc.) +- Use clear, descriptive constant names (`sizeLabels`, not `labels`) +- Provide realistic, meaningful labels (not "Label 1", "Label 2") + +**When NOT to use:** + +- Component has only 1-2 options (no need for label mappings) +- Labels are self-explanatory (e.g., true/false) +- Stories don't share any common code + +## Section patterns + +### Overview + +**Purpose**: Quick introduction showing the component in its most common use case. + +**Documentation**: The Overview story content comes from two sources: + +1. **JSDoc description above meta**: Provides the main description of what the component does and when to use it. This description: + - Is displayed as the primary documentation for the Overview story + - Can include markdown formatting and links to other components + - Should reference other components using Storybook paths: `[Badge](../?path=/docs/badge--readme)` + - Should provide fuller context than the subtitle + +2. **`parameters.docs.subtitle`**: Provides a brief summary displayed as the subtitle. This subtitle: + - Cannot include links (plain text only) + - Should be concise and to-the-point + - Should not repeat the JSDoc description verbatim + - Complements the JSDoc description + +**Pattern**: + +```typescript +/** + * A badge is a non-interactive visual label that displays a status, category, or attribute. + * Badges can be used to highlight important information or to categorize items. For interactive + * labels, see [Button](../?path=/docs/button--readme). + */ +const meta: Meta = { + title: 'Badge', + component: 'swc-badge', + args, + argTypes, + render: (args) => template(args), + parameters: { + docs: { subtitle: `Visual label for status, category, or attribute` }, + // ... other parameters + }, + tags: ['migrated'], +}; + +export default meta; + +// ... HELPERS section if needed ... + +export const Overview: Story = { + tags: ['overview'], + args: { + // Most common/representative configuration + }, +}; +``` + +**Note**: No JSDoc comment needed on the Overview story itself - the meta JSDoc and subtitle provide the documentation. + +### Anatomy + +**Purpose**: Document both visual structure (what users see) and technical structure (slots, parts, properties). + +**Required content:** + +- **All slots** with descriptions +- **Content-rendering properties** (label, icon, src, value, etc.) +- **CSS custom properties** (key design tokens) +- Visual examples showing structure + +**Consolidation rule**: Combine all slotted content combinations into a **single Anatomy story**. + +**Pattern**: + +```typescript +/** + * ### Visual structure + * + * A component-name consists of: + * + * 1. **Primary element** - Main visual component + * 2. **Secondary element** - Additional visual content + * 3. **Optional indicator** - Shown conditionally + * + * ### Technical structure + * + * #### Slots + * + * - **Default slot**: Primary content (text or HTML) + * - **icon slot**: Optional icon element + * - **description slot**: Additional descriptive content + * + * #### Properties + * + * Properties that render visual content: + * + * - **label**: Text label displayed by the component + * - **icon**: Icon identifier to display + * - **src**: Image source URL + * - **value**: Displayed value content + * + * #### CSS parts + * + * Exposed shadow DOM parts for styling: + * + * - **base**: The component's base wrapper + * - **track**: Progress track element + * - **fill**: Progress fill element + * + * #### CSS custom properties + * + * Key design tokens that control appearance: + * + * - **--spectrum-component-size**: Overall size of the component + * - **--spectrum-component-background-color**: Background color + * - **--spectrum-component-border-radius**: Corner rounding + * + * All variations shown below for comparison. + */ +export const Anatomy: Story = { + render: (args) => html` + ${template({ ...args, /* minimal */ })} + ${template({ ...args, /* with icon */ })} + ${template({ ...args, /* with additional content */ })} + `, + tags: ['anatomy'], + parameters: { + flexLayout: true, + }, +}; +``` + +**Key principles:** + +- Start with visual structure (designer-focused) +- Follow with technical structure (developer-focused) +- Document all slots with clear descriptions +- List content-rendering properties (label, icon, src, value, etc.) +- Include CSS parts if exposed for styling customization +- Document key CSS custom properties (design tokens) +- Show all meaningful combinations in one story + +### Options + +**Purpose**: Document every attribute or property not covered in Anatomy, States, or Behaviors. + +**Order**: Sizes → Semantic variants → Non-semantic variants → Quiet/Subtle/Emphasized → Outline → Static color → Positioning → Other options + +**Consolidation rules**: + +- All sizes → single `Sizes` story +- All semantic variants → single `SemanticVariants` story +- All non-semantic variants → single `NonSemanticVariants` story + +**Layout requirement**: Use `flexLayout: true` for stories displaying multiple variations. + +**Pattern for sizes**: + +```typescript +/** + * Component-names come in [X] sizes to fit various contexts: + * + * - **Small (s)**: Used for inline indicators or space-constrained areas + * - **Medium (m)**: Default size, used for typical use cases + * - **Large (l)**: Used for prominent displays or primary content areas + * - **Extra-large (xl)**: Maximum visibility (if applicable) + * + * All sizes shown below for comparison. + */ +export const Sizes: Story = { + render: (args) => html` + ${template({ ...args, size: 's', label: 'Small' })} + ${template({ ...args, size: 'm', label: 'Medium' })} + ${template({ ...args, size: 'l', label: 'Large' })} + `, + tags: ['options'], + parameters: { + flexLayout: true, + 'section-order': 1, + }, +}; +``` + +**Pattern for semantic variants**: + +```typescript +/** + * Semantic variants provide meaning through color: + * + * - **accent**: New, beta, prototype, draft + * - **informative**: Active, in use, live, published + * - **neutral**: Archived, deleted, paused, draft, not started, ended + * - **positive**: Approved, complete, success, new, purchased, licensed + * - **notice**: Needs approval, pending, scheduled + * - **negative**: Error, alert, rejected, failed + * + * All semantic variants shown below for comparison. + */ +export const SemanticVariants: Story = { + render: (args) => html` + ${template({ ...args, variant: 'positive', label: 'Positive' })} + ${template({ ...args, variant: 'informative', label: 'Informative' })} + ${template({ ...args, variant: 'negative', label: 'Negative' })} + ${template({ ...args, variant: 'neutral', label: 'Neutral' })} + ${template({ ...args, variant: 'notice', label: 'Notice' })} + `, + tags: ['options'], + parameters: { + flexLayout: true, + 'section-order': 2, + }, +}; +``` + +**Pattern for static color**: + +```typescript +/** + * Use the `static-color` attribute when displaying over images or colored backgrounds: + * + * - **white**: Use on dark or colored backgrounds for better contrast + * - **black**: Use on light backgrounds for better contrast + * + * Both variants shown below with appropriate backgrounds. + */ +export const StaticColors: Story = { + render: (args) => html` + ${['white', 'black'].map((color) => html`${template({ ...args, 'static-color': color })}`)} + `, + args: { + label: 'Loading', + }, + tags: ['options', '!test'], + parameters: { + flexLayout: false, + staticColorsDemo: true, + 'section-order': 5, + }, +}; +``` + +### States + +**Purpose**: Document all possible states the component can be in. + +**Order**: Default → Selected → Active → Disabled → Readonly → Error → Loading/Pending/Indeterminate → Other states + +**Consolidation rule**: Combine all states into a **single States story** when possible (or minimal stories when states are complex). + +**Pattern**: + +```typescript +/** + * Components can exist in various states: + * + * - **Default**: Normal, interactive state + * - **Selected**: Item has been chosen or activated + * - **Disabled**: Functionality exists but is not available + * - **Error**: Validation failure or error condition + * + * All states shown below for comparison. + */ +export const States: Story = { + render: (args) => html` + ${template({ ...args, label: 'Default' })} + ${template({ ...args, selected: true, label: 'Selected' })} + ${template({ ...args, disabled: true, label: 'Disabled' })} + ${template({ ...args, invalid: true, label: 'Error' })} + `, + tags: ['states'], + parameters: { + flexLayout: true, + }, +}; +``` + +**Pattern for complex states** (when animation or interaction is critical): + +```typescript +/** + * The indeterminate state shows an animated loading indicator when progress is unknown or cannot be determined. + * The animation automatically loops until the state changes. + */ +export const Indeterminate: Story = { + tags: ['states'], + args: { + indeterminate: true, + label: 'Loading...', + }, +}; +``` + +**Disabled state template**: + +```typescript +/** + * A component in a disabled state shows that [functionality] exists, but is not available in that circumstance. + * This can be used to maintain layout continuity and communicate that [functionality] may become available later. + * + * **ARIA support**: When disabled, the component automatically sets `aria-disabled="true"`. + */ +``` + +### Behaviors + +**Purpose**: Document methods, events, and automatic behaviors. + +**Pattern for automatic behaviors**: + +```typescript +/** + * ### Text handling + * + * Long text content automatically wraps to multiple lines to fit the available space. + * When space is constrained, text truncates with an ellipsis (...). + * + * ### Focus management + * + * When opened, focus is automatically trapped within the component. + * When closed, focus returns to the triggering element. + */ +export const TextWrapping: Story = { + render: (args) => html` + ${template({ 'default-slot': 'Short text' })} + ${template({ 'default-slot': 'This is a much longer text that will wrap to multiple lines when the container becomes too narrow to fit it all on one line' })} + `, + tags: ['behaviors'], + parameters: { + flexLayout: true, + }, +}; +``` + +**Pattern for methods**: + +```typescript +/** + * ### Methods + * + * The component exposes the following public methods: + * + * - **open()**: Opens the component programmatically + * - **close()**: Closes the component programmatically + * - **toggle()**: Toggles between open and closed states + * - **reset()**: Resets the component to its initial state + * + * Example usage: + * + * ```javascript + * const component = document.querySelector('swc-component-name'); + * component.open(); + * ``` + */ +export const Methods: Story = { + tags: ['behaviors'], + // ... implementation +}; +``` + +**Pattern for events**: + +```typescript +/** + * ### Events + * + * The component dispatches the following custom events: + * + * - **change**: Fired when the value changes (bubbles: true, composed: true) + * - **input**: Fired during user input (bubbles: true, composed: true) + * - **swc-opened**: Fired when the component opens (bubbles: true, composed: true) + * - **swc-closed**: Fired when the component closes (bubbles: true, composed: true) + * + * Example event listener: + * + * ```javascript + * component.addEventListener('change', (event) => { + * console.log('Value changed:', event.target.value); + * }); + * ``` + */ +export const Events: Story = { + tags: ['behaviors'], + // ... implementation +}; +``` + +### Accessibility + +**Purpose**: Document built-in accessibility features and provide best practices guidance. + +**Required structure**: Two subsections - Features and Best practices. + +**Pattern**: + +```typescript +/** + * ### Features + * + * The `` element implements several accessibility features: + * + * #### Keyboard navigation + * + * - Tab: Moves focus to/from the component + * - Space or Enter: Activates the component + * - Arrow keys: Navigate between items + * - Escape: Closes the component (if applicable) + * + * #### ARIA implementation + * + * 1. **ARIA role**: Automatically sets `role="progressbar"` (or appropriate role) + * 2. **Labeling**: Uses the `label` attribute as `aria-label` + * 3. **States**: + * - Sets `aria-valuenow` with current progress value + * - Sets `aria-disabled="true"` when disabled + * 4. **Status communication**: Screen readers announce value changes + * + * #### Visual accessibility + * + * - Progress is shown visually through multiple cues, not relying solely on color + * - High contrast mode is supported with appropriate color overrides + * - Static color variants ensure sufficient contrast on different backgrounds + * + * ### Best practices + * + * - Always provide a descriptive `label` that explains what the component represents + * - Use meaningful, specific labels (e.g., "Uploading document" instead of "Loading") + * - Ensure sufficient color contrast between the component and its background + * - Use semantic variants when status has specific meaning + * - Test with screen readers to verify announcements are clear + */ +export const Accessibility: Story = { + tags: ['a11y'], + args: { + // ... accessible example + }, +}; +``` + +**Common accessibility features to document:** + +- **Keyboard navigation**: Document all keyboard interactions +- **ARIA role**: Document automatic role assignment +- **ARIA labels**: Document labeling mechanism (aria-label, aria-labelledby) +- **ARIA states**: Document state attributes (aria-disabled, aria-valuenow, etc.) +- **Color meaning**: For components using color to convey information +- **High contrast mode**: If supported via forced-colors media query + +**Accessibility requirements for all stories:** + +All stories must demonstrate accessible usage: + +- Include required labels (`label`, `aria-label`, or slot content) +- Use meaningful, realistic content (no placeholder text) +- Show proper ARIA usage when applicable +- Never demonstrate inaccessible patterns + +## 1st-gen to 2nd-gen comparison + +When creating 2nd-gen documentation, check 1st-gen (`1st-gen/packages/*/README.md`) for content to preserve or differences to highlight. + +### Where to document differences + +Document 1st-gen vs 2nd-gen differences directly in the relevant story's JSDoc comment where that difference is apparent. + +**Pattern for documenting differences**: + +```typescript +/** + * Component-names come in [X] sizes... + * + * **Note**: The `xl` size is new in 2nd-gen and not available in 1st-gen. + * + * All sizes shown below for comparison. + */ +export const Sizes: Story = { + // ... +}; +``` + +Or for removed features: + +```typescript +/** + * ### Methods + * + * The component exposes the following public methods... + * + * **Migration note**: The `validate()` method from 1st-gen has been removed in 2nd-gen. + * Use the `invalid` property instead for validation state. + */ +export const Methods: Story = { + // ... +}; +``` + +### Content to check from 1st-gen + +Compare 2nd-gen against 1st-gen for: + +1. **Size options**: Verify all sizes (especially `xl`) are documented +2. **Variant lists**: Ensure all variants are listed (check for `accent`, etc.) +3. **States**: Check for disabled, loading, or other states +4. **Behavioral details**: Text wrapping, truncation, tooltip integration +5. **Keyboard interactions**: If documented in 1st-gen +6. **Methods and events**: Check for API changes +7. **Advanced examples**: Tooltips, containers, complex layouts +8. **Specific guidance**: Color contrast guidance for static-color variants + +### Common differences to highlight + +**New in 2nd-gen:** + +- Additional sizes (e.g., `xl`) +- Additional variants (e.g., `accent`) +- New properties (e.g., `subtle`, `outline`) +- Improved ARIA implementation +- Better high contrast support + +**Changed in 2nd-gen:** + +- Property names (e.g., `emphasized` → `outline`) +- Slot names or structure +- Event names or payloads +- Default values + +**Removed from 2nd-gen:** + +- Deprecated properties +- Removed methods +- Unsupported variants + +### Using CEM for comparison + +Use the Custom Elements Manifest (CEM) to verify differences: + +1. **Check 1st-gen CEM**: `1st-gen/packages/component-name/custom-elements.json` +2. **Check 2nd-gen types**: `2nd-gen/packages/core/components/component-name/*.types.ts` +3. **Compare**: + - Properties and their types + - Slots and their descriptions + - Methods and their signatures + - Events and their details + +**Example verification**: + +```bash +# Check 1st-gen properties +grep -A 5 '"name": "size"' 1st-gen/packages/badge/custom-elements.json + +# Check 2nd-gen types +grep "VALID_SIZES" 2nd-gen/packages/core/components/badge/Badge.types.ts +``` + +### Common gaps to check + +- [ ] Missing installation instructions +- [ ] Missing size options (xl is common) +- [ ] Missing semantic variants (accent is common) +- [ ] Undocumented disabled state +- [ ] Missing readonly state documentation +- [ ] Missing ARIA disabled documentation +- [ ] Lack of tooltip integration examples +- [ ] Missing icon-only accessibility guidance +- [ ] No keyboard navigation documentation +- [ ] Missing static-color contrast guidance +- [ ] No text truncation/wrapping behavior explanation +- [ ] Missing methods documentation +- [ ] Missing events documentation + +## Verification and accuracy + +**Critical**: Always verify documentation against the actual component implementation to prevent hallucinations and inaccuracies. + +### Verification process + +After creating or updating documentation: + +1. **Read the component source files:** + - Base class implementation (`2nd-gen/packages/core/components/*/Component.base.ts`) + - Component-specific implementation (`2nd-gen/packages/swc/components/*/Component.ts`) + - TypeScript type definitions (`2nd-gen/packages/core/components/*/Component.types.ts`) + - CSS stylesheets (`2nd-gen/packages/swc/components/*/component.css`) + +2. **Verify slots:** + - Check JSDoc comments in component class (`@slot` decorators) + - Verify slot names in the render method + - Confirm which slots are required vs. optional + +3. **Verify properties:** + - Check `@property` decorators in component and base classes + - Verify property types, defaults, and reflect values + - Confirm valid values (enums, arrays defined in types file) + - Check if properties exist in both base and subclass + +4. **Verify CSS custom properties:** + - Search CSS file for custom properties (e.g., `--spectrum-component-*`) + - Confirm properties are actually used in the styles + - Check if properties vary by size/variant + +5. **Verify ARIA implementation:** + - Search for `aria-*` attributes in component code + - Check for `role` assignments + - Verify attribute behavior (when added/removed) + - **Don't claim attributes that aren't implemented** + +6. **Verify behavior:** + - Check for methods in component class (public methods only) + - Look for event dispatching (`dispatchEvent`, custom events) + - Verify automatic behaviors in lifecycle methods + - Check CSS for animations, transitions, media queries + +7. **Verify constraints:** + - Look for validation logic (e.g., outline only with semantic variants) + - Check for console warnings about invalid combinations + - Verify size/variant/option restrictions + +### Common hallucination patterns to avoid + +❌ **Claiming attributes that don't exist:** + +```typescript +// WRONG: Claiming a `label` attribute when component only has a slot +- Provide label via the `label` attribute +``` + +✅ **Verify what actually exists:** + +```typescript +// CORRECT: Check component source for actual API +- Provide label via the default slot or `aria-label` attribute +``` + +❌ **Assuming ARIA attributes are set:** + +```typescript +// WRONG: Claiming aria-valuemin/aria-valuemax without checking +- Includes `aria-valuemin="0"` and `aria-valuemax="100"` +``` + +✅ **Verify ARIA implementation in code:** + +```typescript +// CORRECT: Only document what the component actually sets +- Sets `aria-valuenow` with the current progress value +``` + +❌ **Documenting non-existent CSS properties:** + +```typescript +// WRONG: Inventing custom properties +- **--spectrum-badge-animation-duration**: Animation timing +``` + +✅ **Search CSS file for actual properties:** + +```typescript +// CORRECT: Only document properties that exist in CSS +- **--spectrum-badge-background-color-default**: Background color +``` + +❌ **Claiming methods that don't exist:** + +```typescript +// WRONG: Assuming common methods exist +- **reset()**: Resets the component to initial state +``` + +✅ **Check component class for public methods:** + +```typescript +// CORRECT: Only document methods defined in the class +// (If no public methods exist, state this clearly) +``` + +### Verification tools + +Use these commands to verify claims: + +```bash +# Search for properties +grep -n "@property" 2nd-gen/packages/swc/components/component-name/Component.ts + +# Search for slots +grep -n "@slot" 2nd-gen/packages/core/components/component-name/Component.base.ts + +# Search for ARIA attributes +grep -n "aria-" 2nd-gen/packages/swc/components/component-name/Component.ts + +# Search for CSS custom properties +grep -n "--spectrum-component-" 2nd-gen/packages/swc/components/component-name/component.css + +# Search for events +grep -n "dispatchEvent\|new CustomEvent" 2nd-gen/packages/swc/components/component-name/Component.ts + +# Compare with 1st-gen +diff <(grep "@property" 1st-gen/packages/component-name/*.ts) \ + <(grep "@property" 2nd-gen/packages/swc/components/component-name/*.ts) +``` + +### Documentation after verification + +After verifying accuracy: + +1. Document what was checked in commit message or PR +2. Note any inaccuracies found and fixed +3. Include code references for major claims (in JSDoc comments if helpful) +4. Consider creating a verification checklist for complex components + +## General guidelines + +### Documentation style + +- **Use sentence case** for all headings and descriptions +- **Be specific** - Instead of "Loading", use "Uploading document" or "Processing request" +- **Show, don't just tell** - Include visual examples for every concept +- **Consolidate variations** - Combine related options into single stories for easier comparison +- **Think multi-audience** - Balance designer needs (visual) with developer needs (technical) + +### Component linking + +When referencing other components in the JSDoc description above meta: + +- **Use Storybook paths**: Link to the component's overview story using relative paths +- **Format**: `[ComponentName](../?path=/docs/component-name--readme)` +- **Component name format**: Use kebab-case in the path (e.g., `action-button`, `progress-circle`) +- **Always link to readme**: Use `--readme` as the story anchor + +**Examples**: + +```typescript +/** + * A `` is a non-interactive visual label. For interactive labels, + * see [Action Button](../?path=/docs/action-button--readme). + */ + +/** + * Progress circles are commonly used with [Button](../?path=/docs/button--readme) + * and [Card](../?path=/docs/card--readme) components to show loading states. + */ +``` + +### Code examples + +- **Use realistic content** - Avoid placeholder text, use meaningful labels +- **Always be accessible** - Include required labels, ARIA attributes +- **Show complete patterns** - Don't abbreviate important details +- **Use consistent formatting** - Follow project style guidelines + +### Story organization + +- **Use flexLayout** for multi-item comparisons +- **Set section-order** to control display order within sections +- **Tag appropriately** - Use correct tags for each section +- **Add JSDoc comments** - Explain what each story demonstrates (except Playground and Overview) + +### JSDoc heading levels + +All headings in JSDoc comments must start at level 3 (`###`) or deeper: + +- **Level 3 (`###`)**: Top-level sections within a story (e.g., "Visual structure", "Features", "Methods") +- **Level 4 (`####`)**: Sub-sections within those sections (e.g., "Slots", "ARIA implementation", "Keyboard navigation") + +This ensures proper hierarchy since JSDoc content is rendered within the story context, subordinate to the main section headings in the usage.mdx file (which use `##`). + +## Checklist + +When creating or updating documentation: + +- [ ] Overview story with common use case +- [ ] JSDoc description above meta (explains component purpose, links to related components) +- [ ] `parameters.docs.subtitle` is concise and non-repetitive (plain text, no links) +- [ ] Helpers section for shared label mappings and utilities (if applicable) +- [ ] Anatomy with both visual and technical structure +- [ ] All slots documented with descriptions +- [ ] All content-rendering properties listed +- [ ] CSS parts documented (if exposed) +- [ ] CSS custom properties (design tokens) documented +- [ ] All configuration options documented +- [ ] All states documented +- [ ] Methods documented (if applicable) +- [ ] Events documented (if applicable) +- [ ] Automatic behaviors explained +- [ ] Comprehensive accessibility section with features and best practices +- [ ] All examples use accessible, meaningful content +- [ ] Consistent flexLayout usage for comparisons +- [ ] Proper story tags for all sections +- [ ] JSDoc comments for all stories (except Playground and Overview) +- [ ] JSDoc headings start at level 3 (`###`) or deeper +- [ ] **Checked 1st-gen README.md for missing content or differences** +- [ ] **Documented 1st-gen differences where apparent (new/changed/removed features)** +- [ ] **Verified against component implementation** (no hallucinations) +- [ ] All slots verified in component source +- [ ] All properties verified with `@property` decorators +- [ ] All CSS custom properties verified in stylesheet +- [ ] ARIA attributes verified in component code +- [ ] Methods and events verified in implementation diff --git a/.cursor/rules/stories-format.mdc b/.cursor/rules/stories-format.mdc new file mode 100644 index 00000000000..8f708ce5ed1 --- /dev/null +++ b/.cursor/rules/stories-format.mdc @@ -0,0 +1,684 @@ +--- +globs: 2nd-gen/packages/swc/components/*/stories/** +alwaysApply: true +--- +# Storybook stories format standards + +Enforce consistent formatting and technical structure for Storybook stories files in 2nd-gen components. + +**See also**: `.cursor/rules/stories-documentation.mdc` for comprehensive guidance on WHAT to document (content, patterns, examples). + +## Scope + +Apply to all `.stories.ts` and `.usage.mdx` files in `2nd-gen/packages/swc/components/*/stories/`. + +## File structure + +### Stories file (`.stories.ts`) + +Required structure with visual separators between sections: + +1. **Copyright header** (lines 1-11) +2. **Imports** +3. **METADATA** - Meta object with component configuration +4. **HELPERS** - Shared label mappings and utilities (if needed) +5. **AUTODOCS STORY** - Playground story +6. **ANATOMY STORIES** - Component structure (if applicable) +7. **OPTIONS STORIES** - Variants, sizes, styles +8. **STATES STORIES** - Component states (if applicable) +9. **BEHAVIORS STORIES** - Built-in functionality (if applicable) +10. **ACCESSIBILITY STORIES** - A11y demonstration + +#### Visual separators + +```typescript +// ──────────────── +// METADATA +// ──────────────── + +// ──────────────────── +// HELPERS +// ──────────────────── + +// ──────────────────── +// AUTODOCS STORY +// ──────────────────── + +// ────────────────────────── +// ANATOMY STORIES +// ────────────────────────── + +// ────────────────────────── +// OPTIONS STORIES +// ────────────────────────── + +// ────────────────────────── +// STATES STORIES +// ────────────────────────── + +// ────────────────────────────── +// BEHAVIORS STORIES +// ────────────────────────────── + +// ──────────────────────────────── +// ACCESSIBILITY STORIES +// ──────────────────────────────── +``` + +## Key Principles + +### Always Pass Args Through + +When using custom `render` functions, **always** spread `...args` into template calls: + +```typescript +// ✅ Good - args are passed through +export const MyStory: Story = { + render: (args) => html` + ${template({ ...args, size: 's' })} + `, +}; + +// ❌ Bad - args are lost +export const MyStory: Story = { + render: (args) => html` + ${template({ size: 's' })} + `, +}; +``` + +This ensures: + +- Storybook controls work correctly +- Args from the Playground/global level are respected +- Component defaults can be overridden + +### When to Use render vs args + +- **Use `args` directly**: When the default render is sufficient (single component, no wrapper) +- **Use `render: (args) =>`**: When you need multiple instances, custom HTML structure, or conditional rendering + +```typescript +// ✅ Good - simple case uses args +export const Overview: Story = { + args: { size: 'm', label: 'Example' }, + tags: ['overview'], +}; + +// ✅ Good - complex case uses render with args +export const Sizes: Story = { + render: (args) => html` + ${template({ ...args, size: 's' })} + ${template({ ...args, size: 'm' })} + ${template({ ...args, size: 'l' })} + `, + tags: ['options'], +}; +``` + +### Usage file (`.usage.mdx`) + +Keep minimal—use `` to pull in stories by tag. Only include sections with corresponding stories. + +**Note**: Installation instructions are programmatically generated by `DocumentTemplate.mdx` and should not be manually added. + +```mdx +import { SpectrumStories } from '../../../.storybook/blocks'; + +## Anatomy + + + +## Options + + + +## States + + + +## Behaviors + + + +## Accessibility + + +``` + +**Section rules**: + +- Use `hideTitle` for Anatomy and Accessibility only +- Follow this section order, skipping inapplicable sections +- Use sentence case for headings +- Do not include an Installation section (it's auto-generated) + +## Meta configuration + +### Required fields + +```typescript +/** + * Component description explaining its purpose and key features. + * + * This description is displayed in the Overview story. It should provide context about + * what the component does and when to use it. If referencing other components, link to + * their Storybook paths using relative URLs (e.g., `` becomes + * `[Badge](../?path=/docs/badge--overview)`). + */ +const meta: Meta = { + title: 'Component name', + component: 'swc-component-name', + args, + argTypes, + render: (args) => template(args), + parameters: { + actions: { handles: events }, // If events exist + docs: { + subtitle: `Component description` // Required - displayed in Overview, cannot include links + }, + design: { type: 'figma', url: 'https://www.figma.com/...'}, // Recommended + stackblitz: { url: 'https://stackblitz.com/...'}, // Recommended + }, + tags: ['migrated'], +}; +``` + +**Important notes:** + +- **JSDoc description above meta**: Displayed in the Overview story. Can include markdown links to other components. +- **`parameters.docs.subtitle`**: Displayed as the subtitle in the Overview story. Cannot include links (plain text only). +- **Avoid repetition**: The subtitle and JSDoc description should complement each other, not duplicate content. The subtitle is a brief summary; the JSDoc provides fuller context. +- **Component links**: When referencing other components in the JSDoc description, use relative Storybook paths: `[ComponentName](../?path=/docs/component-name--overview)` + +## Layout and decorators + +Use `flexLayout: true` for stories displaying multiple items (sizes, variants, states). This applies flex layout with consistent spacing. + +```typescript +export const Sizes: Story = { + render: () => html` + Small + Medium + Large + Extra-large + `, + parameters: { flexLayout: true }, + tags: ['options'], +}; +``` + +### Customizing layout + +Extend with `parameters.styles` or use styles alone for custom layouts: + +```typescript +export const Sizes: Story = { + parameters: { + flexLayout: true, + styles: { + 'flex-wrap': 'wrap', + 'max-inline-size': '80ch' + }, + }, +}; + +// Or fully custom (no flex defaults): +export const GridLayout: Story = { + parameters: { + styles: { + display: 'grid', + 'grid-template-columns': 'repeat(3, 1fr)' + }, + }, +}; +``` + +### Static color decorator + +For static color stories, use `staticColorsDemo: true` with `flexLayout: true`: + +```typescript +export const StaticColors: Story = { + render: (args) => html` + ${['white', 'black'].map((color) => template({ 'static-color': color }), + parameters: { + flexLayout: true, + staticColorsDemo: true + }, + tags: ['options', '!test'], +}; +``` + +The decorator displays two background zones—dark gradient for `static-color="white"` content, light gradient for `static-color="black"` content. + +## Story ordering + +Control display order within sections using `section-order`. Stories sort by lowest value first, then alphabetically for ties or missing values. + +```typescript +export const Sizes: Story = { + tags: ['options'], + parameters: { 'section-order': 1 }, +}; + +export const StaticColors: Story = { + tags: ['options'], + parameters: { 'section-order': 10 }, +}; +``` + +## Tags + +### Required tags + +| Tag | Usage | +|-----|-------| +| `'autodocs'`, `'dev'` | Playground story only | +| `'overview'` | Overview story | +| `'anatomy'` | Anatomy stories | +| `'options'` | Variant, size, and style stories | +| `'states'` | State demonstration stories | +| `'behaviors'` | Method, event, and automatic behavior stories | +| `'a11y'` | Accessibility story | +| `'migrated'` | On meta object | + +### Exclusion tags + +- `'!test'` - Exclude from test runs +- `'!dev'` - Exclude from dev Storybook + +## Story types + +### Playground + +First story after meta. No JSDoc comment. Set args to the most common use case—this appears as the docs page preview. + +```typescript +export const Playground: Story = { + args: { /* most common use case */ }, + tags: ['autodocs', 'dev'], +}; +``` + +**Note**: Use `args` directly (not `render`) when the default render is sufficient. Only use `render: (args) => html` when you need custom rendering. + +Include comprehensive JSDoc comment above the meta object explaining what the component does. + +Every story export must have a JSDoc comment explaining: + +- What it demonstrates +- Any important context or usage notes +- Best practices if relevant + +**Exceptions**: Do NOT add JSDoc comments above the Playground or Overview stories. + +Use markdown formatting within JSDoc: + +- `**Bold**` for emphasis +- Bullet lists for multiple points +- Code formatting with backticks + +```typescript +/** + * A `` is a UI element that displays a **status** or **message**. + */ +export const Playground: Story = { + args: { /* most common use case */ }, + tags: ['autodocs', 'dev'], +}; +``` + +### Overview + +Quick introduction showing the component in its most common use case. No JSDoc comment needed. + +```typescript +export const Overview: Story = { + args: { + // Most common/representative configuration + }, + tags: ['overview'], +}; +``` + +**Note**: Use `args` directly when possible. Only use `render: (args) =>` if you need custom HTML structure around the component. + +### Anatomy + +Document all slots and content-rendering properties (e.g., `label`, `icon`, `src`). Combine variations into one story. + +**Important**: When using `render: (args) =>`, **always** spread `...args` into template calls to ensure Storybook controls work correctly. + +```typescript +/** + * A component-name consists of: + * + * - **Default slot**: Primary content + * - **icon slot**: Optional icon element + * - **label**: Text label rendered by the component + */ +export const Anatomy: Story = { + render: (args) => html` + ${template({ ...args, /* text only */ })} + ${template({ ...args, /* icon only */ })} + ${template({ ...args, /* text + icon */ })} + `, + tags: ['anatomy'], + parameters: { flexLayout: true }, +}; +``` + +### Options + +Document every attribute/property not covered in Anatomy, States, or Behaviors. Consolidate related options into single stories. Use this order for story section-order: + +| Story | Content | +|-------|---------| +| `Sizes` | All size variants | +| `SemanticVariants` | Positive, informative, negative, notice, neutral | +| `NonSemanticVariants` | Color-coded categories (seafoam, indigo, etc.) | +| `StaticColors` | Static color pattern (see below) | +| `Quiet/Subtle/Emphasized` | Quiet, subtle, emphasized variants | +| `Outline` | Outline variants | +| `Positioning` | Positioning modifiers (fixed, absolute, relative) | + +```typescript +/** + * Components come in multiple sizes to fit various contexts. + */ +export const Sizes: Story = { + render: (args) => html` + ${template({ ...args, size: 's', label: 'Small' })} + ${template({ ...args, size: 'm', label: 'Medium' })} + ${template({ ...args, size: 'l', label: 'Large' })} + `, + tags: ['options'], + parameters: { flexLayout: true }, +}; + +/** + * Semantic variants provide meaning through color. + * + * Use these variants for the following statuses: + * + * - **Informative**: active, in use, live, published + * - **Neutral**: archived, deleted, paused, draft, not started, ended + * - **Positive**: approved, complete, success, new, purchased, licensed + * - **Notice**: needs approval, pending, scheduled, syncing, indexing, processing + * - **Negative**: error, alert, rejected, failed + * + */ +export const SemanticVariants: Story = { + render: (args) => html` + ${template({ ...args, variant: 'positive' })} + ${template({ ...args, variant: 'informative' })} + ${template({ ...args, variant: 'negative' })} + ${template({ ...args, variant: 'notice' })} + ${template({ ...args, variant: 'neutral' })} + `, + tags: ['options'], +}; + +/** + * Non-semantic variants use color-coded categories. + */ +export const NonSemanticVariants: Story = { + render: (args) => html` + ${template({ ...args, variant: 'seafoam' })} + ${template({ ...args, variant: 'indigo' })} + ${/* ... other colors */ ''} + `, + tags: ['options'], +}; +``` + +#### Static color pattern + +For components with `static-color` attribute, implement three stories: + +1. **`StaticBlack`** - `static-color="black"` on light background +2. **`StaticWhite`** - `static-color="white"` on dark background +3. **`StaticColors`** - Both variants side-by-side + +```typescript +/** + * Use `static-color` for display over images or colored backgrounds. + */ +export const StaticBlack: Story = { + args: { 'static-color': 'black' }, + parameters: { flexLayout: false, styles: { color: 'black' } }, + tags: ['options'], +}; + +export const StaticWhite: Story = { + args: { 'static-color': 'white' }, + parameters: { flexLayout: false, styles: { color: 'white' } }, + tags: ['options'], +}; + +export const StaticColors: Story = { + render: (args) => html` + ${['white', 'black'].map((color) => html`${template({ ...args, 'static-color': color })}`)} + `, + parameters: { flexLayout: false, staticColorsDemo: true }, + tags: ['options', '!test'], +}; +``` + +### States + +Combine all states into one story when possible. + +```typescript +/** + * Components can exist in various states. + */ +export const States: Story = { + render: (args) => html` + ${template({ ...args })} + ${template({ ...args, selected: true })} + ${template({ ...args, disabled: true })} + `, + tags: ['states'], + parameters: { flexLayout: true }, +}; +``` + +Complex states (e.g., animated indeterminate) may warrant separate stories. + +```typescript +/** + * Indeterminate state shows unknown progress. + */ +export const Indeterminate: Story = { + tags: ['states'], + args: { + indeterminate: true, + }, +}; +``` + +### Behaviors + +Document automatic (built-in) behaviors, like text-wrapping, as well as, all methods, and all events. + +```typescript +/** + * Long text automatically wraps to multiple lines. + */ +export const TextWrapping: Story = { + render: (args) => html` + ${template({ 'default-slot': 'Long text that wraps to multiple lines' })} + `, + tags: ['behaviors'], +}; +``` + +### Accessibility + +Required for every component. Document features and best practices. + +```typescript +/** + * ### Features + * The `` element implements several accessibility features: + * + * 1. **Feature name**: Description (e.g., keyboard navigation, ARIA states, roles, properties) + * 2. **Feature name**: Description + * + * ### Best practices + * + * - Best practice, such as, "Always provide a descriptive label" + * - Best practice, such as, "Ensure sufficient color contrast" + */ +export const Accessibility: Story = { + tags: ['a11y'], +}; +``` + +## JSDoc requirements + +Every story export requires a JSDoc comment explaining what it demonstrates, **except Playground**. + +Use markdown formatting: + +- `**Bold**` for emphasis +- Bullet lists for multiple points +- Backticks for code + +```typescript +/** + * Semantic variants provide meaning through color: + * - **Positive**: approved, complete, success + * - **Negative**: error, alert, rejected + */ +export const SemanticVariants: Story = { /* ... */ }; +``` + +## Accessibility requirements + +All stories must demonstrate accessible usage: + +1. **Include required labels** - `label`, `aria-label`, or slot content as needed +2. **Use meaningful content** - No "Lorem ipsum" placeholder text +3. **Show proper ARIA usage** - Correct attributes when applicable +4. **Never show inaccessible patterns** + +| Component type | Required | +|----------------|----------| +| Progress indicators | `label` attribute | +| Buttons/actions | Visible text or `aria-label` | +| Form fields | `