diff --git a/.cursor/rules/component-documentation.mdc b/.cursor/rules/component-documentation.mdc new file mode 100644 index 00000000000..43468a41efb --- /dev/null +++ b/.cursor/rules/component-documentation.mdc @@ -0,0 +1,413 @@ +--- +globs: 2nd-gen/packages/swc/components/*/stories/** +alwaysApply: false +--- +# Component documentation standards + +Enforce comprehensive and consistent component documentation across 1st-gen and 2nd-gen implementations. + +## Scope + +Apply to component documentation in: + +- `1st-gen/packages/*/README.md` +- `2nd-gen/packages/swc/components/*/stories/*.stories.ts` +- `2nd-gen/packages/swc/components/*/stories/*.usage.mdx` + +## Documentation completeness requirements + +### Essential sections + +Every component must document: + +1. **Anatomy** - Component structure and key parts +2. **Options** - Variants, sizes, styles, and configuration +3. **Accessibility** - A11y features and best practices + +### Conditional sections + +Include if applicable: + +4. **States** - Different states (disabled, loading, error, etc.) +5. **Behaviors** - Interactive behaviors, text wrapping, etc. +6. **Installation** - How to install and import (may be centralized) + +## Anatomy documentation + +### Required content + +- List of component parts/slots +- Visual examples showing structure +- Explanation of required vs optional elements + +### Example format + +```typescript +/** + * A component-name consists of: + * + * - Primary element description + * - Secondary element description (optional) + * - Additional elements + * + * Components can contain X, Y, or both. + */ +``` + +### Common elements to document + +- Text/label slots +- Icon slots +- Required attributes +- Optional visual elements + +## Options documentation + +### Size options + +If component has sizes, document: + +- All available sizes (s, m, l, xl if supported) +- Default size +- Use cases for each size +- Visual hierarchy guidance + +**Example**: + +```typescript +/** + * Components come in [X] sizes to fit various contexts: + * + * - **Small**: Used for [specific use case] + * - **Medium**: Default size, used for [typical use case] + * - **Large**: Used for [prominent use case] + */ +``` + +### Variant options + +#### Semantic variants + +Document with clear use case guidance: + +```typescript +/** + * Semantic variants provide meaning through color: + * + * - **Positive**: approved, complete, success, new, purchased, licensed + * - **Informative**: active, in use, live, published + * - **Negative**: error, alert, rejected, failed + * - **Neutral**: archived, deleted, paused, draft, not started, ended + * - **Notice**: needs approval, pending, scheduled + */ +``` + +#### Non-semantic variants + +Document purpose and limitations: + +```typescript +/** + * When components are for color-coded categories, they use non-semantic colors. + * Non-semantic variants are ideally used for when there are 8 categories or less. + */ +``` + +### Style modifiers + +Document modifiers like: + +- `outline` style (semantic only) +- `subtle` style (reduced prominence) +- `fixed` positioning options +- `static-color` for colored backgrounds + +### Static color pattern + +For components with static-color support: + +```typescript +/** + * When displaying over images or colored backgrounds, use the `static-color` attribute for better contrast, + * e.g. `static-color="white"` on a dark background or `static-color="black"` on a light background. + */ +``` + +## States documentation + +### Required for components with states + +Document all states: + +- Default/normal state +- Disabled state +- Loading/indeterminate state +- Error state +- Selected/active state + +### 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. + */ +``` + +Include ARIA support documentation: + +``` +- **ARIA Support**: When disabled, the component automatically sets `aria-disabled="true"` +``` + +## Accessibility documentation + +### Required structure + +Every component must have comprehensive accessibility documentation with two subsections: + +1. **Features** - Built-in accessibility features +2. **Best Practices** - Guidelines for proper usage + +### Features template + +```typescript +/** + * ### Features + * + * The `` element implements several accessibility features: + * + * 1. **Feature name**: Clear description of what it does + * 2. **Feature name**: Clear description of what it does + * 3. **Feature name**: Clear description of what it does + * + * ### Best Practices + * + * - Specific, actionable guidance + * - Another best practice + * - Consider X for Y scenarios + */ +``` + +### Common accessibility features to document + +#### ARIA attributes + +- **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.) +- **ARIA Orientation**: Document for components with orientation + +**Examples**: + +``` +- **ARIA Role**: Automatically sets `role="progressbar"` for proper semantic meaning +- **Labeling**: Uses the `label` attribute value as `aria-label` +- **Status Communication**: Screen readers announce progress updates +``` + +#### Color meaning + +For components using color to convey information: + +``` +- **Color Meaning**: Colors are used in combination with text labels to ensure that status information is not conveyed through color alone +``` + +#### Keyboard interactions + +If component is interactive: + +``` +#### Keyboard interactions + +- Tab: Description of tab behavior +- Space or Enter: Description of activation +- Arrow keys: Description of navigation +``` + +### Best practices format + +Use bullet points with clear, actionable guidance: + +``` +- Use semantic variants when the status has specific meaning +- Always provide a descriptive `label` that explains what the component represents +- Ensure sufficient color contrast between the component and its background +- Avoid using for critical actions; use appropriate interactive components instead +``` + +## Cross-reference: 1st-gen to 2nd-gen migration + +### Content to preserve from 1st-gen + +When migrating or enhancing 2nd-gen documentation, check 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. **Do/Don't tables** - Valuable guidance often in 1st-gen +7. **Advanced examples** - Tooltips, containers, complex layouts +8. **Specific color contrast guidance** - Especially for static-color variants + +### Common gaps to check + +Compare 2nd-gen against 1st-gen README.md for: + +- [ ] Missing size options (xl is common) +- [ ] Missing semantic variants (accent is common) +- [ ] Undocumented disabled state +- [ ] Missing ARIA disabled documentation +- [ ] Lack of tooltip integration examples +- [ ] Missing icon-only accessibility guidance +- [ ] No keyboard interaction documentation +- [ ] Missing static-color contrast guidance +- [ ] No text truncation/wrapping behavior explanation + +### Enhancement opportunities + +Areas where 2nd-gen often improves on 1st-gen: + +- Better structured accessibility sections +- Clearer story organization +- More comprehensive anatomy sections +- Better visual comparisons (e.g., static colors side-by-side) +- Improved testability + +## Documentation quality checklist + +### For each component + +- [ ] Anatomy section with clear part descriptions +- [ ] All size options documented with use cases +- [ ] All variant options documented with guidance +- [ ] Semantic variants have usage examples +- [ ] Non-semantic variants note 8-category limit +- [ ] Style modifiers documented (outline, subtle, fixed, static-color) +- [ ] States section if component has states +- [ ] Disabled state documented if supported +- [ ] Accessibility Features section with numbered list +- [ ] Accessibility Best Practices section with bullet points +- [ ] ARIA attributes documented +- [ ] Color meaning documented for status indicators +- [ ] Keyboard interactions documented if interactive +- [ ] Label requirements clearly stated +- [ ] Contrast guidance for static colors +- [ ] Text wrapping/truncation behavior explained +- [ ] Do/don't guidance present + +## Writing style standards + +### Capitalization + +- Use sentence case for headings and descriptions +- Capitalize proper nouns and technical terms (JavaScript, CSS, ARIA, etc.) +- Use proper capitalization for semantic meaning (Positive, Negative, etc.) + +### Formatting + +- Use `**bold**` for emphasis and term introductions +- Use `code formatting` for attributes, values, and code references +- Use bullet lists for multiple related items +- Use numbered lists for sequential features or steps + +### Tone + +- Be clear and concise +- Use present tense +- Be prescriptive for best practices +- Explain "why" not just "what" + +## Example documentation levels + +### Minimal (inadequate) + +```typescript +/** + * Component with variants. + */ +export const Variants: Story = { /* ... */ }; +``` + +❌ Too brief, no context or guidance + +### Adequate + +```typescript +/** + * Badges come in semantic and non-semantic variants for different use cases. + */ +export const Variants: Story = { /* ... */ }; +``` + +⚠️ Basic information but lacks detail + +### Excellent + +```typescript +/** + * Semantic variants allow you to render the badge with a descriptive name that maps to a design-system-aligned color. + * This is the preferred way to assign color to a badge because it will align more consistently with other components in your UI with the same meaning. + * + * Use these variants for the following statuses: + * - **Positive**: approved, complete, success, new, purchased, licensed + * - **Informative**: active, in use, live, published + * - **Negative**: error, alert, rejected, failed + * - **Neutral**: archived, deleted, paused, draft, not started, ended + */ +export const SemanticVariants: Story = { /* ... */ }; +``` + +✅ Comprehensive explanation with use cases and guidance + +## Reference implementations + +### Exemplary migrations + +- **Asset**: 2nd-gen improves on minimal 1st-gen with better structure +- **Progress circle**: Clean reference implementation +- **Divider**: Maintains comprehensive 1st-gen content with improved organization + +### Components needing enhancement + +Check DOC-COMPARISON.md files in each component directory for: + +- Identified gaps vs 1st-gen +- Priority recommendations +- Specific missing content areas + +## Using documentation comparison reports + +Each migrated component has a `DOC-COMPARISON.md` file: + +``` +2nd-gen/packages/swc/components/[component-name]/DOC-COMPARISON.md +``` + +These reports identify: + +- **Missing content areas** - Critical gaps and nice-to-have additions +- **Content depth differences** - Where 1st-gen or 2nd-gen excels +- **Recommendations by priority** - High, medium, low priority enhancements +- **Content quality assessment** - Table comparing specific areas +- **Overall assessment** - Completeness score and summary + +Use these reports when: + +- Enhancing existing 2nd-gen documentation +- Creating new component documentation +- Reviewing documentation completeness +- Prioritizing documentation work + +## Continuous improvement + +When updating component documentation: + +1. Review DOC-COMPARISON.md for identified gaps +2. Check 1st-gen README.md for any additional details +3. Ensure all sections in this standard are addressed +4. Test examples in Storybook +5. Verify accessibility claims with actual implementation +6. Update DOC-COMPARISON.md if gaps are addressed diff --git a/.cursor/rules/stories-format.mdc b/.cursor/rules/stories-format.mdc new file mode 100644 index 00000000000..7ee8116e098 --- /dev/null +++ b/.cursor/rules/stories-format.mdc @@ -0,0 +1,465 @@ +--- +description: 2nd-gen/packages/swc/components/*/stories/** +alwaysApply: false +--- +# Storybook stories format standards + +Enforce consistent formatting and structure for Storybook stories files in 2nd-gen components. + +## Scope + +Apply to all `.stories.ts` and `.usage.mdx` files in: + +- `2nd-gen/packages/swc/components/*/stories/` + +## File structure requirements + +### Stories file (`.stories.ts`) + +Every component must have a `.stories.ts` file with the following structure: + +1. **Copyright header** (lines 1-11) +2. **Imports section** +3. **METADATA section** with visual separator +4. **AUTODOCS STORY section** with visual separator +5. **ANATOMY STORIES section** with visual separator (if applicable) +6. **STATES STORIES section** with visual separator (if applicable) +7. **OPTIONS STORIES section** with visual separator +8. **ACCESSIBILITY STORIES section** with visual separator +9. **HELPER FUNCTIONS section** with visual separator (if needed) + +### Usage file (`.usage.mdx`) + +Every component must have a `.usage.mdx` file with simplified structure: + +```mdx +import { SpectrumStories } from '../../../.storybook/blocks'; + +## Anatomy + + + +## States + + + +## Options + + + +## Accessibility + + +``` + +**Note**: Only include sections that have corresponding stories. + +## Section formatting + +### Visual separators + +Use consistent visual separators for major sections: + +```typescript +// ──────────────── +// METADATA +// ──────────────── + +// ──────────────────── +// AUTODOCS STORY +// ──────────────────── + +// ────────────────────────── +// ANATOMY STORIES +// ────────────────────────── + +// ────────────────────────── +// STATES STORIES +// ────────────────────────── + +// ────────────────────────── +// OPTIONS STORIES +// ────────────────────────── + +// ──────────────────────────────── +// ACCESSIBILITY STORIES +// ──────────────────────────────── + +// ──────────────────────── +// HELPER FUNCTIONS +// ──────────────────────── +``` + +## Meta configuration + +### Required fields + +The meta object must include: + +```typescript +const meta: Meta = { + title: 'Component name', // Required + component: 'swc-component-name', // Required + args, // Required + argTypes, // Required + render: (args) => template(args), // Required + parameters: { + actions: { + handles: events, // If events exist + }, + docs: { + subtitle: `Component description`, // Required + }, + design: { // Optional but recommended + type: 'figma', + url: 'https://www.figma.com/...', + }, + stackblitz: { // Optional but recommended + url: 'https://stackblitz.com/...', + }, + }, + tags: ['migrated'], // Required +}; +``` + +### JSDoc for meta + +Include comprehensive JSDoc comment above the meta object explaining what the component does: + +```typescript +/** + * Component description explaining its purpose and key features. + * Can be multiple paragraphs if needed. + */ +const meta: Meta = { + // ... +}; +``` + +## Story organization and tagging + +### Playground story + +Required first story after meta. Do NOT include JSDoc comment above Playground story: + +```typescript +export const Playground: Story = { + args: { + // Default args for playground + }, + tags: ['autodocs', 'dev'], +}; +``` + +### Anatomy stories + +Stories demonstrating component structure: + +```typescript +/** + * Description of the component's anatomy and key parts. + * Explain what makes up the component. + */ +export const Anatomy: Story = { + render: (args) => html` + ${template({ ...args /* specific anatomy example */ })} + `, + tags: ['anatomy'], + args: {}, +}; +``` + +### States stories + +Stories demonstrating different states (if applicable): + +```typescript +/** + * Description of this particular state. + */ +export const StateName: Story = { + render: (args) => html` + ${template({ ...args /* state example */ })} + `, + tags: ['states'], + args: {}, +}; +``` + +### Options stories + +Stories demonstrating variants, sizes, styles: + +```typescript +/** + * Description of this option/variant. + */ +export const OptionName: Story = { + render: () => html` + ${/* examples */} + `, + tags: ['options'], +}; +``` + +### Accessibility story + +Required accessibility demonstration: + +```typescript +/** + * ### Features + * + * The `` element implements several accessibility features: + * + * 1. **Feature name**: Description + * 2. **Feature name**: Description + * + * ### Best Practices + * + * - Best practice one + * - Best practice two + */ +export const Accessibility: Story = { + render: () => html` + ${/* accessibility examples */} + `, + tags: ['a11y'], +}; +``` + +## Tag usage requirements + +### Required tags + +- `'autodocs'` - Only on Playground story +- `'dev'` - Only on Playground story +- `'anatomy'` - For anatomy stories +- `'states'` - For state demonstration stories (if component has states) +- `'options'` - For variant, size, and style stories +- `'a11y'` - For accessibility demonstration story + +### Exclusion tags + +- `'!test'` - Exclude from test runs (e.g., complex static color stories) +- `'!dev'` - Exclude from dev Storybook + +### Migration tag + +- `'migrated'` - On the meta object to indicate refined format + +## JSDoc requirements + +### Above each story export + +Every story export must have a JSDoc comment explaining: + +- What it demonstrates +- Any important context or usage notes +- Best practices if relevant + +**Exception**: Do NOT add JSDoc comments above the Playground story. + +Use markdown formatting within JSDoc: + +- `**Bold**` for emphasis +- Bullet lists for multiple points +- Code formatting with backticks + +Example: + +```typescript +/** + * Semantic variants allow you to render the badge with a descriptive name that maps to a design-system-aligned color. + * + * Use these variants for the following statuses: + * - **Positive**: approved, complete, success, new, purchased, licensed + * - **Informative**: active, in use, live, published + * - **Negative**: error, alert, rejected, failed + */ +export const SemanticVariants: Story = { + // ... +}; +``` + +## Static color pattern + +For components with `static-color` attribute, implement the three-story pattern: + +```typescript +// Individual color stories without JSDoc +export const StaticWhite: Story = { + args: { + 'static-color': 'white', + // other args + }, +}; + +export const StaticBlack: Story = { + args: { + 'static-color': 'black', + // other args + }, +}; + +/** + * When displaying over images or colored backgrounds, use the `static-color` attribute for better contrast. + */ +export const StaticColors: Story = { + render: createStaticColorStory(template, [ + { args: StaticWhite.args }, + { args: StaticBlack.args }, + ]), + parameters: { + docs: { + source: { + code: generateStaticColorSource('swc-component-name', [ + { args: StaticWhite.args }, + { args: StaticBlack.args }, + ]), + }, + }, + }, + tags: ['options', '!test'], +}; +``` + +## Heading elements in stories + +### Demo class requirement + +When a story includes heading elements (h1, h2, h3, h4, h5, h6) in its rendered output, apply the `demo` class to prevent the heading from appearing in the table of contents: + +```typescript +export const Sizes: Story = { + render: () => + html`
+ ${Divider.VALID_SIZES.map((size) => { + const label = + size === 's' ? 'Small' : size === 'l' ? 'Large' : 'Medium'; + return html`
+

${label}

+ +

Text below the divider.

+
`; + })} +
`, + tags: ['options'], +}; +``` + +**Why**: The `demo` class prevents story headings from being included in the Storybook documentation's table of contents, keeping the TOC clean and focused on actual documentation sections. + +### Common scenarios + +Apply `class="demo"` to headings when: +- Demonstrating size variations with labeled examples +- Showing component hierarchy in rendered examples +- Including headings as part of the component demo context + +Do NOT use the `demo` class for: +- Section headings in JSDoc comments (use markdown) +- Headings in usage.mdx files (these should not exist) + +## Helper functions + +### CONTAINER helper + +If a CONTAINER helper is needed for layout: + +```typescript +/* @todo Pull this up into a decorator for all stories to leverage */ +function CONTAINER(content: TemplateResult<1>[]): TemplateResult { + return html`
+ ${content} +
`; +} +``` + +**Note**: Include @todo comment suggesting extraction to shared utility. + +## Usage.mdx requirements + +### Minimal structure + +Keep usage.mdx files minimal and use `` component: + +```mdx +import { SpectrumStories } from '../../../.storybook/blocks'; + +## Anatomy + + + +## Options + + + +## Accessibility + + +``` + +### Section rules + +- Use `hideTitle` for Anatomy and Accessibility sections +- Do NOT use `hideTitle` for States and Options sections +- Only include sections that have corresponding stories +- Use consistent section heading capitalization (sentence case) + +## Common violations + +### ❌ Don't + +- Use old Canvas/Story imports from '@storybook/addon-docs/blocks' +- Write verbose examples directly in usage.mdx +- Forget JSDoc comments on story exports (except Playground) +- Add JSDoc comment to Playground story +- Use inconsistent section separators +- Tag stories incorrectly (e.g., 'usage' instead of 'options') +- Forget to include subtitle in meta parameters +- Use 'usage' tag (deprecated in refined format) +- Include heading elements in stories without `class="demo"` + +### ✅ Do + +- Use SpectrumStories component in usage.mdx +- Include comprehensive JSDoc for each story (except Playground) +- Keep Playground story without JSDoc comment +- Use consistent visual separators +- Tag stories with proper semantic tags +- Include subtitle in meta.parameters.docs +- Keep usage.mdx minimal and maintainable +- Use 'anatomy', 'states', 'options', 'a11y' tags +- Apply `class="demo"` to any heading elements in story renders + +## Checklist for new/migrated stories + +- [ ] Copyright header present (2025 year) +- [ ] All required sections with visual separators +- [ ] Meta has title, component, args, argTypes, render, parameters.docs.subtitle, tags +- [ ] Playground story with 'autodocs' and 'dev' tags (no JSDoc comment) +- [ ] Anatomy story with 'anatomy' tag and JSDoc +- [ ] States stories with 'states' tag and JSDoc (if applicable) +- [ ] Options stories with 'options' tag and JSDoc +- [ ] Accessibility story with 'a11y' tag and comprehensive JSDoc +- [ ] All story exports have JSDoc comments (except Playground) +- [ ] Playground story does NOT have JSDoc comment +- [ ] Heading elements in stories have `class="demo"` +- [ ] Usage.mdx uses SpectrumStories component +- [ ] Usage.mdx only includes relevant sections +- [ ] Static color pattern implemented correctly (if applicable) +- [ ] Helper functions include @todo comments + +## Reference implementation + +See [`progress-circle`](/Users/ceickhoff/Desktop/Code/spectrum-web-components-2/2nd-gen/packages/swc/components/progress-circle/stories) for the canonical refined format implementation. diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 9b01c8e2744..2906306e0bc 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,3 +1,6 @@ { - "recommendations": ["runem.lit-plugin"] + "recommendations": [ + "runem.lit-plugin", + "bierner.jsdoc-markdown-highlighting" + ] } diff --git a/.vscode/local-extensions/README.md b/.vscode/local-extensions/README.md new file mode 100644 index 00000000000..53f7fbb9d9b --- /dev/null +++ b/.vscode/local-extensions/README.md @@ -0,0 +1,9 @@ +# Installing local extensions in Cursor + +The extensions in this directory are not available in Cursor but are in VSCode. The `vsix` file can be uploaded to Cursor locally to install the extension for usage. + +The following steps will get you started: + +1. Go to cursor, hit Cmd + Shift + P +2. Search for “Extensions: Install from VSIX…” +3. Select the extension file from this directory and download diff --git a/.vscode/local-extensions/bierner.jsdoc-markdown-highlighting-0.0.1.vsix b/.vscode/local-extensions/bierner.jsdoc-markdown-highlighting-0.0.1.vsix new file mode 100644 index 00000000000..e8eb4cac7c4 Binary files /dev/null and b/.vscode/local-extensions/bierner.jsdoc-markdown-highlighting-0.0.1.vsix differ diff --git a/2nd-gen/packages/core/components/progress-circle/ProgressCircle.base.ts b/2nd-gen/packages/core/components/progress-circle/ProgressCircle.base.ts index a56e68518e8..1777635784f 100644 --- a/2nd-gen/packages/core/components/progress-circle/ProgressCircle.base.ts +++ b/2nd-gen/packages/core/components/progress-circle/ProgressCircle.base.ts @@ -90,6 +90,7 @@ export abstract class ProgressCircleBase extends SizedMixin(SpectrumElement, { * Accessible label for the progress circle. * * Used to provide context about what is loading or progressing. + * @required for accessibility */ @property({ type: String }) public label = ''; diff --git a/2nd-gen/packages/core/shared/base/version.ts b/2nd-gen/packages/core/shared/base/version.ts index cb9e28ff658..2177cdd7401 100644 --- a/2nd-gen/packages/core/shared/base/version.ts +++ b/2nd-gen/packages/core/shared/base/version.ts @@ -9,6 +9,5 @@ * OF ANY KIND, either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ - // Generated by genversion. export const version = '1.10.0'; diff --git a/2nd-gen/packages/swc/.storybook/DocumentTemplate.mdx b/2nd-gen/packages/swc/.storybook/DocumentTemplate.mdx index 383a8b82650..f5692e38167 100644 --- a/2nd-gen/packages/swc/.storybook/DocumentTemplate.mdx +++ b/2nd-gen/packages/swc/.storybook/DocumentTemplate.mdx @@ -7,25 +7,56 @@ import { ArgTypes, Description, Subtitle, + useOf, } from '@storybook/addon-docs/blocks'; -import { SpectrumDocs } from './blocks/SpectrumDocs'; -import { SpectrumStories } from './blocks/SpectrumStories'; +import { + GettingStarted, + SpectrumDocs, + SpectrumStories, + StaticPrimaryStory, +} from './blocks'; import '@spectrum-web-components/tabs/sp-tabs.js'; import '@spectrum-web-components/tabs/sp-tab.js'; import '@spectrum-web-components/tabs/sp-tab-panel.js'; +export const checkIsSingleStory = () => { + const resolvedOf = useOf('meta', ['meta']); + const { stories } = resolvedOf.csfFile; + const visibleStories = Object.values(stories).filter((story) => + story.tags?.includes('autodocs' || 'dev') + ); + return visibleStories.length === 1; +}; + +export const SingleStoryDescription = () => { + const isSingleStory = checkIsSingleStory(); + + return (isSingleStory ? : null) + +} + +export const AdvancedExamplesStories = () => { + const isSingleStory = checkIsSingleStory(); + + return (isSingleStory ? null : ) + +} + <Subtitle /> -<Primary /> - +<Description /> +<SingleStoryDescription /> +<StaticPrimaryStory /> +<GettingStarted /> <SpectrumDocs tag="usage" /> ## API <Primary /> <Controls /> +<AdvancedExamplesStories /> ## Feedback diff --git a/2nd-gen/packages/swc/.storybook/assets/404.html b/2nd-gen/packages/swc/.storybook/assets/404.html new file mode 100644 index 00000000000..a8b801668d4 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/404.html @@ -0,0 +1,178 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8" /> + <title>Page not found + + + + + + + + + + +
+ +
+
+

404: Page not found

+
+ An abstract illustrated image with a whimsical color palette +

It's not you. It's us.

+

+ We've made a lot of improvements to the Spectrum CSS + documentation site, including consolidating all of the + documentation and moving it to a single location within + Storybook. +

+

+ If you're looking for information on Spectrum CSS + components, let's get you back to + our landing page. From there, experiment with all of our components to your + heart's content! +

+
+
+ + diff --git a/2nd-gen/packages/swc/.storybook/assets/favicon.png b/2nd-gen/packages/swc/.storybook/assets/favicon.png new file mode 100644 index 00000000000..988b1143327 Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/favicon.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/adobe_logo.svg b/2nd-gen/packages/swc/.storybook/assets/images/adobe_logo.svg new file mode 100644 index 00000000000..bde5a6cd3f7 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/images/adobe_logo.svg @@ -0,0 +1,5 @@ + diff --git a/2nd-gen/packages/swc/.storybook/assets/images/example-ava.png b/2nd-gen/packages/swc/.storybook/assets/images/example-ava.png new file mode 100644 index 00000000000..d77edcca8db Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/images/example-ava.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/example-ava@2x.png b/2nd-gen/packages/swc/.storybook/assets/images/example-ava@2x.png new file mode 100644 index 00000000000..6eb727b7a19 Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/images/example-ava@2x.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/github_logo.svg b/2nd-gen/packages/swc/.storybook/assets/images/github_logo.svg new file mode 100644 index 00000000000..5c9d2e78ff2 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/images/github_logo.svg @@ -0,0 +1,5 @@ + diff --git a/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-dark.png b/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-dark.png new file mode 100644 index 00000000000..961283356b0 Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-dark.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-light.png b/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-light.png new file mode 100644 index 00000000000..ea5f9512b7f Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/images/gradient-background-light.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/gray_migration-guide.png b/2nd-gen/packages/swc/.storybook/assets/images/gray_migration-guide.png new file mode 100644 index 00000000000..43dde6742d5 Binary files /dev/null and b/2nd-gen/packages/swc/.storybook/assets/images/gray_migration-guide.png differ diff --git a/2nd-gen/packages/swc/.storybook/assets/images/npm_logo.svg b/2nd-gen/packages/swc/.storybook/assets/images/npm_logo.svg new file mode 100644 index 00000000000..0e788a552c8 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/images/npm_logo.svg @@ -0,0 +1,11 @@ + diff --git a/2nd-gen/packages/swc/.storybook/assets/images/wc_logo.svg b/2nd-gen/packages/swc/.storybook/assets/images/wc_logo.svg new file mode 100644 index 00000000000..54c216f564e --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/images/wc_logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/2nd-gen/packages/swc/.storybook/assets/logo.svg b/2nd-gen/packages/swc/.storybook/assets/logo.svg new file mode 100644 index 00000000000..b0d13e9c54a --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/logo.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + Spectrum + Web Components + + \ No newline at end of file diff --git a/2nd-gen/packages/swc/.storybook/assets/manager.css b/2nd-gen/packages/swc/.storybook/assets/manager.css new file mode 100644 index 00000000000..8acaf229dd1 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/manager.css @@ -0,0 +1,102 @@ +/* stylelint-disable selector-class-pattern -- Targeting pre-defined Storybook classes */ + +/*! + * Copyright 2024 Adobe. All rights reserved. + * + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +body { + --spectrum-font-family-ar: + myriad-arabic, adobe-clean, 'Source Sans Pro', -apple-system, + blinkmacsystemfont, 'Segoe UI', roboto, ubuntu, 'Trebuchet MS', + 'Lucida Grande', sans-serif; + --spectrum-font-family-he: + myriad-hebrew, adobe-clean, 'Source Sans Pro', -apple-system, + blinkmacsystemfont, 'Segoe UI', roboto, ubuntu, 'Trebuchet MS', + 'Lucida Grande', sans-serif; + + --spectrum-font-family: var(--spectrum-sans-font-family-stack); + --spectrum-font-style: var(--spectrum-default-font-style); + --spectrum-font-size: var(--spectrum-font-size-100); + + margin: 0; + + font-family: var(--spectrum-font-family); + font-size: var(--spectrum-font-size); + font-style: var(--spectrum-font-style); +} + +body:lang(ar) { + font-family: var(--spectrum-font-family-ar); +} + +body:lang(he) { + font-family: var(--spectrum-font-family-he); +} + +.docblock-argstable-body tr td { + letter-spacing: unset; + font-size: 1rem; +} + +.docblock-argstable-body td > span:has(select), +.docblock-argstable-body td textarea { + max-inline-size: 192px !important; +} + +#storybook-explorer-searchfield { + padding: 3px 12px 5px 35px !important; +} + +.sidebar-item { + font-weight: 700 !important; +} + +button[data-action='collapse-root'] { + font-family: var(--spectrum-sans-font-family-stack); + font-style: var(--spectrum-detail-sans-serif-font-style); + font-weight: var(--spectrum-detail-sans-serif-font-weight); + font-size: var(--spectrum-detail-size-m); + margin-block-start: 0; + margin-block-end: 0; + + color: var(--spectrum-detail-color); + + line-height: var(--spectrum-detail-line-height); + letter-spacing: var(--spectrum-detail-letter-spacing); + text-transform: uppercase; +} + +.sidebar-item { + padding-block: 0.25rem; +} + +input, +textarea, +select { + border-radius: 0.25rem !important; +} + +input:focus, +textarea:focus, +select:focus, +#storybook-explorer-searchfield:focus { + border-color: rgb(2, 101, 220) !important; + box-shadow: rgb(2, 101, 220) 0 0 0 1px inset !important; +} + +.sbdocs-toc--custom { + width: 20rem !important; + nav { + width: 100% !important; + } +} +/* stylelint-enable selector-class-pattern */ diff --git a/2nd-gen/packages/swc/.storybook/assets/preview.css b/2nd-gen/packages/swc/.storybook/assets/preview.css new file mode 100644 index 00000000000..ec062419135 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/assets/preview.css @@ -0,0 +1,72 @@ +/* stylelint-disable selector-class-pattern -- Targeting pre-defined Storybook classes */ + +/*! + * Copyright 2024 Adobe. All rights reserved. + * + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +body { + --spectrum-font-family: var(--spectrum-sans-font-family-stack); + --spectrum-font-style: var(--spectrum-default-font-style); + --spectrum-font-size: var(--spectrum-font-size-100); + + margin: 0; + + font-family: var(--spectrum-font-family); + font-size: var(--spectrum-font-size); + font-style: var(--spectrum-font-style); + + color: var(--spectrum-neutral-content-color-default, rgb(34, 34, 34)); + background-color: var(--spectrum-background-base-color, rgb(230, 230, 230)); +} + +.spectrum { + /* Gradient that changes with the color theme. */ + --spectrum-examples-gradient: linear-gradient( + 45deg, + var(--spectrum-magenta-1500), + var(--spectrum-blue-1500) + ); + + /* Gradients that do not change with the color theme, for use in static color backgrounds. */ + --spectrum-examples-gradient-static-black: linear-gradient( + 45deg, + rgb(255 241 246), + rgb(238 245 255) + ); + --spectrum-examples-gradient-static-white: linear-gradient( + 45deg, + rgb(64 0 22), + rgb(14 24 67) + ); + + color: var(--spectrum-neutral-content-color-default); + background-color: var(--spectrum-background-base-color); + -webkit-tap-highlight-color: rgb(0, 0, 0, 0%); +} + +.spectrum .spectrum-examples-static-black { + background: var(--spectrum-examples-gradient-static-black); +} + +.spectrum .spectrum-examples-static-white { + background: var(--spectrum-examples-gradient-static-white); +} + +/* --- DOCS STYLES --- */ +/* Force the modal wrapper to be contained by the frame not the viewport */ +#root-inner { + .spectrum-Modal-wrapper { + inline-size: 100% !important; + } +} + +/* stylelint-enable selector-class-pattern */ diff --git a/2nd-gen/packages/swc/.storybook/blocks/GettingStarted.tsx b/2nd-gen/packages/swc/.storybook/blocks/GettingStarted.tsx new file mode 100644 index 00000000000..5cb311a7d22 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/blocks/GettingStarted.tsx @@ -0,0 +1,48 @@ +import { Markdown, useOf } from '@storybook/addon-docs/blocks'; +import React from 'react'; +import { formatComponentName } from '../helpers/index.js'; + +/** + * A block that renders getting started instructions for a Spectrum Web Component. + * Automatically derives package name and component names from the Storybook meta title. + * + * @param of - The Storybook meta or story to resolve the component from + * @param packageName - Optional override for the package name (defaults to derived kebab-case from title) + * @param componentName - Optional override for the component class name (defaults to derived PascalCase from title) + * @param tagName - Optional override for the custom element tag name (defaults to sp-{packageName}) + */ +export const GettingStarted = ({ of }: { of?: any }) => { + const resolvedOf = useOf(of || 'meta', ['meta']); + + // Extract component name in kebab-case from the title (e.g., "Components/Progress Circle" -> "progress-circle") + const packageName = formatComponentName(resolvedOf.preparedMeta?.title); + + // Extract component name in PascalCase from the title (e.g., "Components/Progress Circle" -> "ProgressCircle") + const baseClassName = formatComponentName( + resolvedOf.preparedMeta?.title, + 'pascal' + ); + + const tagName = `swc-${packageName}`; + + const markdownContent = `## Getting started + +\`\`\`zsh +yarn add @spectrum-web-components/${packageName} +\`\`\` + +Import the side effectful registration of \`<${tagName}>\` via: + +\`\`\`typescript +import '@spectrum-web-components/${packageName}/${tagName}.js'; +\`\`\` + +When looking to leverage the \`${baseClassName}\` base class as a type and/or for extension purposes, do so via: + +\`\`\`typescript +import { ${baseClassName} } from '@spectrum-web-components/${packageName}'; +\`\`\` +`; + + return {markdownContent}; +}; diff --git a/2nd-gen/packages/swc/.storybook/blocks/SpectrumDocs.tsx b/2nd-gen/packages/swc/.storybook/blocks/SpectrumDocs.tsx index d0286d35fd3..3f6d0facfa3 100644 --- a/2nd-gen/packages/swc/.storybook/blocks/SpectrumDocs.tsx +++ b/2nd-gen/packages/swc/.storybook/blocks/SpectrumDocs.tsx @@ -1,8 +1,7 @@ /// import { useOf } from '@storybook/addon-docs/blocks'; import React, { useEffect, useState } from 'react'; - -const TAGS = ['overview', 'usage', 'a11y', 'examples']; +import { formatComponentName } from '../helpers/index.js'; // Glob import all MDX files from component stories directories as compiled React components const mdxModules = import.meta.glob<{ default: React.ComponentType }>( @@ -32,12 +31,9 @@ export const SpectrumDocs = ({ useEffect(() => { // Extract component name from the title (e.g., "Components/Progress Circle" -> "progress-circle") - const title = resolvedOf.preparedMeta?.title || ''; - const componentName = title - .split('/') - .pop() - ?.toLowerCase() - .replace(/\s+/g, '-'); + const componentName = formatComponentName( + resolvedOf.preparedMeta?.title + ); // Find the matching MDX file path based on component name and tag const matchingPath = Object.keys(mdxModules).find((path) => { diff --git a/2nd-gen/packages/swc/.storybook/blocks/SpectrumStories.tsx b/2nd-gen/packages/swc/.storybook/blocks/SpectrumStories.tsx index 835f0ba5944..4e1b43ecb32 100644 --- a/2nd-gen/packages/swc/.storybook/blocks/SpectrumStories.tsx +++ b/2nd-gen/packages/swc/.storybook/blocks/SpectrumStories.tsx @@ -1,8 +1,14 @@ -import { Story, Description, useOf } from '@storybook/addon-docs/blocks'; +import { + Canvas, + Description, + Markdown, + useOf, +} from '@storybook/addon-docs/blocks'; import React from 'react'; /** * A block that renders all stories tagged with a specified tag from the component's stories file. + * Stories are rendered in the order they are defined in the stories file. * - if a meta reference is passed, it finds all tagged stories from that meta * - if nothing is passed, it defaults to the current meta * @@ -12,11 +18,15 @@ import React from 'react'; export const SpectrumStories = ({ of, tag = 'usage', + hideTitle = false, }: { of?: any; tag?: string; + hideTitle?: boolean; }) => { const resolvedOf = useOf(of || 'meta', ['story', 'meta']); + + // Object.values() preserves insertion order (definition order in the file) const taggedStories = Object.values( resolvedOf.type === 'meta' ? resolvedOf.csfFile.stories @@ -30,10 +40,10 @@ export const SpectrumStories = ({ return ( <> {taggedStories.map((story: any) => ( - -

{story.name}

- - + + {!hideTitle && {`### ${story.name}`}} + + ))} diff --git a/2nd-gen/packages/swc/.storybook/blocks/StaticPrimaryStory.tsx b/2nd-gen/packages/swc/.storybook/blocks/StaticPrimaryStory.tsx new file mode 100644 index 00000000000..38353d9f8b4 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/blocks/StaticPrimaryStory.tsx @@ -0,0 +1,86 @@ +import { Canvas, Story, useOf } from '@storybook/addon-docs/blocks'; +import React from 'react'; +import { formatComponentName } from '../helpers/index.js'; + +export const StaticPrimaryStory = () => { + const resolvedOf = useOf('meta', ['meta']); + const primaryStory = Object.values(resolvedOf.csfFile.stories).find( + (story) => + story.name === 'Playground' || story.id.endsWith('--playground') + ); + if (!primaryStory) return null; + primaryStory.argTypes = undefined; + + // Extract component name and create GitHub link + const componentName = formatComponentName(resolvedOf.preparedMeta?.title); + const githubBaseUrl = + 'https://github.com/adobe/spectrum-web-components/tree/main/2nd-gen/packages/swc/components'; + + // Build additional actions dynamically based on available metadata + const additionalActions = []; + + // Spectrum Design Guidance - requires valid component name + if ( + componentName && + !resolvedOf.csfFile.meta.parameters?.missingDesignDocs + ) { + additionalActions.push({ + title: 'Read Spectrum Design Guidance', + onClick: () => { + window.open( + `https://s2.spectrum.corp.adobe.com/page/${componentName}/`, + '_blank' + ); + }, + }); + } + + // Figma - requires design URL in parameters + if (resolvedOf.csfFile.meta.parameters?.design?.url) { + additionalActions.push({ + title: 'View Figma', + onClick: () => { + window.open( + resolvedOf.csfFile.meta.parameters.design.url, + '_blank' + ); + }, + }); + } + + // GitHub - requires valid component name + if (componentName) { + additionalActions.push({ + title: 'View source on GitHub', + onClick: () => { + window.open(`${githubBaseUrl}/${componentName}`, '_blank'); + }, + }); + } + + // Stackblitz - requires stackblitz URL in meta parameters + if (resolvedOf.csfFile.meta.parameters?.stackblitz?.url) { + additionalActions.push({ + title: 'Debug in Stackblitz', + onClick: () => { + window.open( + resolvedOf.csfFile.meta.parameters.stackblitz.url, + '_blank' + ); + }, + }); + } + + const canvasOptions = { + additionalActions, + withToolbar: false, + sourceState: 'none', + layout: 'centered', + }; + + return ( + + + + ); +}; diff --git a/2nd-gen/packages/swc/.storybook/blocks/index.ts b/2nd-gen/packages/swc/.storybook/blocks/index.ts new file mode 100644 index 00000000000..bc81c667da2 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/blocks/index.ts @@ -0,0 +1,4 @@ +export * from './GettingStarted'; +export * from './SpectrumDocs'; +export * from './SpectrumStories'; +export * from './StaticPrimaryStory'; diff --git a/2nd-gen/packages/swc/.storybook/decorators/flex-layout.ts b/2nd-gen/packages/swc/.storybook/decorators/flex-layout.ts new file mode 100644 index 00000000000..bc96953d2c1 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/decorators/flex-layout.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { html } from 'lit'; +import { makeDecorator } from '@storybook/preview-api'; +import type { DecoratorFunction } from '@storybook/types'; + +/** + * Decorator that wraps stories in a flex container with consistent spacing. + * This is useful for components that benefit from horizontal layout with gap spacing. + * + * Can be disabled per-story by setting parameters.flexLayout = false + */ +export const withFlexLayout: DecoratorFunction = makeDecorator({ + name: 'withFlexLayout', + parameterName: 'flexLayout', + wrapper: (StoryFn, context) => { + // Allow stories to opt-out of the flex layout + const flexLayout = context.parameters?.flexLayout ?? true; + + if (!flexLayout) { + return StoryFn(context); + } + + return html` +
+ ${StoryFn(context)} +
+ `; + }, +}); diff --git a/2nd-gen/packages/swc/.storybook/decorators/index.ts b/2nd-gen/packages/swc/.storybook/decorators/index.ts new file mode 100644 index 00000000000..f79d9d49f14 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/decorators/index.ts @@ -0,0 +1,2 @@ +export { withStaticColorBackground } from './static-color-background'; +export { withFlexLayout } from './flex-layout'; diff --git a/2nd-gen/packages/swc/.storybook/decorators/static-color-background.ts b/2nd-gen/packages/swc/.storybook/decorators/static-color-background.ts index 8463ac9258f..baca8445361 100644 --- a/2nd-gen/packages/swc/.storybook/decorators/static-color-background.ts +++ b/2nd-gen/packages/swc/.storybook/decorators/static-color-background.ts @@ -13,13 +13,15 @@ import { html } from 'lit'; import { makeDecorator } from '@storybook/preview-api'; import type { DecoratorFunction } from '@storybook/types'; +import gradientBackgroundDark from '../assets/images/gradient-background-dark.png'; +import gradientBackgroundLight from '../assets/images/gradient-background-light.png'; /** * Static color background settings - matching spectrum-css gradients */ const staticColorSettings = { - black: 'linear-gradient(45deg, rgb(255 241 246), rgb(238 245 255))', - white: 'linear-gradient(45deg, rgb(64 0 22), rgb(14 24 67))', + black: `url(${gradientBackgroundLight}) no-repeat center center`, + white: `url(${gradientBackgroundDark}) no-repeat center center`, } as const; /** @@ -45,9 +47,13 @@ export const withStaticColorBackground: DecoratorFunction = makeDecorator({ return StoryFn(context); } - // Wrap the story with the background + // Wrap the story with the background. + // Note: withFlexLayout will handle the flex styling if enabled, + // so we only add justify-content: center here for additional centering. return html` -
+
${StoryFn(context)}
`; diff --git a/2nd-gen/packages/swc/.storybook/helpers/README.md b/2nd-gen/packages/swc/.storybook/helpers/README.md new file mode 100644 index 00000000000..ea699de4dd3 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/helpers/README.md @@ -0,0 +1,92 @@ +# Storybook helpers + +Reusable utilities for creating consistent Storybook stories across components. + +## Static color wrapper helpers + +When documenting components that support `static-color` variants (for use on colored backgrounds), these helpers provide a clean, reusable pattern. + +### Usage + +Import the helpers in your stories file: + +```typescript +import { + createStaticColorStory, + generateStaticColorSource, +} from '../../../.storybook/helpers/index.js'; +``` + +### Example: Showing multiple static color variants + +```typescript +export const StaticColors: Story = { + render: createStaticColorStory(template, [ + { + staticColor: 'white', + args: { progress: 60, label: 'Loading on dark' }, + }, + { + staticColor: 'black', + args: { progress: 60, label: 'Loading on light' }, + }, + ]), + parameters: { + docs: { + source: { + code: generateStaticColorSource('swc-progress-circle', [ + { + staticColor: 'white', + args: { progress: 60, label: 'Loading on dark' }, + }, + { + staticColor: 'black', + args: { progress: 60, label: 'Loading on light' }, + }, + ]), + }, + }, + }, + tags: ['options'], +}; +``` + +### What it does + +1. **`createStaticColorStory`**: Wraps each component variant in an appropriately colored background + - `white` static color → dark gradient background + - `black` static color → light gradient background + +2. **`generateStaticColorSource`**: Generates clean source code for the docs that excludes the wrapper styling + +### Benefits + +- **Reusable**: Same pattern across all components +- **Clean source**: Source code view shows only the component, not the wrapper +- **Consistent styling**: Uses the same gradient backgrounds as the decorator +- **Type-safe**: Full TypeScript support + +### Individual wrapper + +If you need to wrap a single component: + +```typescript +import { withStaticColorWrapper } from '../../../.storybook/helpers/index.js'; + +export const SingleStatic: Story = { + render: (args) => + withStaticColorWrapper( + template({ ...args, 'static-color': 'white' }), + 'white' + ), +}; +``` + +## Adding new helpers + +When adding new helpers to this directory: + +1. Create the helper file in this directory +2. Export it from `index.ts` +3. Document it in this README +4. Add usage examples in component stories diff --git a/2nd-gen/packages/swc/.storybook/helpers/format-component-name.ts b/2nd-gen/packages/swc/.storybook/helpers/format-component-name.ts new file mode 100644 index 00000000000..be76c8896d9 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/helpers/format-component-name.ts @@ -0,0 +1,18 @@ +export const formatComponentName = ( + title: string, + typeCase: 'kebab' | 'pascal' = 'kebab' +) => { + const formattedComponentName = title + .split('/') + .pop() + ?.toLowerCase() + .replace(/\s+/g, '-'); + + if (typeCase === 'pascal') { + return formattedComponentName + ?.split('-') + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(''); + } + return formattedComponentName; +}; diff --git a/2nd-gen/packages/swc/.storybook/helpers/index.ts b/2nd-gen/packages/swc/.storybook/helpers/index.ts new file mode 100644 index 00000000000..0246cf67a75 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/helpers/index.ts @@ -0,0 +1,18 @@ +/** + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +export { + withStaticColorWrapper, + createStaticColorStory, + generateStaticColorSource, +} from './static-color-wrapper.js'; +export { formatComponentName } from './format-component-name.js'; \ No newline at end of file diff --git a/2nd-gen/packages/swc/.storybook/helpers/static-color-wrapper.ts b/2nd-gen/packages/swc/.storybook/helpers/static-color-wrapper.ts new file mode 100644 index 00000000000..8aedb3edde8 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/helpers/static-color-wrapper.ts @@ -0,0 +1,126 @@ +/** + * Copyright 2025 Adobe. All rights reserved. + * This file is licensed to you under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. You may obtain a copy + * of the License at http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under + * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS + * OF ANY KIND, either express or implied. See the License for the specific language + * governing permissions and limitations under the License. + */ + +import { html } from 'lit'; +import type { TemplateResult } from 'lit'; +import gradientBackgroundDark from '../assets/images/gradient-background-dark.png'; +import gradientBackgroundLight from '../assets/images/gradient-background-light.png'; + +/** + * Background settings for static color variants + */ +const staticColorBackgrounds = { + white: `url(${gradientBackgroundDark}) no-repeat center center`, + black: `url(${gradientBackgroundLight}) no-repeat center center`, +} as const; + +/** + * Wraps a component template in a background appropriate for the given static color + * + * @param content - The component template to wrap + * @param staticColor - The static color variant ('white' or 'black') + * @returns The wrapped template with background styling + */ +export function withStaticColorWrapper( + content: TemplateResult, + staticColor: 'white' | 'black' +): TemplateResult { + const background = staticColorBackgrounds[staticColor]; + + return html` +
+ ${content} +
+ `; +} + +/** + * Creates a story that displays multiple static color variants side by side + * + * @param renderFn - Function that renders a component with given args + * @param variants - Array of variant configurations with args and static color + * @returns Story render function that wraps each variant appropriately + * + * @example + * ```typescript + * export const StaticColors: Story = { + * render: createStaticColorStory(template, [ + * { args: { progress: 60, label: 'Loading on dark', 'static-color': 'white' } }, + * { args: { progress: 60, label: 'Loading on light', 'static-color': 'black' } }, + * ]), + * tags: ['options'], + * }; + * ``` + */ +export function createStaticColorStory>( + renderFn: (args: T) => TemplateResult, + variants: Array<{ args: Partial }> +) { + return (args: T) => html` + ${variants.map(({ args: variantArgs }) => + withStaticColorWrapper( + renderFn({ ...args, ...variantArgs }), + variantArgs['static-color'] as 'white' | 'black' + ) + )} + `; +} + +/** + * Generates clean source code for documentation that excludes the wrapper styling + * + * @param componentName - The web component tag name (e.g., 'swc-progress-circle') + * @param variants - Array of variant configurations + * @returns Source code string for documentation + * + * @example + * ```typescript + * parameters: { + * docs: { + * source: { + * code: generateStaticColorSource('swc-progress-circle', [ + * { args: { progress: 60, label: 'Loading', 'static-color': 'white' } }, + * { args: { progress: 60, label: 'Loading', 'static-color': 'black' } }, + * ]), + * }, + * }, + * } + * ``` + */ +export function generateStaticColorSource>( + componentName: string, + variants: Array<{ args: Partial }> +): string { + return variants + .map(({ args }) => { + const attributes = Object.entries(args) + .map(([key, value]) => { + if (typeof value === 'boolean') { + return value ? key : ''; + } + if (typeof value === 'string') { + return `${key}="${value}"`; + } + if (typeof value === 'number') { + return `${key}="${value}"`; + } + return ''; + }) + .filter(Boolean) + .join(' '); + + return `<${componentName} ${attributes}>`; + }) + .join('\n\n'); +} diff --git a/2nd-gen/packages/swc/.storybook/loaders/font-loader.ts b/2nd-gen/packages/swc/.storybook/loaders/font-loader.ts new file mode 100644 index 00000000000..6b2a700d054 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/loaders/font-loader.ts @@ -0,0 +1,11 @@ +export const FontLoader = async () => ({ + fonts: new Promise((resolve) => { + // First check if the fonts are already loaded + if (typeof window.Typekit !== 'undefined') resolve(); + + // Listen for a custom event indicating the Adobe Fonts have loaded + document.addEventListener('typekit-loaded', () => { + if (typeof window.Typekit !== 'undefined') resolve(); + }); + }), +}); diff --git a/2nd-gen/packages/swc/.storybook/main.ts b/2nd-gen/packages/swc/.storybook/main.ts index c9a95aa6755..67977f63bfa 100644 --- a/2nd-gen/packages/swc/.storybook/main.ts +++ b/2nd-gen/packages/swc/.storybook/main.ts @@ -1,6 +1,7 @@ import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; import { mergeConfig } from 'vite'; +import remarkGfm from 'remark-gfm'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -27,17 +28,6 @@ const config = { defaultName: 'README', }, framework: '@storybook/web-components-vite', - tags: { - a11y: { - defaultFilterSelection: 'exclude', - }, - usage: { - defaultFilterSelection: 'exclude', - }, - examples: { - defaultFilterSelection: 'exclude', - }, - }, core: { disableTelemetry: true, }, @@ -46,6 +36,11 @@ const config = { name: '@storybook/addon-docs', options: { transcludeMarkdown: true, + mdxPluginOptions: { + mdxCompileOptions: { + remarkPlugins: [remarkGfm], + }, + }, }, }, '@storybook/addon-a11y', diff --git a/2nd-gen/packages/swc/.storybook/manager-head.html b/2nd-gen/packages/swc/.storybook/manager-head.html new file mode 100644 index 00000000000..8a2fab05e7b --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/manager-head.html @@ -0,0 +1,46 @@ + + + + + + + + diff --git a/2nd-gen/packages/swc/.storybook/manager.ts b/2nd-gen/packages/swc/.storybook/manager.ts new file mode 100644 index 00000000000..e8a717ef544 --- /dev/null +++ b/2nd-gen/packages/swc/.storybook/manager.ts @@ -0,0 +1,67 @@ +import { addons } from '@storybook/manager-api'; +import { create } from '@storybook/theming'; +import '../tokens/global-vars.css'; +import '../tokens/index.css'; +import '../tokens/light-vars.css'; +import '../tokens/medium-vars.css'; +import './assets/manager.css'; + +import logo from './assets/logo.svg'; + +const root = document.body ?? document.documentElement; +if (root) root.classList.add('spectrum', 'spectrum--light', 'spectrum--medium'); + +addons.setConfig({ + theme: create({ + base: 'light', + + brandTitle: 'Adobe | Spectrum Web Components', + brandUrl: 'https://opensource.adobe.com/spectrum-web-components', // TODO: Add the correct URL once we are publishing 2nd-gen + brandImage: logo, + brandTarget: '_self', + + typography: { + fonts: { + base: 'adobe-clean, "Adobe Clean", "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Trebuchet MS", "Lucida Grande", sans-serif', + code: '"Source Code Pro", Monaco, monospace', + }, + }, + + // colorPrimary: "#7326d3", + colorSecondary: 'rgb(2, 101, 220)', + + /* Being applied to the active state of radio buttons */ + appBg: 'rgb(248, 248, 248)', + /* Being applied to the arg table */ + appContentBg: 'rgb(255, 255, 255)', + appBorderColor: 'rgb(213, 213, 213)', + appBorderRadius: 8, + + /* Text colors */ + fontBase: + 'adobe-clean, "Adobe Clean", "Source Sans Pro", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Trebuchet MS", "Lucida Grande", sans-serif', + fontCode: '"Source Code Pro", Monaco, monospace', + textColor: 'rgb(34, 34, 34)', + textInverseColor: 'rgb(219, 219, 219)', + textMutedColor: 'rgb(175, 175, 175)', + + /* Toolbar default and active colors */ + barTextColor: 'rgb(34, 34, 34)', + barHoverColor: 'rgb(2, 101, 220)', + barSelectedColor: 'rgb(2, 101, 220)', + barBg: 'rgb(255, 255, 255)', + + // buttonBg: "rgb(255, 255, 255)", + // buttonBorder: "transparent", + // booleanBg: "rgb(255, 255, 255)", + // booleanSelectedBg: "rgb(213, 213, 213)", + + /* Form colors */ + inputBg: 'rgb(255, 255, 255)', + inputBorder: 'rgb(177, 177, 177)', + inputTextColor: 'rgb(34, 34, 34)', + inputBorderRadius: 4, + + // gridCellSize?: number; + }), +}); diff --git a/2nd-gen/packages/swc/.storybook/preview-head.html b/2nd-gen/packages/swc/.storybook/preview-head.html index 95e0ee7007a..cd8c4bb514a 100644 --- a/2nd-gen/packages/swc/.storybook/preview-head.html +++ b/2nd-gen/packages/swc/.storybook/preview-head.html @@ -1,14 +1,129 @@ + +