division-ds 0.1.2 → 0.2.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.
Files changed (97) hide show
  1. package/AGENTS.md +16 -2
  2. package/README.md +11 -0
  3. package/division-ds-0.2.0.tgz +0 -0
  4. package/package.json +2 -2
  5. package/public/docs/components/accordion.md +205 -0
  6. package/public/docs/components/ai-label.md +88 -0
  7. package/public/docs/components/alert-dialog.md +290 -0
  8. package/public/docs/components/alert.md +173 -0
  9. package/public/docs/components/approval-request.md +146 -0
  10. package/public/docs/components/auth-form.md +130 -0
  11. package/public/docs/components/avatar-group.md +150 -0
  12. package/public/docs/components/avatar.md +177 -0
  13. package/public/docs/components/badge.md +176 -0
  14. package/public/docs/components/breadcrumbs.md +110 -0
  15. package/public/docs/components/button.md +202 -0
  16. package/public/docs/components/card.md +255 -0
  17. package/public/docs/components/carousel.md +128 -0
  18. package/public/docs/components/cart.md +170 -0
  19. package/public/docs/components/chart-panel.md +356 -0
  20. package/public/docs/components/chat-message.md +154 -0
  21. package/public/docs/components/chat-thread.md +242 -0
  22. package/public/docs/components/checkbox.md +147 -0
  23. package/public/docs/components/checkout-form.md +126 -0
  24. package/public/docs/components/command-palette.md +145 -0
  25. package/public/docs/components/data-table.md +399 -0
  26. package/public/docs/components/description-list.md +101 -0
  27. package/public/docs/components/dialog.md +195 -0
  28. package/public/docs/components/divider.md +95 -0
  29. package/public/docs/components/dropdown-menu.md +220 -0
  30. package/public/docs/components/empty-state.md +143 -0
  31. package/public/docs/components/footer.md +395 -0
  32. package/public/docs/components/form-field.md +213 -0
  33. package/public/docs/components/icon-button.md +125 -0
  34. package/public/docs/components/input.md +198 -0
  35. package/public/docs/components/kanban-board.md +298 -0
  36. package/public/docs/components/kbd.md +70 -0
  37. package/public/docs/components/link.md +96 -0
  38. package/public/docs/components/list-item.md +238 -0
  39. package/public/docs/components/loading-animation.md +70 -0
  40. package/public/docs/components/multi-select.md +200 -0
  41. package/public/docs/components/navbar.md +142 -0
  42. package/public/docs/components/newsletter-form.md +110 -0
  43. package/public/docs/components/page-header.md +149 -0
  44. package/public/docs/components/pagination.md +210 -0
  45. package/public/docs/components/product-card.md +169 -0
  46. package/public/docs/components/progress.md +99 -0
  47. package/public/docs/components/prompt-input.md +149 -0
  48. package/public/docs/components/quantity-stepper.md +138 -0
  49. package/public/docs/components/radio.md +140 -0
  50. package/public/docs/components/rating.md +104 -0
  51. package/public/docs/components/score-ring.md +93 -0
  52. package/public/docs/components/search.md +114 -0
  53. package/public/docs/components/section-heading.md +111 -0
  54. package/public/docs/components/segmented-control.md +124 -0
  55. package/public/docs/components/select.md +222 -0
  56. package/public/docs/components/sheet.md +143 -0
  57. package/public/docs/components/sidebar.md +191 -0
  58. package/public/docs/components/skeleton.md +133 -0
  59. package/public/docs/components/slider.md +112 -0
  60. package/public/docs/components/sparkline.md +126 -0
  61. package/public/docs/components/spinner.md +96 -0
  62. package/public/docs/components/stat.md +104 -0
  63. package/public/docs/components/status-tracker.md +143 -0
  64. package/public/docs/components/stepper.md +145 -0
  65. package/public/docs/components/switch.md +151 -0
  66. package/public/docs/components/tabs.md +178 -0
  67. package/public/docs/components/tag.md +103 -0
  68. package/public/docs/components/textarea.md +146 -0
  69. package/public/docs/components/timeline.md +150 -0
  70. package/public/docs/components/toast.md +192 -0
  71. package/public/docs/components/tool-call.md +105 -0
  72. package/public/docs/components/tooltip.md +106 -0
  73. package/public/manifest/components.json +182 -6
  74. package/public/manifest/tokens.json +39 -0
  75. package/scripts/division-ds.mjs +74 -38
  76. package/scripts/lib/agent-docs.mjs +53 -1
  77. package/scripts/lib/ai-copy.mjs +106 -0
  78. package/scripts/lib/button-spacing.mjs +5 -5
  79. package/scripts/lib/check-text.mjs +44 -0
  80. package/scripts/lib/contrast.mjs +111 -0
  81. package/scripts/lib/host-inline.mjs +59 -0
  82. package/scripts/lib/mcp-server.mjs +115 -0
  83. package/scripts/lib/mcp-tools.mjs +241 -0
  84. package/scripts/lib/props-validator.mjs +1 -1
  85. package/scripts/lib/rules-installer.mjs +42 -4
  86. package/skills/division-ds/SKILL.md +90 -0
  87. package/skills/division-ds/commands/add.md +25 -0
  88. package/skills/division-ds/commands/build.md +46 -0
  89. package/skills/division-ds/commands/fix.md +28 -0
  90. package/skills/division-ds/commands/review.md +25 -0
  91. package/skills/division-ds/topics/ai.md +7 -0
  92. package/skills/division-ds/topics/colour.md +11 -0
  93. package/skills/division-ds/topics/feedback.md +9 -0
  94. package/skills/division-ds/topics/forms.md +19 -0
  95. package/skills/division-ds/topics/layout.md +34 -0
  96. package/skills/division-ds/topics/motion.md +10 -0
  97. package/skills/division-ds/topics/overlays.md +9 -0
@@ -0,0 +1,140 @@
1
+ # Radio
2
+
3
+ > Pick one choice from a set. The whole group is a single tab stop, and the arrow keys move inside it. That’s how native radio buttons behave, and it’s most of the reason this is built on a Radix primitive (Radix is a library of accessible building blocks) instead of three styled inputs.
4
+
5
+ - **Best for:** One choice from a short set, up to about seven, all shown at once.
6
+ - **Not for:** A yes/no question: use Checkbox or Switch. A longer list: use Select.
7
+ - Atom. Source: `src/components/atoms/radio/Radio.tsx`
8
+ - Rendered docs page: `/components/radio`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Radio, RadioGroup } from 'src/components/atoms/radio/Radio'
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `value (RadioGroup)` | `string` | | The selected value, when you control it from outside. |
21
+ | `defaultValue (RadioGroup)` | `string` | | The starting value, when the group manages it itself (uncontrolled). |
22
+ | `onValueChange (RadioGroup)` | `(value: string) => void` | | Fires when the selection changes. |
23
+ | `name (RadioGroup)` | `string` | | The native form field name. |
24
+ | `required (RadioGroup)` | `boolean` | | Uses the browser’s native form validation. |
25
+ | `disabled (RadioGroup)` | `boolean` | | Disables every option in the group. |
26
+ | `orientation (RadioGroup)` | `'horizontal' \| 'vertical'` | | Sets which arrow keys move between options. The visual layout is still up to className. |
27
+ | `aria-label / aria-labelledby (RadioGroup)` | `string` | | One of these is required. They’re ARIA attributes (from Accessible Rich Internet Applications) that give the group a name screen readers announce. A radiogroup with no name is a bug. |
28
+ | `value (Radio)` | `string` | | Required. This option’s value. |
29
+ | `disabled (Radio)` | `boolean` | | Takes just this option out of selection. |
30
+ | `id (Radio)` | `string` | | Pair it with a real label using htmlFor. |
31
+ | `invalid (Radio)` | `boolean` | `false` | Sets aria-invalid, which tells assistive tech the field has an error, and shows the invalid border. |
32
+ | `className` | `string` | | Merged with the control's classes. Later Tailwind utilities (Tailwind is the CSS framework the system uses) win. |
33
+ | `ref` | `Ref<HTMLDivElement> (RadioGroup) / Ref<HTMLButtonElement> (Radio)` | | Passed through to the Radix root element. |
34
+ | `...rest` | `(Radio) Omit<RadioGroup.Item props, 'aria-invalid'>` | | Every Radix RadioGroup.Item prop except aria-invalid, spread onto the item. aria-invalid is typed away instead of being quietly overridden at runtime, because invalid is the only source of both the attribute and the border. RadioGroup itself excludes nothing. It computes no value of its own that a rest prop could defeat. |
35
+
36
+ ## Examples
37
+
38
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
39
+
40
+ ### A group
41
+
42
+ ```tsx
43
+ const SPEEDS = ['standard', 'priority', 'overnight'] as const
44
+
45
+ function LiveExample() {
46
+ const [value, setValue] = useState<string>('standard')
47
+ return (
48
+ <RadioGroup aria-label="Dispatch speed" value={value} onValueChange={setValue}>
49
+ {SPEEDS.map(speed => (
50
+ <div key={speed} className="flex items-center gap-2">
51
+ <Radio value={speed} id={`dispatch-${speed}`} />
52
+ <label htmlFor={`dispatch-${speed}`} className="capitalize">
53
+ {speed}
54
+ </label>
55
+ </div>
56
+ ))}
57
+ </RadioGroup>
58
+ )
59
+ }
60
+
61
+ <LiveExample />
62
+ ```
63
+
64
+ ### With a default
65
+
66
+ ```tsx
67
+ const SPEEDS = ['standard', 'priority', 'overnight'] as const
68
+
69
+ <RadioGroup aria-label="Dispatch speed, defaulted" defaultValue="priority">
70
+ {SPEEDS.map(speed => (
71
+ <div key={speed} className="flex items-center gap-2">
72
+ <Radio value={speed} id={`default-${speed}`} />
73
+ <label htmlFor={`default-${speed}`} className="capitalize">
74
+ {speed}
75
+ </label>
76
+ </div>
77
+ ))}
78
+ </RadioGroup>
79
+ ```
80
+
81
+ ### Horizontal
82
+
83
+ ```tsx
84
+ const SPEEDS = ['standard', 'priority', 'overnight'] as const
85
+
86
+ <RadioGroup aria-label="Dispatch speed, horizontal" orientation="horizontal" defaultValue="standard" className="flex-row flex-wrap gap-4">
87
+ {SPEEDS.map(speed => (
88
+ <div key={speed} className="flex items-center gap-2">
89
+ <Radio value={speed} id={`horizontal-${speed}`} />
90
+ <label htmlFor={`horizontal-${speed}`} className="capitalize">
91
+ {speed}
92
+ </label>
93
+ </div>
94
+ ))}
95
+ </RadioGroup>
96
+ ```
97
+
98
+ ### Disabled option
99
+
100
+ ```tsx
101
+ const SPEEDS = ['standard', 'priority', 'overnight'] as const
102
+
103
+ <RadioGroup aria-label="Dispatch speed, one option disabled" defaultValue="standard">
104
+ {SPEEDS.map(speed => (
105
+ <div key={speed} className="flex items-center gap-2">
106
+ <Radio value={speed} id={`disabled-${speed}`} disabled={speed === 'overnight'} />
107
+ <label htmlFor={`disabled-${speed}`} className="capitalize">
108
+ {speed}
109
+ </label>
110
+ </div>
111
+ ))}
112
+ </RadioGroup>
113
+ ```
114
+
115
+ ## Keyboard
116
+
117
+ | Keys | Result |
118
+ |---|---|
119
+ | `Tab` | Enters the group at the selected option, or at the first one if nothing’s selected. |
120
+ | `↓` `→` | Selects the next option. |
121
+ | `↑` `←` | Selects the previous option. |
122
+ | `Space` | Selects the focused option. |
123
+
124
+ ## Do
125
+
126
+ - Name the group with aria-label or a FormField legend.
127
+ - Offer a default when one option really is the norm.
128
+ - Keep the set short. Past about seven options, use a Select.
129
+
130
+ ## Don't
131
+
132
+ - Don't use radios for a yes/no. That's a Checkbox or a Switch.
133
+ - Don't put a radio outside a RadioGroup. Both the roving focus and the value come from the group.
134
+ - Don't make choosing an option perform the action. Put a button after the group.
135
+
136
+ ## Anatomy
137
+
138
+ - **RadioGroup**: Has role=radiogroup, which tells assistive tech (screen readers and similar tools) this is a set of radios. It handles roving focus, where only one option is reachable by Tab and the arrows move between the rest, and it owns the value.
139
+ - **Radio**: Has role=radio. A 20px (pixel) square inside a 24px hit area, the region that responds to a click or tap.
140
+ - **Indicator**: A filled 10px square, not a dot. The system’s shape language has no circles.
@@ -0,0 +1,104 @@
1
+ # Rating
2
+
3
+ > A score out of five, either shown or collected. The display is one image that a screen reader reads as one sentence. The input is a radio group, because picking a rating means picking one value.
4
+
5
+ - **Best for:** Showing or collecting a score out of five.
6
+ - **Not for:** Anything that isn't a rating, such as a difficulty grade.
7
+ - Molecule. Source: `src/components/molecules/rating/Rating.tsx`
8
+ - Rendered docs page: `/components/rating`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Rating, RatingInput } from 'src/components/molecules/rating/Rating'
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `value (Rating)` | `number` | | From 0 up to max, rounded to the nearest half for both the drawing and what screen readers say. |
21
+ | `max (Rating)` | `number` | `5` | Number of stars. |
22
+ | `label (Rating)` | `string` | | What is being rated, read out as "<label> rated 4.5 out of 5". |
23
+ | `showValue (Rating)` | `boolean` | `false` | Prints the rounded value beside the stars, aria-hidden. |
24
+ | `size (Rating)` | `'sm' \| 'md'` | `'md'` | Star size. |
25
+ | `className (Rating)` | `string` | | Combined with the root classes. Where Tailwind utility classes clash, the later one wins. |
26
+ | `ref (Rating)` | `Ref<HTMLSpanElement>` | | Passed through to the root span. |
27
+ | `label (RatingInput)` | `string` | | Required. The radiogroup's name, for example "Rate the Prism holo sight". |
28
+ | `value (RatingInput)` | `number` | | The selected value, when your page holds it (controlled). |
29
+ | `defaultValue (RatingInput)` | `number` | | Starting value when the component holds it itself (uncontrolled). |
30
+ | `onValueChange (RatingInput)` | `(value: number) => void` | | Fires when a star is picked by click or arrow key. |
31
+ | `max (RatingInput)` | `number` | `5` | Number of stars. |
32
+ | `disabled (RatingInput)` | `boolean` | | Disables every star. |
33
+ | `className (RatingInput)` | `string` | | Combined with the root classes. |
34
+ | `ref (RatingInput)` | `Ref<HTMLDivElement>` | | Passed through to the Radix radiogroup root. It's built straight on that, not on the Radio atom: see the anatomy row above. |
35
+
36
+ ## Examples
37
+
38
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
39
+
40
+ ### Display
41
+
42
+ ```tsx
43
+ <div className="flex flex-col gap-3">
44
+ <Rating value={4.5} label="Prism holo sight" showValue />
45
+ <Rating value={3} label="Bump helmet" showValue />
46
+ <Rating value={5} label="Field goggles" showValue />
47
+ <Rating value={0.5} label="Dummy radio" showValue />
48
+ </div>
49
+ ```
50
+
51
+ ### Small, beside a price
52
+
53
+ ```tsx
54
+ <div className="flex items-center gap-2">
55
+ <span className="font-display font-semibold">€128</span>
56
+ <Rating value={4} size="sm" label="Prism holo sight" />
57
+ </div>
58
+ ```
59
+
60
+ ### Input
61
+
62
+ ```tsx
63
+ function LiveInput() {
64
+ const [value, setValue] = useState(4)
65
+ return (
66
+ <div className="flex items-center gap-3">
67
+ <RatingInput label="Rate the Prism holo sight" value={value} onValueChange={setValue} />
68
+ <span className="font-mono text-xs text-[var(--color-text-secondary)]">
69
+ You rated it {value} {value === 1 ? 'star' : 'stars'}
70
+ </span>
71
+ </div>
72
+ )
73
+ }
74
+
75
+ <LiveInput />
76
+ ```
77
+
78
+ ## Keyboard
79
+
80
+ | Keys | Result |
81
+ |---|---|
82
+ | `Tab` | Moves into the group, onto the selected star, or the first one. |
83
+ | `←` `↑` `→` `↓` | One star less (Left or Up) or more (Right or Down), selecting as it goes. It wraps round: past the last star goes to the first, and past the first goes to the last. |
84
+ | `Space` | Selects the focused star. |
85
+
86
+ ## Do
87
+
88
+ - Name what is rated in the display's label when the surrounding page doesn't.
89
+ - Show the number of reviews next to the stars: "4.5 (212)".
90
+ - Label an input with the action: "Rate the Prism holo sight".
91
+
92
+ ## Don't
93
+
94
+ - Don't use more than five stars.
95
+ - Don't let hovering over a star select anything.
96
+ - Don't use stars for anything that isn't a rating, such as a difficulty grade.
97
+
98
+ ## Anatomy
99
+
100
+ - **Rating**: Has role=img and the name "4.5 out of 5", so a screen reader treats it as one picture. The stars are aria-hidden (screen readers skip them).
101
+ - **Star**: Full, half or empty. Filled ones use the signal colour; empty ones are a border.selection outline.
102
+ - **Value**: An optional printed number. It's aria-hidden because the name already says it.
103
+ - **RatingInput**: A radiogroup named by label, built straight on Radix RadioGroup (Radix is the library of unstyled, accessible building blocks this system uses) instead of the Radio atom. Radio draws a 20px (pixel) square box with a filled inner square to show it's selected, and a star can't be drawn inside a square without either cropping it or leaving the box empty around it.
104
+ - **Star option**: A 28px radio named "3 stars".
@@ -0,0 +1,93 @@
1
+ # ScoreRing
2
+
3
+ > One 0 to 100 score shown as a ring with the number in its centre, split into bands the way Lighthouse (Google’s website audit tool) bands its scores. It’s a meter, so a screen reader, the software that reads the page aloud, hears both the score and its band.
4
+
5
+ - **Best for:** A 0 to 100 score with good and bad bands, like an audit, a health check or a quality score. Several side by side read as one report.
6
+ - **Not for:** Progress through a task: use Progress. A number with a change over time: use Stat. Parts of a whole: use a donut ChartPanel.
7
+ - Atom. Source: `src/components/atoms/score-ring/ScoreRing.tsx`
8
+ - Rendered docs page: `/components/score-ring`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { ScoreRing, scoreBand } from 'src/components/atoms/score-ring/ScoreRing'
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `label` | `string` | | Required. What’s being scored. It’s both the accessible name and the caption. |
21
+ | `value` | `number` | | Required. 0 to 100, clamped and rounded to a whole number. |
22
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | The ring is 56, 96 or 128px (pixels) wide. |
23
+ | `className` | `string` | | Merged with the meter’s classes. Later Tailwind utilities (Tailwind is the CSS framework the system uses) win. |
24
+ | `ref` | `Ref<HTMLDivElement>` | | Points to the meter. |
25
+ | `...rest` | `Omit<HTMLAttributes<HTMLDivElement>, 'children'>` | | Every native div prop except children, spread onto the meter. The ring draws its own content. |
26
+ | `scoreBand()` | `(value: number) => 'hostile' \| 'warning' \| 'ok'` | | The banding the ring uses. It’s exported so a table or a sentence beside the ring agrees with it. |
27
+
28
+ ## Types and helpers
29
+
30
+ Exported beside the component, from the same file. Import a type with `import type { ... }`.
31
+
32
+ ```ts
33
+ export type ScoreBand = 'hostile' | 'warning' | 'ok'
34
+ ```
35
+
36
+ ```ts
37
+ /** Lighthouse's own bands, and its own words for them: 0-49 poor, 50-89 needs improvement, 90-100
38
+ * good. Exported so a table or a summary sentence beside the rings can band a score the same way. */
39
+ export function scoreBand(value: number): ScoreBand
40
+ ```
41
+
42
+ ## Examples
43
+
44
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
45
+
46
+ ### A report like Lighthouse (Google's website audit tool): four scores in a row
47
+
48
+ ```tsx
49
+ <ScoreRing label="Performance" value={42} />
50
+ <ScoreRing label="Accessibility" value={98} />
51
+ <ScoreRing label="Best practices" value={77} />
52
+ <ScoreRing label="SEO" value={100} />
53
+ ```
54
+
55
+ ### Sizes: sm, md and lg
56
+
57
+ ```tsx
58
+ <ScoreRing label="Stock health" value={64} size="sm" />
59
+ <ScoreRing label="Stock health" value={64} />
60
+ <ScoreRing label="Stock health" value={64} size="lg" />
61
+ ```
62
+
63
+ ### The band boundaries: 49, 50, 89 and 90
64
+
65
+ ```tsx
66
+ <ScoreRing label="Depot A" value={49} size="sm" />
67
+ <ScoreRing label="Depot B" value={50} size="sm" />
68
+ <ScoreRing label="Depot C" value={89} size="sm" />
69
+ <ScoreRing label="Depot D" value={90} size="sm" />
70
+ ```
71
+
72
+ ## Keyboard
73
+
74
+ Not focusable. No keyboard behaviour of its own.
75
+
76
+ ## Do
77
+
78
+ - Put several rings in a row for one report, like Lighthouse’s four scores.
79
+ - Name what’s being scored in a word or two.
80
+ - State the weakest score in the sentence beside the rings.
81
+
82
+ ## Don't
83
+
84
+ - Don't use it for a value that isn't out of 100. Rescale it first, or use Stat.
85
+ - Don't colour the number. The arc carries the band, and the number stays text.primary.
86
+
87
+ ## Anatomy
88
+
89
+ - **Ring**: A hairline track plus an arc as long as the score, with square (butt) ends.
90
+ - **Arc colour**: Set by band: hostile below 50, warning from 50 to 89, ok from 90. Colour is never the only signal, because the number is always printed.
91
+ - **Ticks**: Three marks outside the ring at 0, 50 and 90. They mark the start and the two band boundaries.
92
+ - **Number**: The score in display figures, centred in the ring.
93
+ - **Label**: Mono (a monospaced font), uppercase, under the ring. It’s also the meter’s accessible name, the name assistive tech announces for it.
@@ -0,0 +1,114 @@
1
+ # Search
2
+
3
+ > A search field inside a search landmark (a region of the page that screen readers can jump straight to). Enter submits, Escape clears, and a one-key shortcut can move focus to it from anywhere on the page.
4
+
5
+ - **Best for:** Searching the content of the page that shows the results, inside a search landmark.
6
+ - **Not for:** Suggestions that appear as you type, or jumping to an action: use CommandPalette.
7
+ - Molecule. Source: `src/components/molecules/search/Search.tsx`
8
+ - Rendered docs page: `/components/search`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Search } from 'src/components/molecules/search/Search'
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `label` | `string` | | Required. The name screen readers use for the field, and for the search landmark. |
21
+ | `value` | `string` | | The value, when your page holds it (controlled). |
22
+ | `defaultValue` | `string` | `''` | Starting value when the component holds it itself (uncontrolled). |
23
+ | `onValueChange` | `(value: string) => void` | | Called whenever the value changes, clearing included. |
24
+ | `onSearch` | `(query: string) => void` | | Called on Enter with the value, spaces trimmed from both ends. It isn't called for an empty query. |
25
+ | `placeholder` | `string` | `'Search'` | |
26
+ | `shortcut` | `string` | | One printable key, for example "/". It moves focus to the field from anywhere on the page, except while someone is typing in another text field. While the field is empty, it shows as a Kbd hint. |
27
+ | `inputSize` | `'sm' \| 'md' \| 'lg'` | `'md'` | |
28
+ | `className` | `string` | | Combined with the root classes. Where Tailwind utility classes clash, the later one wins. |
29
+ | `ref` | `Ref<HTMLInputElement>` | | Passed through to the input element. |
30
+
31
+ ## Examples
32
+
33
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
34
+
35
+ ### Default
36
+
37
+ ```tsx
38
+ <div className="max-w-xs w-full">
39
+ <Search label="Search the catalogue" />
40
+ </div>
41
+ ```
42
+
43
+ ### With a shortcut
44
+
45
+ ```tsx
46
+ <div className="max-w-xs w-full">
47
+ <Search label="Search the manifest" shortcut="/" />
48
+ <p className="font-body text-xs text-[var(--color-text-secondary)] mt-3">
49
+ Press / anywhere on this page to focus it.
50
+ </p>
51
+ </div>
52
+ ```
53
+
54
+ ### Submitting
55
+
56
+ ```tsx
57
+ function SubmittingExample() {
58
+ const [result, setResult] = useState<string | null>(null)
59
+
60
+ return (
61
+ <div className="max-w-xs w-full">
62
+ <Search label="Search the depot list" onSearch={query => setResult(query)} />
63
+ <p className="font-body text-xs text-[var(--color-text-secondary)] mt-3">
64
+ {result ? `Searched for: ${result}` : 'Press Enter to search.'}
65
+ </p>
66
+ </div>
67
+ )
68
+ }
69
+
70
+ <SubmittingExample />
71
+ ```
72
+
73
+ ### Sizes
74
+
75
+ ```tsx
76
+ <div className="max-w-xs w-full">
77
+ <Search label="Search, small" placeholder="Small" inputSize="sm" />
78
+ </div>
79
+ <div className="max-w-xs w-full">
80
+ <Search label="Search, medium" placeholder="Medium" inputSize="md" />
81
+ </div>
82
+ <div className="max-w-xs w-full">
83
+ <Search label="Search, large" placeholder="Large" inputSize="lg" />
84
+ </div>
85
+ ```
86
+
87
+ ## Keyboard
88
+
89
+ | Keys | Result |
90
+ |---|---|
91
+ | `Enter` | Submits the query, spaces trimmed. |
92
+ | `Escape` | Clears the field. |
93
+ | `/` | Moves focus to the field, unless another text field has focus. It works for whichever key the shortcut prop names. |
94
+ | `Tab` | Moves between the field and the clear button. |
95
+
96
+ ## Do
97
+
98
+ - Name what's being searched: 'Search the catalogue', not 'Search'.
99
+ - Pick a shortcut that isn't a letter people type in forms; '/' is the usual one.
100
+ - Keep results on the page that owns the search, not in a popup under the field.
101
+
102
+ ## Don't
103
+
104
+ - Don't add suggestions that appear as you type. That's the command palette's job, and it has a listbox built for it.
105
+ - Don't search on every keystroke without saying so. Results that shift while someone types are hard to follow with a screen reader.
106
+ - Don't put two search landmarks on one page without giving them different labels.
107
+
108
+ ## Anatomy
109
+
110
+ - **Landmark**: A form with role=search, named by label, so people using a screen reader can jump to it.
111
+ - **Icon**: Decoration only. It's aria-hidden, so screen readers skip it.
112
+ - **Field**: An Input of type search. The browser's own clear button is hidden, because you can't reach it with the keyboard.
113
+ - **Clear button**: Only shows while there's text. It puts focus back in the field.
114
+ - **Shortcut hint**: A Kbd (the keyboard-key badge), aria-hidden, and only shown while the field is empty. Screen readers get the shortcut from aria-keyshortcuts on the field instead.
@@ -0,0 +1,111 @@
1
+ # SectionHeading
2
+
3
+ > The heading that opens a section of a page: a title under a hairline, with an optional description and an optional action. The page tells it which heading level to use, because only the page knows its own outline.
4
+
5
+ - **Best for:** Opening a section of a page, at the level that carries on the outline.
6
+ - **Not for:** The page title: use PageHeader.
7
+ - Molecule. Source: `src/components/molecules/section-heading/SectionHeading.tsx`
8
+ - Rendered docs page: `/components/section-heading`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { SectionHeading } from 'src/components/molecules/section-heading/SectionHeading'
14
+ ```
15
+
16
+ ## Props
17
+
18
+ | Prop | Type | Default | Description |
19
+ |---|---|---|---|
20
+ | `title` | `string` | | Required. The heading text. |
21
+ | `level` | `2 \| 3 \| 4` | | Required, with no default. The heading level the page outline needs at this point. Leave it out and the code won't compile. |
22
+ | `description` | `ReactNode` | | One or two sentences under the title. |
23
+ | `action` | `ReactNode` | | A single control at the end of the row, usually a link to the full list. |
24
+ | `index` | `number` | | The section's position in a numbered run of sections, drawn above the title. It's decoration and never part of the heading's name. |
25
+ | `id` | `string` | | Goes on the heading element, not the wrapper, so a section can point aria-labelledby at it (that's how the section gets its name for screen readers). |
26
+ | `className` | `string` | | Added to the root. Where Tailwind utility classes clash, the later one wins. |
27
+ | `ref` | `Ref<HTMLDivElement>` | | Passed through to the root element. |
28
+
29
+ ## Examples
30
+
31
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
32
+
33
+ ### Title only
34
+
35
+ ```tsx
36
+ <SectionHeading level={4} title="Top-rated replicas" />
37
+ ```
38
+
39
+ ### With a description and an action
40
+
41
+ ```tsx
42
+ <SectionHeading
43
+ level={4}
44
+ title="Top-rated accessories"
45
+ description="Optics and parts, ordered by what the reviews say."
46
+ action={<Link href="#accessories">All accessories</Link>}
47
+ />
48
+ ```
49
+
50
+ ### Numbered, in a run
51
+
52
+ ```tsx
53
+ <div className="flex flex-col gap-10">
54
+ <SectionHeading level={4} index={1} title="Replicas" description="Three lengths on one receiver." />
55
+ <SectionHeading level={4} index={2} title="Optics" description="A tube sight and a holo sight." />
56
+ <SectionHeading
57
+ level={4}
58
+ index={3}
59
+ title="Parts and accessories"
60
+ action={
61
+ <Button asChild variant="secondary" size="sm">
62
+ <a href="#parts">All parts</a>
63
+ </Button>
64
+ }
65
+ />
66
+ </div>
67
+ ```
68
+
69
+ ### Naming its section
70
+
71
+ ```tsx
72
+ <section aria-labelledby="section-heading-docs-care">
73
+ <SectionHeading
74
+ level={4}
75
+ id="section-heading-docs-care"
76
+ title="Care plans"
77
+ description="The section below is a region named “Care plans”. It takes that name from this heading, which carries the id."
78
+ />
79
+ <p className="mt-4 font-body text-sm text-[var(--color-text-primary)]">
80
+ A chrono check, a tune and upgrade fitting, billed monthly.
81
+ </p>
82
+ </section>
83
+ ```
84
+
85
+ ## Keyboard
86
+
87
+ | Keys | Result |
88
+ |---|---|
89
+ | `Tab` | Moves to the action, if there is one. The heading itself isn't a Tab stop. |
90
+
91
+ ## Do
92
+
93
+ - Pass the level that carries on the page outline: h2 under the page h1, h3 inside an h2 section.
94
+ - Give the heading an id and point the section at it with aria-labelledby, so the region has a name.
95
+ - Name the action after where it goes: "All replicas", not "View all" four times on one page.
96
+ - Number every section in a run, or none of them.
97
+
98
+ ## Don't
99
+
100
+ - Don't use it as the page title. A page has one h1, and that isn't a section opener.
101
+ - Don't pick a level for its size. The size is the same at every level; the level is about the outline.
102
+ - Don't put more than one control in the action slot. A second one belongs in the section body.
103
+ - Don't stack one straight under another. A section heading opens content, not a second heading.
104
+
105
+ ## Anatomy
106
+
107
+ - **Rule**: A hairline along the top, in border.hairline-strong. It marks where the section starts.
108
+ - **Index**: Optional. A two-digit number in monospace type, on its own line above the title, and aria-hidden so screen readers skip it. The leading zero is in numeral.pad and the digit in accent.signal.text. It sits above the title, not in front, so the title keeps the same left edge as its description and its section.
109
+ - **Title**: The heading element, h2 to h4 (heading levels 2 to 4). Oxanium, uppercase, 20px (pixels), going up to 24px when the heading has 42rem or more of width.
110
+ - **Description**: Optional. Body text in text.secondary, at most 60 characters a line.
111
+ - **Action**: Optional. One control at the far end of the row. It drops under the text when there isn't room.