diff --git a/.changeset/document-headless-primitives.md b/.changeset/document-headless-primitives.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/document-headless-primitives.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/packages/swingset/src/components/DocsViewer.tsx b/packages/swingset/src/components/DocsViewer.tsx index d84acda1d60..30275bcb9c7 100644 --- a/packages/swingset/src/components/DocsViewer.tsx +++ b/packages/swingset/src/components/DocsViewer.tsx @@ -10,7 +10,16 @@ import { ViewSource } from './ViewSource'; const docModules: Record = { button: dynamic(() => import('../stories/button.mdx')), input: dynamic(() => import('../stories/input.mdx')), + // Headless primitives — alphabetical. + accordion: dynamic(() => import('../stories/accordion.mdx')), + autocomplete: dynamic(() => import('../stories/autocomplete.mdx')), collapsible: dynamic(() => import('../stories/collapsible.mdx')), + dialog: dynamic(() => import('../stories/dialog.mdx')), + menu: dynamic(() => import('../stories/menu.mdx')), + popover: dynamic(() => import('../stories/popover.mdx')), + select: dynamic(() => import('../stories/select.mdx')), + tabs: dynamic(() => import('../stories/tabs.mdx')), + tooltip: dynamic(() => import('../stories/tooltip.mdx')), }; interface DocsViewerProps { diff --git a/packages/swingset/src/lib/registry.ts b/packages/swingset/src/lib/registry.ts index 63d3f8cce13..1137b984ef4 100644 --- a/packages/swingset/src/lib/registry.ts +++ b/packages/swingset/src/lib/registry.ts @@ -1,6 +1,9 @@ // Import stories explicitly to control order and avoid type casting through unknown. +import { meta as accordionMeta } from '../stories/accordion.stories'; +import { meta as autocompleteMeta } from '../stories/autocomplete.stories'; import { Disabled, meta as buttonMeta, Primary, Sizes } from '../stories/button.stories'; import { meta as collapsibleMeta } from '../stories/collapsible.stories'; +import { meta as dialogMeta } from '../stories/dialog.stories'; import { Default, Disabled as InputDisabled, @@ -8,6 +11,11 @@ import { meta as inputMeta, Sizes as InputSizes, } from '../stories/input.stories'; +import { meta as menuMeta } from '../stories/menu.stories'; +import { meta as popoverMeta } from '../stories/popover.stories'; +import { meta as selectMeta } from '../stories/select.stories'; +import { meta as tabsMeta } from '../stories/tabs.stories'; +import { meta as tooltipMeta } from '../stories/tooltip.stories'; import { toSlug } from './slug'; import type { StoryModule } from './types'; @@ -18,9 +26,30 @@ const inputModule: StoryModule = { meta: inputMeta, Default, Sizes: InputSizes, // Headless primitives carry just `meta` (no story functions). Like every component // they're documented as a single overview page; their live demos come from `` / // `` embeds in the MDX, which import the stories module directly. +const accordionModule: StoryModule = { meta: accordionMeta }; +const autocompleteModule: StoryModule = { meta: autocompleteMeta }; const collapsibleModule: StoryModule = { meta: collapsibleMeta }; +const dialogModule: StoryModule = { meta: dialogMeta }; +const menuModule: StoryModule = { meta: menuMeta }; +const popoverModule: StoryModule = { meta: popoverMeta }; +const selectModule: StoryModule = { meta: selectMeta }; +const tabsModule: StoryModule = { meta: tabsMeta }; +const tooltipModule: StoryModule = { meta: tooltipMeta }; -export const registry: StoryModule[] = [buttonModule, inputModule, collapsibleModule]; +export const registry: StoryModule[] = [ + buttonModule, + inputModule, + // Primitives — alphabetical within the group. + accordionModule, + autocompleteModule, + collapsibleModule, + dialogModule, + menuModule, + popoverModule, + selectModule, + tabsModule, + tooltipModule, +]; /** Look up a component's story module from its slug (derived from `meta.title`). */ export function getModuleBySlug(slug: string): StoryModule | undefined { diff --git a/packages/swingset/src/stories/accordion.mdx b/packages/swingset/src/stories/accordion.mdx new file mode 100644 index 00000000000..ba7bae68de8 --- /dev/null +++ b/packages/swingset/src/stories/accordion.mdx @@ -0,0 +1,123 @@ +import * as AccordionStories from './accordion.stories'; + +# Accordion + +A set of stacked sections where each is toggled by its own heading button, from +`@clerk/headless`. It is a **headless** primitive: it supplies behavior, single/multiple +open state, roving-focus keyboard navigation, ARIA wiring, and the expand/collapse +animation lifecycle, but ships **no styles** — you bring your own CSS by targeting the +`data-cl-*` attributes each part emits. + +## Example + +The demo below is intentionally unstyled — it renders the raw primitive so you can see its +behavior and ARIA wiring. Click a trigger to open its panel; in `single` mode opening one +closes the others, and arrow keys move focus between triggers. + + + +## Usage + +```tsx +import { Accordion } from '@clerk/headless/accordion'; + + + + + Section 1 + + Content for section 1 + + + + Section 2 + + Content for section 2 + +; +``` + +### Single (one item open at a time) + +```tsx +{/* items */} +``` + +### Controlled + +```tsx +const [value, setValue] = useState(['item-1']); + + + {/* items */} +; +``` + +## Parts + +| Part | Default Element | Description | +| ------------------- | --------------- | ----------------------------------------------------------- | +| `Accordion.Root` | `
` | Root wrapper; owns open-item state and Home/End/arrow nav | +| `Accordion.Item` | `
` | Wraps one section; provides item context (value, open, IDs) | +| `Accordion.Header` | `

` | Heading wrapper for the trigger | +| `Accordion.Trigger` | `