@arbor-education/design-system.components 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent-memory/blanche-designspert/MEMORY.md +189 -0
- package/.agent-memory/dorothy-fact-checker/MEMORY.md +228 -0
- package/.agent-memory/dorothy-fact-checker/numberinput_component.md +53 -0
- package/.agent-memory/dorothy-fact-checker/progress_component.md +36 -0
- package/.agent-memory/rose-storybookspert/MEMORY.md +105 -0
- package/.agent-memory/sophia-componentspert/MEMORY.md +34 -0
- package/{.claude/agent-memory → .agent-memory}/sophia-componentspert/components.md +170 -17
- package/{.claude → .gather}/agents/blanche-designspert.md +7 -2
- package/{.claude → .gather}/agents/dorothy-fact-checker.md +7 -2
- package/{.claude → .gather}/agents/rose-storybookspert.md +80 -11
- package/{.claude → .gather}/agents/sophia-componentspert.md +9 -4
- package/.gather/gather.yaml +9 -0
- package/{CLAUDE.md → .gather/instructions/project-overview.md} +42 -9
- package/{.claude → .gather}/skills/analyze-design/README.md +5 -0
- package/{.claude → .gather}/skills/analyze-design/SKILL.md +1 -1
- package/.gather/skills/analyze-design/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/README.md +5 -0
- package/{.claude → .gather}/skills/create-page/design-analysis-template.md +5 -0
- package/.gather/skills/create-page/meta.md +4 -0
- package/{.claude → .gather}/skills/create-page/page-template.scss +5 -0
- package/{.claude → .gather}/skills/create-page/page-template.tsx +5 -0
- package/{.claude → .gather}/skills/map-legacy/README.md +5 -0
- package/.gather/skills/map-legacy/meta.md +4 -0
- package/{.claude → .gather}/skills/migrate-page/README.md +5 -0
- package/.gather/skills/migrate-page/meta.md +4 -0
- package/.gather/skills/write-stories/README.md +157 -0
- package/.gather/skills/write-stories/SKILL.md +841 -0
- package/.gather/skills/write-stories/meta.md +4 -0
- package/.ralph/storybook-upgrade/knowledge.md +308 -0
- package/.ralph/storybook-upgrade/prd.json +777 -0
- package/.ralph/storybook-upgrade/progress.md +342 -0
- package/.storybook/DocsTemplate.tsx +122 -0
- package/.storybook/preview.ts +40 -0
- package/.stylelintignore +2 -0
- package/CHANGELOG.md +14 -0
- package/{.claude/component-library.md → component-library.md} +27 -10
- package/dist/components/articleCard/ArticleCard.d.ts +30 -0
- package/dist/components/articleCard/ArticleCard.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.js +24 -0
- package/dist/components/articleCard/ArticleCard.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts +18 -0
- package/dist/components/articleCard/ArticleCard.stories.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.stories.js +112 -0
- package/dist/components/articleCard/ArticleCard.stories.js.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts +2 -0
- package/dist/components/articleCard/ArticleCard.test.d.ts.map +1 -0
- package/dist/components/articleCard/ArticleCard.test.js +49 -0
- package/dist/components/articleCard/ArticleCard.test.js.map +1 -0
- package/dist/components/badge/Badge.stories.d.ts +85 -6
- package/dist/components/badge/Badge.stories.d.ts.map +1 -1
- package/dist/components/badge/Badge.stories.js +626 -27
- package/dist/components/badge/Badge.stories.js.map +1 -1
- package/dist/components/banner/Banner.stories.d.ts +129 -63
- package/dist/components/banner/Banner.stories.d.ts.map +1 -1
- package/dist/components/banner/Banner.stories.js +855 -39
- package/dist/components/banner/Banner.stories.js.map +1 -1
- package/dist/components/button/Button.stories.d.ts +148 -8
- package/dist/components/button/Button.stories.d.ts.map +1 -1
- package/dist/components/button/Button.stories.js +1089 -80
- package/dist/components/button/Button.stories.js.map +1 -1
- package/dist/components/card/Card.d.ts +41 -12
- package/dist/components/card/Card.d.ts.map +1 -1
- package/dist/components/card/Card.js +46 -17
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/card/Card.stories.d.ts +9 -84
- package/dist/components/card/Card.stories.d.ts.map +1 -1
- package/dist/components/card/Card.stories.js +15 -73
- package/dist/components/card/Card.stories.js.map +1 -1
- package/dist/components/card/Card.test.js +50 -152
- package/dist/components/card/Card.test.js.map +1 -1
- package/dist/components/dot/Dot.stories.d.ts +46 -11
- package/dist/components/dot/Dot.stories.d.ts.map +1 -1
- package/dist/components/dot/Dot.stories.js +504 -15
- package/dist/components/dot/Dot.stories.js.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.d.ts +89 -14
- package/dist/components/dropdown/Dropdown.stories.d.ts.map +1 -1
- package/dist/components/dropdown/Dropdown.stories.js +769 -17
- package/dist/components/dropdown/Dropdown.stories.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +95 -35
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +1174 -69
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts +96 -9
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js +717 -10
- package/dist/components/formField/inputs/checkbox/CheckboxInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts +149 -11
- package/dist/components/formField/inputs/number/NumberInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/number/NumberInput.stories.js +624 -10
- package/dist/components/formField/inputs/number/NumberInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts +74 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js +673 -44
- package/dist/components/formField/inputs/radio/RadioButtonInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts +119 -1
- package/dist/components/formField/inputs/text/TextInput.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/text/TextInput.stories.js +549 -10
- package/dist/components/formField/inputs/text/TextInput.stories.js.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts +129 -4
- package/dist/components/formField/inputs/textArea/TextArea.stories.d.ts.map +1 -1
- package/dist/components/formField/inputs/textArea/TextArea.stories.js +577 -3
- package/dist/components/formField/inputs/textArea/TextArea.stories.js.map +1 -1
- package/dist/components/formField/inputs/time/TimeInput.d.ts +1 -1
- package/dist/components/formField/inputs/time/TimeInput.stories.d.ts +1 -1
- package/dist/components/heading/Heading.stories.d.ts +449 -50
- package/dist/components/heading/Heading.stories.d.ts.map +1 -1
- package/dist/components/heading/Heading.stories.js +536 -60
- package/dist/components/heading/Heading.stories.js.map +1 -1
- package/dist/components/icoText/IcoText.d.ts +37 -0
- package/dist/components/icoText/IcoText.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.js +29 -0
- package/dist/components/icoText/IcoText.js.map +1 -0
- package/dist/components/icoText/IcoText.stories.d.ts +34 -0
- package/dist/components/icoText/IcoText.stories.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.stories.js +24 -0
- package/dist/components/icoText/IcoText.stories.js.map +1 -0
- package/dist/components/icoText/IcoText.test.d.ts +2 -0
- package/dist/components/icoText/IcoText.test.d.ts.map +1 -0
- package/dist/components/icoText/IcoText.test.js +27 -0
- package/dist/components/icoText/IcoText.test.js.map +1 -0
- package/dist/components/icon/Icon.stories.d.ts +81 -10
- package/dist/components/icon/Icon.stories.d.ts.map +1 -1
- package/dist/components/icon/Icon.stories.js +979 -8
- package/dist/components/icon/Icon.stories.js.map +1 -1
- package/dist/components/kpiCard/KPICard.d.ts +13 -0
- package/dist/components/kpiCard/KPICard.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.js +8 -0
- package/dist/components/kpiCard/KPICard.js.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts +9 -0
- package/dist/components/kpiCard/KPICard.stories.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.stories.js +18 -0
- package/dist/components/kpiCard/KPICard.stories.js.map +1 -0
- package/dist/components/kpiCard/KPICard.test.d.ts +2 -0
- package/dist/components/kpiCard/KPICard.test.d.ts.map +1 -0
- package/dist/components/kpiCard/KPICard.test.js +37 -0
- package/dist/components/kpiCard/KPICard.test.js.map +1 -0
- package/dist/components/kvpList/KVPList.d.ts +34 -0
- package/dist/components/kvpList/KVPList.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.js +20 -0
- package/dist/components/kvpList/KVPList.js.map +1 -0
- package/dist/components/kvpList/KVPList.stories.d.ts +27 -0
- package/dist/components/kvpList/KVPList.stories.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.stories.js +18 -0
- package/dist/components/kvpList/KVPList.stories.js.map +1 -0
- package/dist/components/kvpList/KVPList.test.d.ts +2 -0
- package/dist/components/kvpList/KVPList.test.d.ts.map +1 -0
- package/dist/components/kvpList/KVPList.test.js +29 -0
- package/dist/components/kvpList/KVPList.test.js.map +1 -0
- package/dist/components/pill/Pill.stories.d.ts +71 -19
- package/dist/components/pill/Pill.stories.d.ts.map +1 -1
- package/dist/components/pill/Pill.stories.js +573 -14
- package/dist/components/pill/Pill.stories.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +75 -298
- package/dist/components/progress/Progress.stories.d.ts.map +1 -1
- package/dist/components/progress/Progress.stories.js +449 -52
- package/dist/components/progress/Progress.stories.js.map +1 -1
- package/dist/components/separator/Separator.stories.d.ts +58 -5
- package/dist/components/separator/Separator.stories.d.ts.map +1 -1
- package/dist/components/separator/Separator.stories.js +443 -4
- package/dist/components/separator/Separator.stories.js.map +1 -1
- package/dist/components/singleUser/SingleUser.d.ts +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- package/dist/components/tag/Tag.stories.d.ts +116 -5
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +581 -28
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/index.css +194 -23
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/eslint.config.mts +5 -1
- package/package.json +3 -3
- package/src/components/articleCard/ArticleCard.stories.tsx +132 -0
- package/src/components/articleCard/ArticleCard.test.tsx +121 -0
- package/src/components/articleCard/ArticleCard.tsx +100 -0
- package/src/components/articleCard/articleCard.scss +39 -0
- package/src/components/badge/Badge.stories.tsx +869 -42
- package/src/components/banner/Banner.stories.tsx +1081 -63
- package/src/components/button/Button.stories.tsx +1394 -99
- package/src/components/card/Card.stories.tsx +35 -79
- package/src/components/card/Card.test.tsx +72 -190
- package/src/components/card/Card.tsx +117 -58
- package/src/components/card/card.scss +18 -31
- package/src/components/dot/Dot.stories.tsx +723 -32
- package/src/components/dropdown/Dropdown.stories.tsx +1174 -35
- package/src/components/formField/FormField.stories.tsx +1522 -105
- package/src/components/formField/inputs/checkbox/CheckboxInput.stories.tsx +1020 -15
- package/src/components/formField/inputs/number/NumberInput.stories.tsx +908 -15
- package/src/components/formField/inputs/radio/RadioButtonInput.stories.tsx +932 -51
- package/src/components/formField/inputs/text/TextInput.stories.tsx +773 -13
- package/src/components/formField/inputs/textArea/TextArea.stories.tsx +756 -8
- package/src/components/heading/Heading.stories.tsx +752 -120
- package/src/components/icoText/IcoText.stories.tsx +47 -0
- package/src/components/icoText/IcoText.test.tsx +41 -0
- package/src/components/icoText/IcoText.tsx +93 -0
- package/src/components/icoText/icoText.scss +34 -0
- package/src/components/icon/Icon.stories.tsx +1446 -12
- package/src/components/kpiCard/KPICard.stories.tsx +47 -0
- package/src/components/kpiCard/KPICard.test.tsx +60 -0
- package/src/components/kpiCard/KPICard.tsx +45 -0
- package/src/components/kpiCard/kpiCard.scss +35 -0
- package/src/components/kvpList/KVPList.stories.tsx +51 -0
- package/src/components/kvpList/KVPList.test.tsx +66 -0
- package/src/components/kvpList/KVPList.tsx +109 -0
- package/src/components/kvpList/kvpList.scss +64 -0
- package/src/components/pill/Pill.stories.tsx +867 -21
- package/src/components/progress/Progress.stories.tsx +625 -58
- package/src/components/separator/Separator.stories.tsx +730 -8
- package/src/components/separator/separator.scss +12 -3
- package/src/components/tag/Tag.stories.tsx +755 -53
- package/src/index.scss +4 -0
- package/src/index.ts +13 -4
- package/src/tokens.scss +6 -0
- package/tokens/json/Arbor.json +30 -0
- package/.claude/agent-memory/blanche-designspert/MEMORY.md +0 -64
- package/.claude/agent-memory/dorothy-fact-checker/MEMORY.md +0 -129
- package/.claude/agent-memory/rose-storybookspert/MEMORY.md +0 -29
- package/.claude/agent-memory/sophia-componentspert/MEMORY.md +0 -14
- package/.claude/design-assessment-daily-attendance-2026-04-10.md +0 -566
- package/.claude/figma-assessment-7154-58899.md +0 -404
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-11086-97537.md +0 -392
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-41974.md +0 -474
- package/.claude/figma-assessment-UKQfcxnT4rlHHNuiumt4o1-551-43094.md +0 -462
- package/.claude/figma-assessment-fcFK4CGzkz2fVyY3koX8ZE-7154-59061.md +0 -440
- package/.claude/migration-report-custom-report-writer-2026-02-19.md +0 -591
- /package/{.claude/agent-memory → .agent-memory}/blanche-designspert/token-review-patterns.md +0 -0
- /package/{.claude/agent-memory → .agent-memory}/rose-storybookspert/patterns.md +0 -0
- /package/{.claude → .gather}/skills/create-page/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/map-legacy/SKILL.md +0 -0
- /package/{.claude → .gather}/skills/migrate-page/SKILL.md +0 -0
|
@@ -1,30 +1,782 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Controls, Heading, Markdown, Primary, Stories, Subheading, Subtitle, Title } from '@storybook/addon-docs/blocks';
|
|
3
|
+
import { useState } from 'react';
|
|
2
4
|
import { Dropdown } from './Dropdown';
|
|
3
5
|
import { Button } from '../button/Button';
|
|
6
|
+
import { Icon } from '../icon/Icon';
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Sub-component prop tables (Storybook cannot auto-generate ArgTypes for
|
|
9
|
+
// components whose props come from external Radix UI types)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
const TRIGGER_PROPS = `
|
|
12
|
+
| Prop | Type | Default | Description |
|
|
13
|
+
|---|---|---|---|
|
|
14
|
+
| \`children\` | \`React.ReactElement\` | — | The trigger element. Must be a React element (\`<Button>\`, \`<a>\`, custom components) — \`asChild\` is applied internally. Plain text or Fragments are not supported. Also pass \`disabled\` to the child element so it gets the correct visual and ARIA state. |
|
|
15
|
+
| \`disabled\` | \`boolean\` | \`false\` | Prevents the dropdown from opening. |
|
|
16
|
+
`.trim();
|
|
17
|
+
const CONTENT_PROPS = `
|
|
18
|
+
| Prop | Type | Default | Description |
|
|
19
|
+
|---|---|---|---|
|
|
20
|
+
| \`children\` | \`React.ReactNode\` | — | The menu content — \`Dropdown.Item\`, \`Dropdown.SelectItem\`, \`Dropdown.Separator\`, and \`Dropdown.Group\` elements. |
|
|
21
|
+
| \`contentProps\` | \`DropdownMenuContentProps\` | — | Passed to the Radix \`DropdownMenu.Content\`. Key values: \`align\` (\`'start' \\| 'center' \\| 'end'\`, default \`'start'\`), \`side\` (\`'top' \\| 'right' \\| 'bottom' \\| 'left'\`, default \`'bottom'\`), \`sideOffset\` (gap in px), \`avoidCollisions\` (auto-flip when clipped, default \`true\`). |
|
|
22
|
+
| \`portalProps\` | \`DropdownMenuPortalProps\` | — | Passed to the Radix \`DropdownMenu.Portal\`. Rarely needed — content already renders via \`PopupParentContext\`. |
|
|
23
|
+
| \`className\` | \`string\` | \`''\` | Additional CSS class for the content panel. |
|
|
24
|
+
`.trim();
|
|
25
|
+
const ITEM_PROPS = `
|
|
26
|
+
| Prop | Type | Default | Description |
|
|
27
|
+
|---|---|---|---|
|
|
28
|
+
| \`children\` | \`React.ReactNode\` | — | Item label. Can include icons — they align automatically via the item's flex layout. Use \`size={16}\` on icons to match line height. |
|
|
29
|
+
| \`onSelect\` | \`(event: Event) => void\` | — | Called when the item is activated (click or keyboard). The dropdown closes after selection by default. |
|
|
30
|
+
| \`disabled\` | \`boolean\` | \`false\` | Renders at 50% opacity with \`aria-disabled="true"\` and \`pointer-events: none\`. Still visible to screen readers. |
|
|
31
|
+
| \`textValue\` | \`string\` | — | Optional string for typeahead matching. Use when \`children\` is not a plain string (e.g. it includes icons). |
|
|
32
|
+
| \`className\` | \`string\` | \`''\` | Additional CSS class. |
|
|
33
|
+
`.trim();
|
|
34
|
+
const SELECT_ITEM_PROPS = `
|
|
35
|
+
| Prop | Type | Default | Description |
|
|
36
|
+
|---|---|---|---|
|
|
37
|
+
| \`selected\` | \`boolean\` | — | **Required.** Controlled checked state. When \`true\`, a checkmark icon is shown on the right. |
|
|
38
|
+
| \`onSelectChange\` | \`(selected: boolean) => void\` | — | **Required.** Called when the item is toggled. Receives the new checked state. |
|
|
39
|
+
| \`closeAfterSelection\` | \`boolean\` | \`true\` | When \`false\`, the dropdown stays open after selection — essential for multi-select menus. Pass \`false\` on every \`SelectItem\` in a multi-select menu. |
|
|
40
|
+
| \`disabled\` | \`boolean\` | \`false\` | Renders the item disabled. |
|
|
41
|
+
| \`children\` | \`React.ReactNode\` | — | Item label. |
|
|
42
|
+
| \`textValue\` | \`string\` | — | Optional string for typeahead matching. |
|
|
43
|
+
`.trim();
|
|
44
|
+
const GROUP_PROPS = `
|
|
45
|
+
| Prop | Type | Default | Description |
|
|
46
|
+
|---|---|---|---|
|
|
47
|
+
| \`children\` | \`React.ReactNode\` | — | The items in this group. Renders with \`role="group"\` for screen reader announcements. Combine with \`Dropdown.Separator\` for a visible divider. |
|
|
48
|
+
| \`className\` | \`string\` | \`''\` | Additional CSS class. |
|
|
49
|
+
`.trim();
|
|
50
|
+
const SEPARATOR_PROPS = `
|
|
51
|
+
| Prop | Type | Default | Description |
|
|
52
|
+
|---|---|---|---|
|
|
53
|
+
| \`className\` | \`string\` | \`''\` | Additional CSS class. The separator renders as a zero-config horizontal rule with no configurable content. |
|
|
54
|
+
`.trim();
|
|
55
|
+
const DESCRIPTION_INTRO = [
|
|
56
|
+
'A **non-blocking popup menu** built on [Radix UI\'s DropdownMenu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) primitive.',
|
|
57
|
+
'`modal={false}` is hardcoded — the background is never blocked, letting users continue interacting with the rest of the page while the menu is open.',
|
|
58
|
+
].join('\n');
|
|
59
|
+
const PROPS_INTRO = 'The preview below is wired to the **Controls** panel — tweak any prop to see the story update in real time.';
|
|
60
|
+
const USAGE_GUIDANCE = [
|
|
61
|
+
'## When to use',
|
|
62
|
+
'',
|
|
63
|
+
'- **Quick contextual actions** — a row of records in a table that each need an "Actions" menu (edit, archive, delete)',
|
|
64
|
+
'- **Filter panels** — letting a teacher select which year groups to display in an attendance report without losing their place',
|
|
65
|
+
'- **Column visibility toggles** — showing/hiding columns in a data grid, e.g. the custom column menu in Arbor\'s assessment tracker',
|
|
66
|
+
'- **Date-range pickers** — anchored to a filter chip so the picker appears near the trigger (see the attendance date filter)',
|
|
67
|
+
'- **Multi-select checkboxes** — choosing which guardian contacts to include in a bulk message without navigating away',
|
|
68
|
+
'',
|
|
69
|
+
'## When NOT to use',
|
|
70
|
+
'',
|
|
71
|
+
'| Instead of Dropdown... | Use... | Why |',
|
|
72
|
+
'|---|---|---|',
|
|
73
|
+
'| Data-driven option lists | `SelectDropdown` | Handles fetch state, search, and controlled value binding |',
|
|
74
|
+
'| Colour picking | `ColourPickerDropdown` | Specialised colour grid UI |',
|
|
75
|
+
'| Workflows needing focus lock | `Modal` | Blocking, centred, keyboard-trapped dialog |',
|
|
76
|
+
'| Side-panel editing | `Slideover` | Slides from the right edge, larger content area |',
|
|
77
|
+
'',
|
|
78
|
+
'## Compound component API',
|
|
79
|
+
'',
|
|
80
|
+
'```tsx',
|
|
81
|
+
'<Dropdown>',
|
|
82
|
+
' <Dropdown.Trigger>',
|
|
83
|
+
' <Button variant="dropdown">Actions</Button>',
|
|
84
|
+
' </Dropdown.Trigger>',
|
|
85
|
+
' <Dropdown.Content>',
|
|
86
|
+
' <Dropdown.Item onSelect={fn}>View profile</Dropdown.Item>',
|
|
87
|
+
' <Dropdown.Separator />',
|
|
88
|
+
' <Dropdown.Group>',
|
|
89
|
+
' <Dropdown.SelectItem selected={checked} onSelectChange={setChecked}>',
|
|
90
|
+
' Year 7',
|
|
91
|
+
' </Dropdown.SelectItem>',
|
|
92
|
+
' </Dropdown.Group>',
|
|
93
|
+
' </Dropdown.Content>',
|
|
94
|
+
'</Dropdown>',
|
|
95
|
+
'```',
|
|
96
|
+
].join('\n');
|
|
97
|
+
const DEVELOPER_NOTES = [
|
|
98
|
+
'## Critical usage notes',
|
|
99
|
+
'',
|
|
100
|
+
'- **`Dropdown.Trigger` uses `asChild`** — the child element *becomes* the trigger. It must be a',
|
|
101
|
+
' `React.ReactElement` (not a string, not a Fragment). `<Button>`, `<a>`, custom components — all fine.',
|
|
102
|
+
' Plain text — not fine.',
|
|
103
|
+
'- **`closeAfterSelection` defaults to `true`** on `Dropdown.SelectItem`. For multi-select patterns where',
|
|
104
|
+
' the dropdown should stay open, pass `closeAfterSelection={false}` on **every** `SelectItem`.',
|
|
105
|
+
'- **Content portals** — `Dropdown.Content` renders outside the normal DOM flow via `PopupParentContext`.',
|
|
106
|
+
' Never use `overflow: hidden` or `position: relative` on story wrappers.',
|
|
107
|
+
'- **Controlled vs uncontrolled** — pass `open` + `onOpenChange` to control state externally, or omit both',
|
|
108
|
+
' to let Radix manage it internally.',
|
|
109
|
+
'',
|
|
110
|
+
'> **Built on [Radix UI DropdownMenu](https://www.radix-ui.com/primitives/docs/components/dropdown-menu).** Sub-component',
|
|
111
|
+
'> props like `contentProps` and `portalProps` are passed directly to the underlying Radix primitives — see the',
|
|
112
|
+
'> [Radix DropdownMenu docs](https://www.radix-ui.com/primitives/docs/components/dropdown-menu) for the full API',
|
|
113
|
+
'> (alignment, collision detection, animation callbacks, etc.).',
|
|
114
|
+
'',
|
|
115
|
+
'## Accessibility',
|
|
116
|
+
'',
|
|
117
|
+
'- **Keyboard navigation**: `ArrowDown` / `ArrowUp` moves between items; `Enter` / `Space` activates the',
|
|
118
|
+
' focused item; `Escape` closes; typeahead jumps to the first item matching the typed character.',
|
|
119
|
+
'- **ARIA roles**: the menu panel has `role="menu"`; regular items have `role="menuitem"`; select items',
|
|
120
|
+
' have `role="menuitemcheckbox"`.',
|
|
121
|
+
'- **Screen reader announcements**: Radix announces the checked/unchecked state of `SelectItem`',
|
|
122
|
+
' automatically via `aria-checked`.',
|
|
123
|
+
'- **Trigger**: Radix sets `aria-expanded` on the trigger automatically.',
|
|
124
|
+
'- **Disabled items**: rendered with `aria-disabled="true"` and `pointer-events: none` — still readable by',
|
|
125
|
+
' screen readers but cannot be activated.',
|
|
126
|
+
].join('\n');
|
|
127
|
+
const RELATED_COMPONENTS = [
|
|
128
|
+
'## Related components',
|
|
129
|
+
'',
|
|
130
|
+
'[SelectDropdown](?path=/docs/components-formfield-inputs-selectdropdown--docs) · [ColourPickerDropdown](?path=/docs/components-formfield-inputs-colourpickerdropdown--docs) · [Modal](?path=/docs/components-modals-modal--docs) · [SlideoverManager](?path=/docs/components-slideovermanager--docs) · [Button](?path=/docs/components-button--docs)',
|
|
131
|
+
].join('\n');
|
|
132
|
+
function DropdownDocsPage() {
|
|
133
|
+
return (_jsxs(_Fragment, { children: [_jsx(Title, {}), _jsx(Subtitle, {}), _jsx(Markdown, { children: DESCRIPTION_INTRO }), _jsx(Heading, { children: "Interactive example" }), _jsx(Markdown, { children: PROPS_INTRO }), _jsx(Primary, {}), _jsx(Heading, { children: "Props reference" }), _jsx(Subheading, { children: "Root props (Dropdown)" }), _jsx(Controls, {}), _jsx(Subheading, { children: "Dropdown.Trigger props" }), _jsx(Markdown, { children: TRIGGER_PROPS }), _jsx(Subheading, { children: "Dropdown.Content props" }), _jsx(Markdown, { children: CONTENT_PROPS }), _jsx(Subheading, { children: "Dropdown.Item props" }), _jsx(Markdown, { children: ITEM_PROPS }), _jsx(Subheading, { children: "Dropdown.SelectItem props" }), _jsx(Markdown, { children: SELECT_ITEM_PROPS }), _jsx(Subheading, { children: "Dropdown.Group props" }), _jsx(Markdown, { children: GROUP_PROPS }), _jsx(Subheading, { children: "Dropdown.Separator props" }), _jsx(Markdown, { children: SEPARATOR_PROPS }), _jsx(Heading, { children: "Usage guidance" }), _jsx(Markdown, { children: USAGE_GUIDANCE }), _jsx(Heading, { children: "Developer notes" }), _jsx(Markdown, { children: DEVELOPER_NOTES }), _jsx(Heading, { children: "Examples" }), _jsx(Stories, { title: "" }), _jsx(Markdown, { children: RELATED_COMPONENTS })] }));
|
|
134
|
+
}
|
|
4
135
|
const meta = {
|
|
5
136
|
title: 'Components/Dropdown',
|
|
6
137
|
component: Dropdown,
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
138
|
+
tags: ['autodocs'],
|
|
139
|
+
parameters: {
|
|
140
|
+
layout: 'centered',
|
|
141
|
+
docs: {
|
|
142
|
+
page: DropdownDocsPage,
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
argTypes: {
|
|
146
|
+
open: {
|
|
147
|
+
control: 'boolean',
|
|
148
|
+
description: 'Controlled open state. Pair with `onOpenChange` to drive the dropdown externally. Omit for uncontrolled usage.',
|
|
149
|
+
table: {
|
|
150
|
+
type: { summary: 'boolean' },
|
|
151
|
+
defaultValue: { summary: 'undefined' },
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
defaultOpen: {
|
|
155
|
+
control: 'boolean',
|
|
156
|
+
description: 'Initial open state for uncontrolled mode. Do not combine with `open`.',
|
|
157
|
+
table: {
|
|
158
|
+
type: { summary: 'boolean' },
|
|
159
|
+
defaultValue: { summary: 'false' },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
onOpenChange: {
|
|
163
|
+
description: 'Callback fired when the open state changes. Receives the new boolean state.',
|
|
164
|
+
table: {
|
|
165
|
+
type: { summary: '(open: boolean) => void' },
|
|
166
|
+
},
|
|
167
|
+
},
|
|
168
|
+
dir: {
|
|
169
|
+
control: 'select',
|
|
170
|
+
options: ['ltr', 'rtl'],
|
|
171
|
+
description: 'Text reading direction. Inherits from the document direction if omitted.',
|
|
172
|
+
table: {
|
|
173
|
+
type: { summary: "'ltr' | 'rtl'" },
|
|
174
|
+
defaultValue: { summary: 'ltr' },
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
children: {
|
|
178
|
+
description: 'The compound component tree. Must include `Dropdown.Trigger` and `Dropdown.Content`. Sub-component props (`Dropdown.Item`, `Dropdown.SelectItem`, etc.) are documented in the individual stories below.',
|
|
179
|
+
control: false,
|
|
180
|
+
table: {
|
|
181
|
+
type: { summary: 'ReactNode' },
|
|
182
|
+
},
|
|
183
|
+
},
|
|
13
184
|
},
|
|
14
185
|
};
|
|
15
|
-
export
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
186
|
+
export default meta;
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
// Helper: attach a per-story description to docs
|
|
189
|
+
// ---------------------------------------------------------------------------
|
|
190
|
+
const withDescription = (story, description) => ({
|
|
191
|
+
...story,
|
|
192
|
+
parameters: {
|
|
193
|
+
...story.parameters,
|
|
194
|
+
docs: { ...story.parameters?.docs, description: { story: description } },
|
|
20
195
|
},
|
|
196
|
+
});
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Stateful template components (named components avoid hooks-in-callbacks lint issues)
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
const ControlledOpenTemplate = () => {
|
|
201
|
+
const [open, setOpen] = useState(false);
|
|
202
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)', padding: 'var(--spacing-xlarge)' }, children: [_jsxs("p", { style: { margin: 0, fontSize: '0.875rem', color: 'var(--color-grey-600)' }, children: ["Dropdown is currently:", ' ', _jsx("strong", { children: open ? 'open' : 'closed' })] }), _jsxs(Dropdown, { open: open, onOpenChange: setOpen, children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "primary", children: "Controlled trigger" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => setOpen(false), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => setOpen(false), children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => setOpen(false), children: "Send message to parents" })] })] })] }));
|
|
203
|
+
};
|
|
204
|
+
const WithSelectItemsTemplate = () => {
|
|
205
|
+
const [viewSelected, setViewSelected] = useState(false);
|
|
206
|
+
const [editSelected, setEditSelected] = useState(true);
|
|
207
|
+
const [exportSelected, setExportSelected] = useState(false);
|
|
208
|
+
return (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "dropdown", children: "Column options" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.SelectItem, { selected: viewSelected, onSelectChange: setViewSelected, children: "View student profile" }), _jsx(Dropdown.SelectItem, { selected: editSelected, onSelectChange: setEditSelected, children: "Edit assessment details" }), _jsx(Dropdown.SelectItem, { selected: exportSelected, onSelectChange: setExportSelected, children: "Export to CSV" })] })] }) }));
|
|
209
|
+
};
|
|
210
|
+
const MultiSelectPersistentTemplate = () => {
|
|
211
|
+
const [year7, setYear7] = useState(true);
|
|
212
|
+
const [year8, setYear8] = useState(false);
|
|
213
|
+
const [year9, setYear9] = useState(true);
|
|
214
|
+
const [year10, setYear10] = useState(false);
|
|
215
|
+
const [year11, setYear11] = useState(false);
|
|
216
|
+
const selectedCount = [year7, year8, year9, year10, year11].filter(Boolean).length;
|
|
217
|
+
return (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsxs(Button, { variant: "dropdown", children: ["Year groups", selectedCount > 0 ? ` (${selectedCount})` : ''] }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.SelectItem, { selected: year7, onSelectChange: setYear7, closeAfterSelection: false, children: "Year 7" }), _jsx(Dropdown.SelectItem, { selected: year8, onSelectChange: setYear8, closeAfterSelection: false, children: "Year 8" }), _jsx(Dropdown.SelectItem, { selected: year9, onSelectChange: setYear9, closeAfterSelection: false, children: "Year 9" }), _jsx(Dropdown.SelectItem, { selected: year10, onSelectChange: setYear10, closeAfterSelection: false, children: "Year 10" }), _jsx(Dropdown.SelectItem, { selected: year11, onSelectChange: setYear11, closeAfterSelection: false, children: "Year 11" })] })] }) }));
|
|
21
218
|
};
|
|
22
|
-
|
|
219
|
+
const MixedItemTypesTemplate = () => {
|
|
220
|
+
const [csvSelected, setCsvSelected] = useState(false);
|
|
221
|
+
const [pdfSelected, setPdfSelected] = useState(true);
|
|
222
|
+
return (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Record options" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.SelectItem, { selected: csvSelected, onSelectChange: setCsvSelected, closeAfterSelection: false, children: "Export to CSV" }), _jsx(Dropdown.SelectItem, { selected: pdfSelected, onSelectChange: setPdfSelected, closeAfterSelection: false, children: "Export to PDF" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" })] })] }) }));
|
|
223
|
+
};
|
|
224
|
+
const SchoolContextExampleTemplate = () => {
|
|
225
|
+
const [year7, setYear7] = useState(true);
|
|
226
|
+
const [year8, setYear8] = useState(true);
|
|
227
|
+
const [year9, setYear9] = useState(false);
|
|
228
|
+
const [year10, setYear10] = useState(false);
|
|
229
|
+
const [year11, setYear11] = useState(false);
|
|
230
|
+
const [allYears, setAllYears] = useState(false);
|
|
231
|
+
const handleAllYears = (checked) => {
|
|
232
|
+
setAllYears(checked);
|
|
233
|
+
if (checked) {
|
|
234
|
+
setYear7(true);
|
|
235
|
+
setYear8(true);
|
|
236
|
+
setYear9(true);
|
|
237
|
+
setYear10(true);
|
|
238
|
+
setYear11(true);
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
const selectedYears = [
|
|
242
|
+
year7 && 'Year 7',
|
|
243
|
+
year8 && 'Year 8',
|
|
244
|
+
year9 && 'Year 9',
|
|
245
|
+
year10 && 'Year 10',
|
|
246
|
+
year11 && 'Year 11',
|
|
247
|
+
].filter(Boolean);
|
|
248
|
+
const label = allYears
|
|
249
|
+
? 'All year groups'
|
|
250
|
+
: selectedYears.length === 0
|
|
251
|
+
? 'Select year groups'
|
|
252
|
+
: selectedYears.join(', ');
|
|
253
|
+
return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 'var(--spacing-large)', padding: 'var(--spacing-xlarge)', minWidth: '280px' }, children: [_jsx("p", { className: "ds-text", style: { margin: 0 }, children: "Attendance report filter" }), _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "dropdown", children: label }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.SelectItem, { selected: allYears, onSelectChange: handleAllYears, closeAfterSelection: false, children: "All year groups" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.SelectItem, { selected: year7, onSelectChange: setYear7, closeAfterSelection: false, children: "Year 7" }), _jsx(Dropdown.SelectItem, { selected: year8, onSelectChange: setYear8, closeAfterSelection: false, children: "Year 8" }), _jsx(Dropdown.SelectItem, { selected: year9, onSelectChange: setYear9, closeAfterSelection: false, children: "Year 9" }), _jsx(Dropdown.SelectItem, { selected: year10, onSelectChange: setYear10, closeAfterSelection: false, children: "Year 10" }), _jsx(Dropdown.SelectItem, { selected: year11, onSelectChange: setYear11, closeAfterSelection: false, children: "Year 11" })] })] })] }));
|
|
254
|
+
};
|
|
255
|
+
// ---------------------------------------------------------------------------
|
|
256
|
+
// Stories
|
|
257
|
+
// ---------------------------------------------------------------------------
|
|
258
|
+
export const Default = withDescription({
|
|
23
259
|
args: {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
],
|
|
260
|
+
defaultOpen: false,
|
|
261
|
+
dir: 'ltr',
|
|
27
262
|
},
|
|
28
|
-
}
|
|
29
|
-
|
|
263
|
+
render: args => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { ...args, children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "primary", children: "Open menu" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => { }, children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => { }, children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => { }, children: "Archive record" })] })] }) })),
|
|
264
|
+
}, 'The simplest possible Dropdown — a trigger button with action items. Click the button to open, click outside or press **Escape** to close. Notice how the page behind remains fully interactive — this is the `modal={false}` behaviour that distinguishes Dropdown from a blocking Modal. Use the **Controls** panel above to toggle `defaultOpen` or change the text direction.');
|
|
265
|
+
export const WithItems = withDescription({
|
|
266
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Student actions" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => console.log('message'), children: "Send message to parents" }), _jsx(Dropdown.Item, { onSelect: () => console.log('export'), children: "Export to CSV" })] })] }) })),
|
|
267
|
+
parameters: {
|
|
268
|
+
docs: {
|
|
269
|
+
source: {
|
|
270
|
+
language: 'tsx',
|
|
271
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
272
|
+
|
|
273
|
+
function StudentActionsDropdown() {
|
|
274
|
+
return (
|
|
275
|
+
<Dropdown>
|
|
276
|
+
<Dropdown.Trigger>
|
|
277
|
+
<Button variant="secondary">Student actions</Button>
|
|
278
|
+
</Dropdown.Trigger>
|
|
279
|
+
<Dropdown.Content>
|
|
280
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
281
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>Edit assessment details</Dropdown.Item>
|
|
282
|
+
<Dropdown.Item onSelect={() => console.log('message')}>Send message to parents</Dropdown.Item>
|
|
283
|
+
<Dropdown.Item onSelect={() => console.log('export')}>Export to CSV</Dropdown.Item>
|
|
284
|
+
</Dropdown.Content>
|
|
285
|
+
</Dropdown>
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
export default StudentActionsDropdown;`,
|
|
290
|
+
},
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
}, 'Standard action menu using `Dropdown.Item`. Each item fires its `onSelect` callback and closes the dropdown automatically. Use `Dropdown.Item` when the action is a one-shot command (navigate, trigger a workflow) rather than a toggle. Items support keyboard navigation — use `ArrowDown` / `ArrowUp` to move between them.');
|
|
294
|
+
export const WithDisabledItems = withDescription({
|
|
295
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Student actions" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { disabled: true, onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Item, { disabled: true, onSelect: () => console.log('message'), children: "Send message to parents" }), _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" })] })] }) })),
|
|
296
|
+
parameters: {
|
|
297
|
+
docs: {
|
|
298
|
+
source: {
|
|
299
|
+
language: 'tsx',
|
|
300
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
301
|
+
|
|
302
|
+
function DisabledItemsDropdown() {
|
|
303
|
+
return (
|
|
304
|
+
<Dropdown>
|
|
305
|
+
<Dropdown.Trigger>
|
|
306
|
+
<Button variant="secondary">Student actions</Button>
|
|
307
|
+
</Dropdown.Trigger>
|
|
308
|
+
<Dropdown.Content>
|
|
309
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
310
|
+
<Dropdown.Item disabled onSelect={() => console.log('edit')}>
|
|
311
|
+
Edit assessment details
|
|
312
|
+
</Dropdown.Item>
|
|
313
|
+
<Dropdown.Item disabled onSelect={() => console.log('message')}>
|
|
314
|
+
Send message to parents
|
|
315
|
+
</Dropdown.Item>
|
|
316
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
317
|
+
</Dropdown.Content>
|
|
318
|
+
</Dropdown>
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export default DisabledItemsDropdown;`,
|
|
323
|
+
},
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
}, 'Items can be individually disabled with the `disabled` prop. Disabled items render at 50% opacity with `aria-disabled="true"` and `pointer-events: none` — they appear in the menu so users understand which actions exist, but cannot be activated. Use this when an action is contextually unavailable (e.g. "Edit assessment" disabled for locked marking periods).');
|
|
327
|
+
export const ControlledOpen = withDescription({
|
|
328
|
+
render: ControlledOpenTemplate,
|
|
329
|
+
parameters: {
|
|
330
|
+
docs: {
|
|
331
|
+
source: {
|
|
332
|
+
language: 'tsx',
|
|
333
|
+
code: `import { useState } from 'react';
|
|
334
|
+
import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
335
|
+
|
|
336
|
+
function ControlledDropdown() {
|
|
337
|
+
const [open, setOpen] = useState(false);
|
|
338
|
+
|
|
339
|
+
return (
|
|
340
|
+
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 'var(--spacing-large)' }}>
|
|
341
|
+
<p style={{ margin: 0 }}>
|
|
342
|
+
Dropdown is currently: <strong>{open ? 'open' : 'closed'}</strong>
|
|
343
|
+
</p>
|
|
344
|
+
<Dropdown open={open} onOpenChange={setOpen}>
|
|
345
|
+
<Dropdown.Trigger>
|
|
346
|
+
<Button variant="primary">Controlled trigger</Button>
|
|
347
|
+
</Dropdown.Trigger>
|
|
348
|
+
<Dropdown.Content>
|
|
349
|
+
<Dropdown.Item onSelect={() => setOpen(false)}>View student profile</Dropdown.Item>
|
|
350
|
+
<Dropdown.Item onSelect={() => setOpen(false)}>Edit assessment details</Dropdown.Item>
|
|
351
|
+
<Dropdown.Item onSelect={() => setOpen(false)}>Send message to parents</Dropdown.Item>
|
|
352
|
+
</Dropdown.Content>
|
|
353
|
+
</Dropdown>
|
|
354
|
+
</div>
|
|
355
|
+
);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export default ControlledDropdown;`,
|
|
359
|
+
},
|
|
360
|
+
},
|
|
361
|
+
},
|
|
362
|
+
}, "Pass `open` and `onOpenChange` to control the dropdown's open state from outside. The indicator above the button reflects the current state in real time. Use controlled mode when you need to programmatically open or close the menu (e.g. opening it in response to a keyboard shortcut, or closing it after an async action completes).");
|
|
363
|
+
export const WithSelectItems = withDescription({
|
|
364
|
+
render: WithSelectItemsTemplate,
|
|
365
|
+
parameters: {
|
|
366
|
+
docs: {
|
|
367
|
+
source: {
|
|
368
|
+
language: 'tsx',
|
|
369
|
+
code: `import { useState } from 'react';
|
|
370
|
+
import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
371
|
+
|
|
372
|
+
function ColumnOptionsDropdown() {
|
|
373
|
+
const [viewSelected, setViewSelected] = useState(false);
|
|
374
|
+
const [editSelected, setEditSelected] = useState(true);
|
|
375
|
+
const [exportSelected, setExportSelected] = useState(false);
|
|
376
|
+
|
|
377
|
+
return (
|
|
378
|
+
<Dropdown>
|
|
379
|
+
<Dropdown.Trigger>
|
|
380
|
+
<Button variant="dropdown">Column options</Button>
|
|
381
|
+
</Dropdown.Trigger>
|
|
382
|
+
<Dropdown.Content>
|
|
383
|
+
<Dropdown.SelectItem selected={viewSelected} onSelectChange={setViewSelected}>
|
|
384
|
+
View student profile
|
|
385
|
+
</Dropdown.SelectItem>
|
|
386
|
+
<Dropdown.SelectItem selected={editSelected} onSelectChange={setEditSelected}>
|
|
387
|
+
Edit assessment details
|
|
388
|
+
</Dropdown.SelectItem>
|
|
389
|
+
<Dropdown.SelectItem selected={exportSelected} onSelectChange={setExportSelected}>
|
|
390
|
+
Export to CSV
|
|
391
|
+
</Dropdown.SelectItem>
|
|
392
|
+
</Dropdown.Content>
|
|
393
|
+
</Dropdown>
|
|
394
|
+
);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export default ColumnOptionsDropdown;`,
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
}, 'Use `Dropdown.SelectItem` when each item is a toggle rather than a one-shot action. Selected items display a checkmark icon on the right. By default `closeAfterSelection={true}`, so clicking an item checks it **and** closes the dropdown — ideal for single-selection patterns. To keep the dropdown open for multi-select, see the **MultiSelectPersistent** story.');
|
|
402
|
+
export const MultiSelectPersistent = withDescription({
|
|
403
|
+
render: MultiSelectPersistentTemplate,
|
|
404
|
+
parameters: {
|
|
405
|
+
docs: {
|
|
406
|
+
source: {
|
|
407
|
+
language: 'tsx',
|
|
408
|
+
code: `import { useState } from 'react';
|
|
409
|
+
import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
410
|
+
|
|
411
|
+
function YearGroupFilter() {
|
|
412
|
+
const [year7, setYear7] = useState(true);
|
|
413
|
+
const [year8, setYear8] = useState(false);
|
|
414
|
+
const [year9, setYear9] = useState(true);
|
|
415
|
+
const [year10, setYear10] = useState(false);
|
|
416
|
+
const [year11, setYear11] = useState(false);
|
|
417
|
+
|
|
418
|
+
const selectedCount = [year7, year8, year9, year10, year11].filter(Boolean).length;
|
|
419
|
+
|
|
420
|
+
return (
|
|
421
|
+
<Dropdown>
|
|
422
|
+
<Dropdown.Trigger>
|
|
423
|
+
<Button variant="dropdown">
|
|
424
|
+
Year groups{selectedCount > 0 ? \` (\${selectedCount})\` : ''}
|
|
425
|
+
</Button>
|
|
426
|
+
</Dropdown.Trigger>
|
|
427
|
+
<Dropdown.Content>
|
|
428
|
+
{/* closeAfterSelection={false} keeps the menu open for multi-select */}
|
|
429
|
+
<Dropdown.SelectItem selected={year7} onSelectChange={setYear7} closeAfterSelection={false}>Year 7</Dropdown.SelectItem>
|
|
430
|
+
<Dropdown.SelectItem selected={year8} onSelectChange={setYear8} closeAfterSelection={false}>Year 8</Dropdown.SelectItem>
|
|
431
|
+
<Dropdown.SelectItem selected={year9} onSelectChange={setYear9} closeAfterSelection={false}>Year 9</Dropdown.SelectItem>
|
|
432
|
+
<Dropdown.SelectItem selected={year10} onSelectChange={setYear10} closeAfterSelection={false}>Year 10</Dropdown.SelectItem>
|
|
433
|
+
<Dropdown.SelectItem selected={year11} onSelectChange={setYear11} closeAfterSelection={false}>Year 11</Dropdown.SelectItem>
|
|
434
|
+
</Dropdown.Content>
|
|
435
|
+
</Dropdown>
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export default YearGroupFilter;`,
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
}, "For multi-select filters, pass `closeAfterSelection={false}` on **every** `Dropdown.SelectItem` to keep the menu open between selections. The button label updates to show how many year groups are selected. This pattern is common on Arbor's attendance and progress reports where teachers want to filter by multiple year groups at once.");
|
|
444
|
+
export const WithGroups = withDescription({
|
|
445
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Actions" }) }), _jsxs(Dropdown.Content, { children: [_jsxs(Dropdown.Group, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('message'), children: "Send message to parents" }), _jsx(Dropdown.Item, { onSelect: () => console.log('email'), children: "Send email to guardian" })] }), _jsxs(Dropdown.Group, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('export'), children: "Export to CSV" }), _jsx(Dropdown.Item, { onSelect: () => console.log('report'), children: "Generate report" })] }), _jsx(Dropdown.Group, { children: _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" }) })] })] }) })),
|
|
446
|
+
parameters: {
|
|
447
|
+
docs: {
|
|
448
|
+
source: {
|
|
449
|
+
language: 'tsx',
|
|
450
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
451
|
+
|
|
452
|
+
function GroupedActionsDropdown() {
|
|
453
|
+
return (
|
|
454
|
+
<Dropdown>
|
|
455
|
+
<Dropdown.Trigger>
|
|
456
|
+
<Button variant="secondary">Actions</Button>
|
|
457
|
+
</Dropdown.Trigger>
|
|
458
|
+
<Dropdown.Content>
|
|
459
|
+
<Dropdown.Group>
|
|
460
|
+
<Dropdown.Item onSelect={() => console.log('message')}>Send message to parents</Dropdown.Item>
|
|
461
|
+
<Dropdown.Item onSelect={() => console.log('email')}>Send email to guardian</Dropdown.Item>
|
|
462
|
+
</Dropdown.Group>
|
|
463
|
+
<Dropdown.Group>
|
|
464
|
+
<Dropdown.Item onSelect={() => console.log('export')}>Export to CSV</Dropdown.Item>
|
|
465
|
+
<Dropdown.Item onSelect={() => console.log('report')}>Generate report</Dropdown.Item>
|
|
466
|
+
</Dropdown.Group>
|
|
467
|
+
<Dropdown.Group>
|
|
468
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
469
|
+
</Dropdown.Group>
|
|
470
|
+
</Dropdown.Content>
|
|
471
|
+
</Dropdown>
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
export default GroupedActionsDropdown;`,
|
|
476
|
+
},
|
|
477
|
+
},
|
|
478
|
+
},
|
|
479
|
+
}, '`Dropdown.Group` provides semantic grouping via `role="group"` — screen readers can announce group boundaries. No visual divider is rendered automatically; combine with `Dropdown.Separator` if you want visible separation (see **WithGroupsAndSeparators**). Use groups to organise related actions: Communication / Data & Reporting / Destructive actions.');
|
|
480
|
+
export const WithGroupsAndSeparators = withDescription({
|
|
481
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Actions" }) }), _jsxs(Dropdown.Content, { children: [_jsxs(Dropdown.Group, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('message'), children: "Send message to parents" }), _jsx(Dropdown.Item, { onSelect: () => console.log('email'), children: "Send email to guardian" })] }), _jsx(Dropdown.Separator, {}), _jsxs(Dropdown.Group, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('export'), children: "Export to CSV" }), _jsx(Dropdown.Item, { onSelect: () => console.log('report'), children: "Generate report" })] }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.Group, { children: _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" }) })] })] }) })),
|
|
482
|
+
parameters: {
|
|
483
|
+
docs: {
|
|
484
|
+
source: {
|
|
485
|
+
language: 'tsx',
|
|
486
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
487
|
+
|
|
488
|
+
function GroupsWithSeparatorsDropdown() {
|
|
489
|
+
return (
|
|
490
|
+
<Dropdown>
|
|
491
|
+
<Dropdown.Trigger>
|
|
492
|
+
<Button variant="secondary">Actions</Button>
|
|
493
|
+
</Dropdown.Trigger>
|
|
494
|
+
<Dropdown.Content>
|
|
495
|
+
<Dropdown.Group>
|
|
496
|
+
<Dropdown.Item onSelect={() => console.log('message')}>Send message to parents</Dropdown.Item>
|
|
497
|
+
<Dropdown.Item onSelect={() => console.log('email')}>Send email to guardian</Dropdown.Item>
|
|
498
|
+
</Dropdown.Group>
|
|
499
|
+
<Dropdown.Separator />
|
|
500
|
+
<Dropdown.Group>
|
|
501
|
+
<Dropdown.Item onSelect={() => console.log('export')}>Export to CSV</Dropdown.Item>
|
|
502
|
+
<Dropdown.Item onSelect={() => console.log('report')}>Generate report</Dropdown.Item>
|
|
503
|
+
</Dropdown.Group>
|
|
504
|
+
<Dropdown.Separator />
|
|
505
|
+
<Dropdown.Group>
|
|
506
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
507
|
+
</Dropdown.Group>
|
|
508
|
+
</Dropdown.Content>
|
|
509
|
+
</Dropdown>
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export default GroupsWithSeparatorsDropdown;`,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
},
|
|
517
|
+
}, "Combine `Dropdown.Group` (semantic) with `Dropdown.Separator` (visual) for the clearest information hierarchy. The separator is a zero-config horizontal rule — just drop it between groups. This pattern maps directly to Arbor's \"Communication / Data & Reporting / Actions\" grouping convention used on student and staff record pages.");
|
|
518
|
+
export const MixedItemTypes = withDescription({
|
|
519
|
+
render: MixedItemTypesTemplate,
|
|
520
|
+
parameters: {
|
|
521
|
+
docs: {
|
|
522
|
+
source: {
|
|
523
|
+
language: 'tsx',
|
|
524
|
+
code: `import { useState } from 'react';
|
|
525
|
+
import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
526
|
+
|
|
527
|
+
function RecordOptionsDropdown() {
|
|
528
|
+
const [csvSelected, setCsvSelected] = useState(false);
|
|
529
|
+
const [pdfSelected, setPdfSelected] = useState(true);
|
|
530
|
+
|
|
531
|
+
return (
|
|
532
|
+
<Dropdown>
|
|
533
|
+
<Dropdown.Trigger>
|
|
534
|
+
<Button variant="secondary">Record options</Button>
|
|
535
|
+
</Dropdown.Trigger>
|
|
536
|
+
<Dropdown.Content>
|
|
537
|
+
{/* One-shot actions */}
|
|
538
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
539
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>Edit assessment details</Dropdown.Item>
|
|
540
|
+
<Dropdown.Separator />
|
|
541
|
+
{/* Toggle actions — stay open for multi-select */}
|
|
542
|
+
<Dropdown.SelectItem selected={csvSelected} onSelectChange={setCsvSelected} closeAfterSelection={false}>
|
|
543
|
+
Export to CSV
|
|
544
|
+
</Dropdown.SelectItem>
|
|
545
|
+
<Dropdown.SelectItem selected={pdfSelected} onSelectChange={setPdfSelected} closeAfterSelection={false}>
|
|
546
|
+
Export to PDF
|
|
547
|
+
</Dropdown.SelectItem>
|
|
548
|
+
<Dropdown.Separator />
|
|
549
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
550
|
+
</Dropdown.Content>
|
|
551
|
+
</Dropdown>
|
|
552
|
+
);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
export default RecordOptionsDropdown;`,
|
|
556
|
+
},
|
|
557
|
+
},
|
|
558
|
+
},
|
|
559
|
+
}, 'You can mix `Dropdown.Item` (one-shot actions) and `Dropdown.SelectItem` (toggles) in the same content panel, separated by `Dropdown.Separator`. Here the top section has navigational actions, the middle has export format toggles (multi-select, stays open), and the bottom has a final action. This mirrors real Arbor pages where you might want both "go somewhere" and "configure how you export" in one menu.');
|
|
560
|
+
export const WithIcons = withDescription({
|
|
561
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", iconRightName: "3-dot", iconRightScreenReaderText: "More actions", hasHorizontalPadding: false }) }), _jsxs(Dropdown.Content, { children: [_jsxs(Dropdown.Item, { onSelect: () => console.log('view'), children: [_jsx(Icon, { name: "user", size: 16 }), "View student profile"] }), _jsxs(Dropdown.Item, { onSelect: () => console.log('edit'), children: [_jsx(Icon, { name: "pencil", size: 16 }), "Edit assessment details"] }), _jsxs(Dropdown.Item, { onSelect: () => console.log('message'), children: [_jsx(Icon, { name: "mail", size: 16 }), "Send message to parents"] }), _jsx(Dropdown.Separator, {}), _jsxs(Dropdown.Item, { onSelect: () => console.log('delete'), children: [_jsx(Icon, { name: "trash", size: 16 }), "Delete permanently"] })] })] }) })),
|
|
562
|
+
parameters: {
|
|
563
|
+
docs: {
|
|
564
|
+
source: {
|
|
565
|
+
language: 'tsx',
|
|
566
|
+
code: `import { Dropdown, Button, Icon } from '@arbor-education/design-system.components';
|
|
567
|
+
|
|
568
|
+
function IconItemsDropdown() {
|
|
569
|
+
return (
|
|
570
|
+
<Dropdown>
|
|
571
|
+
<Dropdown.Trigger>
|
|
572
|
+
<Button variant="secondary" iconRightName="3-dot" iconRightScreenReaderText="More actions" hasHorizontalPadding={false} />
|
|
573
|
+
</Dropdown.Trigger>
|
|
574
|
+
<Dropdown.Content>
|
|
575
|
+
<Dropdown.Item onSelect={() => console.log('view')}>
|
|
576
|
+
<Icon name="user" size={16} />
|
|
577
|
+
View student profile
|
|
578
|
+
</Dropdown.Item>
|
|
579
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>
|
|
580
|
+
<Icon name="pencil" size={16} />
|
|
581
|
+
Edit assessment details
|
|
582
|
+
</Dropdown.Item>
|
|
583
|
+
<Dropdown.Item onSelect={() => console.log('message')}>
|
|
584
|
+
<Icon name="mail" size={16} />
|
|
585
|
+
Send message to parents
|
|
586
|
+
</Dropdown.Item>
|
|
587
|
+
<Dropdown.Separator />
|
|
588
|
+
<Dropdown.Item onSelect={() => console.log('delete')}>
|
|
589
|
+
<Icon name="trash" size={16} />
|
|
590
|
+
Delete permanently
|
|
591
|
+
</Dropdown.Item>
|
|
592
|
+
</Dropdown.Content>
|
|
593
|
+
</Dropdown>
|
|
594
|
+
);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export default IconItemsDropdown;`,
|
|
598
|
+
},
|
|
599
|
+
},
|
|
600
|
+
},
|
|
601
|
+
}, 'Icons inside `Dropdown.Item` children align automatically via the item\'s flex layout (`gap` + `align-items: center`). Use `size={16}` to match the item\'s line height. The trigger here uses an icon-only `Button` with `iconRightName="3-dot"` — the classic "more actions" kebab pattern used throughout Arbor\'s table rows. Always provide `iconRightScreenReaderText` so screen reader users know what the button does.');
|
|
602
|
+
export const WithDestructiveAction = withDescription({
|
|
603
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Student actions" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" }), _jsx(Dropdown.Separator, {}), _jsx(Dropdown.Item, { onSelect: () => console.log('delete'), children: _jsxs("span", { style: { color: 'var(--color-semantic-destructive-600)', display: 'flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }, children: [_jsx(Icon, { name: "trash", size: 16 }), "Delete permanently"] }) })] })] }) })),
|
|
604
|
+
parameters: {
|
|
605
|
+
docs: {
|
|
606
|
+
source: {
|
|
607
|
+
language: 'tsx',
|
|
608
|
+
code: `import { Dropdown, Button, Icon } from '@arbor-education/design-system.components';
|
|
609
|
+
|
|
610
|
+
function DestructiveActionDropdown() {
|
|
611
|
+
return (
|
|
612
|
+
<Dropdown>
|
|
613
|
+
<Dropdown.Trigger>
|
|
614
|
+
<Button variant="secondary">Student actions</Button>
|
|
615
|
+
</Dropdown.Trigger>
|
|
616
|
+
<Dropdown.Content>
|
|
617
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
618
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>Edit assessment details</Dropdown.Item>
|
|
619
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
620
|
+
<Dropdown.Separator />
|
|
621
|
+
<Dropdown.Item onSelect={() => console.log('delete')}>
|
|
622
|
+
<span style={{ color: 'var(--color-semantic-destructive-600)', display: 'flex', alignItems: 'center', gap: 'var(--spacing-xsmall)' }}>
|
|
623
|
+
<Icon name="trash" size={16} />
|
|
624
|
+
Delete permanently
|
|
625
|
+
</span>
|
|
626
|
+
</Dropdown.Item>
|
|
627
|
+
</Dropdown.Content>
|
|
628
|
+
</Dropdown>
|
|
629
|
+
);
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
export default DestructiveActionDropdown;`,
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
}, 'Destructive actions (delete, revoke, expel) belong at the **bottom** of the menu, separated by a `Dropdown.Separator`, styled in the semantic destructive colour token `--color-semantic-destructive-600`. This placement and colour treatment reinforces that the action is irreversible. Consider whether a destructive Dropdown item should instead open a confirmation `Modal` before proceeding — for truly irreversible data changes, a Modal is always safer.');
|
|
637
|
+
export const AlignEnd = withDescription({
|
|
638
|
+
render: () => (_jsx("div", { style: { display: 'flex', justifyContent: 'flex-end', width: '300px', padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Right-aligned trigger" }) }), _jsxs(Dropdown.Content, { contentProps: { align: 'end' }, children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => console.log('export'), children: "Export to CSV" })] })] }) })),
|
|
639
|
+
parameters: {
|
|
640
|
+
docs: {
|
|
641
|
+
source: {
|
|
642
|
+
language: 'tsx',
|
|
643
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
644
|
+
|
|
645
|
+
function AlignEndDropdown() {
|
|
646
|
+
return (
|
|
647
|
+
<Dropdown>
|
|
648
|
+
<Dropdown.Trigger>
|
|
649
|
+
<Button variant="secondary">Right-aligned trigger</Button>
|
|
650
|
+
</Dropdown.Trigger>
|
|
651
|
+
<Dropdown.Content contentProps={{ align: 'end' }}>
|
|
652
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
653
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>Edit assessment details</Dropdown.Item>
|
|
654
|
+
<Dropdown.Item onSelect={() => console.log('export')}>Export to CSV</Dropdown.Item>
|
|
655
|
+
</Dropdown.Content>
|
|
656
|
+
</Dropdown>
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export default AlignEndDropdown;`,
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
},
|
|
664
|
+
}, "Pass `contentProps={{ align: 'end' }}` to anchor the content panel's right edge to the trigger's right edge. Use this when your trigger sits near the right side of the viewport (e.g. an \"Actions\" button in the last column of a table) so the menu doesn't overflow off-screen. Default is `align: 'start'` (left-anchored).");
|
|
665
|
+
export const OpenUpward = withDescription({
|
|
666
|
+
render: () => (_jsx("div", { style: { display: 'flex', alignItems: 'flex-end', height: '240px', padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { children: _jsx(Button, { variant: "secondary", children: "Trigger near bottom" }) }), _jsxs(Dropdown.Content, { contentProps: { side: 'top' }, children: [_jsx(Dropdown.Item, { onSelect: () => console.log('view'), children: "View student profile" }), _jsx(Dropdown.Item, { onSelect: () => console.log('edit'), children: "Edit assessment details" }), _jsx(Dropdown.Item, { onSelect: () => console.log('export'), children: "Export to CSV" }), _jsx(Dropdown.Item, { onSelect: () => console.log('archive'), children: "Archive record" })] })] }) })),
|
|
667
|
+
parameters: {
|
|
668
|
+
docs: {
|
|
669
|
+
source: {
|
|
670
|
+
language: 'tsx',
|
|
671
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
672
|
+
|
|
673
|
+
function OpenUpwardDropdown() {
|
|
674
|
+
return (
|
|
675
|
+
<Dropdown>
|
|
676
|
+
<Dropdown.Trigger>
|
|
677
|
+
<Button variant="secondary">Trigger near bottom</Button>
|
|
678
|
+
</Dropdown.Trigger>
|
|
679
|
+
<Dropdown.Content contentProps={{ side: 'top' }}>
|
|
680
|
+
<Dropdown.Item onSelect={() => console.log('view')}>View student profile</Dropdown.Item>
|
|
681
|
+
<Dropdown.Item onSelect={() => console.log('edit')}>Edit assessment details</Dropdown.Item>
|
|
682
|
+
<Dropdown.Item onSelect={() => console.log('export')}>Export to CSV</Dropdown.Item>
|
|
683
|
+
<Dropdown.Item onSelect={() => console.log('archive')}>Archive record</Dropdown.Item>
|
|
684
|
+
</Dropdown.Content>
|
|
685
|
+
</Dropdown>
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
export default OpenUpwardDropdown;`,
|
|
690
|
+
},
|
|
691
|
+
},
|
|
692
|
+
},
|
|
693
|
+
}, "Pass `contentProps={{ side: 'top' }}` to open the menu upward from the trigger. Use this when the trigger is near the bottom of the viewport and the menu would otherwise be clipped — for example, an actions button in the last row of a paginated table. Radix also supports `avoidCollisions={true}` (the default) which automatically flips the panel when there is insufficient space.");
|
|
694
|
+
export const TriggerDisabled = withDescription({
|
|
695
|
+
render: () => (_jsx("div", { style: { padding: 'var(--spacing-xlarge)' }, children: _jsxs(Dropdown, { children: [_jsx(Dropdown.Trigger, { disabled: true, children: _jsx(Button, { variant: "secondary", disabled: true, children: "Actions (unavailable)" }) }), _jsxs(Dropdown.Content, { children: [_jsx(Dropdown.Item, { children: "View student profile" }), _jsx(Dropdown.Item, { children: "Edit assessment details" })] })] }) })),
|
|
696
|
+
parameters: {
|
|
697
|
+
docs: {
|
|
698
|
+
source: {
|
|
699
|
+
language: 'tsx',
|
|
700
|
+
code: `import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
701
|
+
|
|
702
|
+
function TriggerDisabledDropdown() {
|
|
703
|
+
return (
|
|
704
|
+
<Dropdown>
|
|
705
|
+
<Dropdown.Trigger disabled>
|
|
706
|
+
<Button variant="secondary" disabled>
|
|
707
|
+
Actions (unavailable)
|
|
708
|
+
</Button>
|
|
709
|
+
</Dropdown.Trigger>
|
|
710
|
+
<Dropdown.Content>
|
|
711
|
+
<Dropdown.Item>View student profile</Dropdown.Item>
|
|
712
|
+
<Dropdown.Item>Edit assessment details</Dropdown.Item>
|
|
713
|
+
</Dropdown.Content>
|
|
714
|
+
</Dropdown>
|
|
715
|
+
);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
export default TriggerDisabledDropdown;`,
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
},
|
|
722
|
+
}, 'Pass `disabled` on `Dropdown.Trigger` to prevent the dropdown from opening at all. Because `Trigger` uses `asChild`, you should **also** pass `disabled` to the child element (the `Button` here) so it receives the correct visual and ARIA disabled state. Use this when the entire set of actions is contextually unavailable, e.g. on a read-only record.');
|
|
723
|
+
export const SchoolContextExample = withDescription({
|
|
724
|
+
render: SchoolContextExampleTemplate,
|
|
725
|
+
parameters: {
|
|
726
|
+
docs: {
|
|
727
|
+
source: {
|
|
728
|
+
language: 'tsx',
|
|
729
|
+
code: `import { useState } from 'react';
|
|
730
|
+
import { Dropdown, Button } from '@arbor-education/design-system.components';
|
|
731
|
+
|
|
732
|
+
function AttendanceReportFilter() {
|
|
733
|
+
const [year7, setYear7] = useState(true);
|
|
734
|
+
const [year8, setYear8] = useState(true);
|
|
735
|
+
const [year9, setYear9] = useState(false);
|
|
736
|
+
const [year10, setYear10] = useState(false);
|
|
737
|
+
const [year11, setYear11] = useState(false);
|
|
738
|
+
const [allYears, setAllYears] = useState(false);
|
|
739
|
+
|
|
740
|
+
const handleAllYears = (checked: boolean) => {
|
|
741
|
+
setAllYears(checked);
|
|
742
|
+
if (checked) {
|
|
743
|
+
setYear7(true); setYear8(true); setYear9(true);
|
|
744
|
+
setYear10(true); setYear11(true);
|
|
745
|
+
}
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const selectedYears = [
|
|
749
|
+
year7 && 'Year 7', year8 && 'Year 8', year9 && 'Year 9',
|
|
750
|
+
year10 && 'Year 10', year11 && 'Year 11',
|
|
751
|
+
].filter(Boolean);
|
|
752
|
+
|
|
753
|
+
const label = allYears
|
|
754
|
+
? 'All year groups'
|
|
755
|
+
: selectedYears.length === 0 ? 'Select year groups' : selectedYears.join(', ');
|
|
756
|
+
|
|
757
|
+
return (
|
|
758
|
+
<Dropdown>
|
|
759
|
+
<Dropdown.Trigger>
|
|
760
|
+
<Button variant="dropdown">{label}</Button>
|
|
761
|
+
</Dropdown.Trigger>
|
|
762
|
+
<Dropdown.Content>
|
|
763
|
+
<Dropdown.SelectItem selected={allYears} onSelectChange={handleAllYears} closeAfterSelection={false}>
|
|
764
|
+
All year groups
|
|
765
|
+
</Dropdown.SelectItem>
|
|
766
|
+
<Dropdown.Separator />
|
|
767
|
+
<Dropdown.SelectItem selected={year7} onSelectChange={setYear7} closeAfterSelection={false}>Year 7</Dropdown.SelectItem>
|
|
768
|
+
<Dropdown.SelectItem selected={year8} onSelectChange={setYear8} closeAfterSelection={false}>Year 8</Dropdown.SelectItem>
|
|
769
|
+
<Dropdown.SelectItem selected={year9} onSelectChange={setYear9} closeAfterSelection={false}>Year 9</Dropdown.SelectItem>
|
|
770
|
+
<Dropdown.SelectItem selected={year10} onSelectChange={setYear10} closeAfterSelection={false}>Year 10</Dropdown.SelectItem>
|
|
771
|
+
<Dropdown.SelectItem selected={year11} onSelectChange={setYear11} closeAfterSelection={false}>Year 11</Dropdown.SelectItem>
|
|
772
|
+
</Dropdown.Content>
|
|
773
|
+
</Dropdown>
|
|
774
|
+
);
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
export default AttendanceReportFilter;`,
|
|
778
|
+
},
|
|
779
|
+
},
|
|
780
|
+
},
|
|
781
|
+
}, 'A realistic Arbor attendance report filter demonstrating the full multi-select pattern. Selecting "All year groups" checks every year group simultaneously. The trigger label reflects the current selection. All items use `closeAfterSelection={false}` so teachers can pick multiple year groups in a single interaction. This is the canonical Arbor usage pattern for Dropdown-based filters.');
|
|
30
782
|
//# sourceMappingURL=Dropdown.stories.js.map
|