division-ds 0.1.2 → 0.2.1

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 (98) hide show
  1. package/AGENTS.md +23 -3
  2. package/README.md +11 -0
  3. package/division-ds-0.2.1.tgz +0 -0
  4. package/package.json +2 -2
  5. package/public/docs/components/accordion.md +207 -0
  6. package/public/docs/components/ai-label.md +90 -0
  7. package/public/docs/components/alert-dialog.md +292 -0
  8. package/public/docs/components/alert.md +175 -0
  9. package/public/docs/components/approval-request.md +148 -0
  10. package/public/docs/components/auth-form.md +132 -0
  11. package/public/docs/components/avatar-group.md +152 -0
  12. package/public/docs/components/avatar.md +179 -0
  13. package/public/docs/components/badge.md +178 -0
  14. package/public/docs/components/breadcrumbs.md +112 -0
  15. package/public/docs/components/button.md +204 -0
  16. package/public/docs/components/card.md +257 -0
  17. package/public/docs/components/carousel.md +130 -0
  18. package/public/docs/components/cart.md +172 -0
  19. package/public/docs/components/chart-panel.md +358 -0
  20. package/public/docs/components/chat-message.md +157 -0
  21. package/public/docs/components/chat-thread.md +244 -0
  22. package/public/docs/components/checkbox.md +149 -0
  23. package/public/docs/components/checkout-form.md +128 -0
  24. package/public/docs/components/command-palette.md +147 -0
  25. package/public/docs/components/data-table.md +401 -0
  26. package/public/docs/components/description-list.md +103 -0
  27. package/public/docs/components/dialog.md +197 -0
  28. package/public/docs/components/divider.md +97 -0
  29. package/public/docs/components/dropdown-menu.md +222 -0
  30. package/public/docs/components/empty-state.md +145 -0
  31. package/public/docs/components/footer.md +397 -0
  32. package/public/docs/components/form-field.md +216 -0
  33. package/public/docs/components/icon-button.md +127 -0
  34. package/public/docs/components/input.md +200 -0
  35. package/public/docs/components/kanban-board.md +300 -0
  36. package/public/docs/components/kbd.md +72 -0
  37. package/public/docs/components/link.md +98 -0
  38. package/public/docs/components/list-item.md +241 -0
  39. package/public/docs/components/loading-animation.md +72 -0
  40. package/public/docs/components/multi-select.md +202 -0
  41. package/public/docs/components/navbar.md +144 -0
  42. package/public/docs/components/newsletter-form.md +112 -0
  43. package/public/docs/components/page-header.md +151 -0
  44. package/public/docs/components/pagination.md +212 -0
  45. package/public/docs/components/product-card.md +172 -0
  46. package/public/docs/components/progress.md +101 -0
  47. package/public/docs/components/prompt-input.md +151 -0
  48. package/public/docs/components/quantity-stepper.md +140 -0
  49. package/public/docs/components/radio.md +177 -0
  50. package/public/docs/components/rating.md +106 -0
  51. package/public/docs/components/score-ring.md +95 -0
  52. package/public/docs/components/search.md +116 -0
  53. package/public/docs/components/section-heading.md +113 -0
  54. package/public/docs/components/segmented-control.md +126 -0
  55. package/public/docs/components/select.md +224 -0
  56. package/public/docs/components/sheet.md +145 -0
  57. package/public/docs/components/sidebar.md +193 -0
  58. package/public/docs/components/skeleton.md +135 -0
  59. package/public/docs/components/slider.md +114 -0
  60. package/public/docs/components/sparkline.md +128 -0
  61. package/public/docs/components/spinner.md +98 -0
  62. package/public/docs/components/stat.md +106 -0
  63. package/public/docs/components/status-tracker.md +145 -0
  64. package/public/docs/components/stepper.md +147 -0
  65. package/public/docs/components/switch.md +153 -0
  66. package/public/docs/components/tabs.md +180 -0
  67. package/public/docs/components/tag.md +105 -0
  68. package/public/docs/components/textarea.md +148 -0
  69. package/public/docs/components/timeline.md +152 -0
  70. package/public/docs/components/toast.md +194 -0
  71. package/public/docs/components/tool-call.md +107 -0
  72. package/public/docs/components/tooltip.md +108 -0
  73. package/public/manifest/components.json +198 -15
  74. package/public/manifest/tokens.json +75 -5
  75. package/scripts/division-ds.mjs +78 -42
  76. package/scripts/lib/agent-docs.mjs +80 -6
  77. package/scripts/lib/ai-copy.mjs +120 -0
  78. package/scripts/lib/button-spacing.mjs +5 -5
  79. package/scripts/lib/check-text.mjs +57 -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 +116 -0
  83. package/scripts/lib/mcp-tools.mjs +266 -0
  84. package/scripts/lib/props-validator.mjs +3 -2
  85. package/scripts/lib/rules-installer.mjs +42 -4
  86. package/scripts/lib/token-discipline.mjs +10 -3
  87. package/skills/division-ds/SKILL.md +90 -0
  88. package/skills/division-ds/commands/add.md +25 -0
  89. package/skills/division-ds/commands/build.md +46 -0
  90. package/skills/division-ds/commands/fix.md +28 -0
  91. package/skills/division-ds/commands/review.md +25 -0
  92. package/skills/division-ds/topics/ai.md +8 -0
  93. package/skills/division-ds/topics/colour.md +11 -0
  94. package/skills/division-ds/topics/feedback.md +9 -0
  95. package/skills/division-ds/topics/forms.md +22 -0
  96. package/skills/division-ds/topics/layout.md +36 -0
  97. package/skills/division-ds/topics/motion.md +10 -0
  98. package/skills/division-ds/topics/overlays.md +9 -0
@@ -0,0 +1,72 @@
1
+ # Kbd
2
+
3
+ > A small keycap that shows a key's name. Making shortcuts easy to find was one of the motifs from the G1 mood board (the project's first approval step) worth keeping: if a surface has a shortcut, the surface says so.
4
+
5
+ - **Best for:** Showing a keyboard shortcut next to the action it triggers, with one Kbd per key.
6
+ - **Not for:** Anything that isn't a key, such as a status: use Badge.
7
+ - Atom. Source: `src/components/atoms/kbd/Kbd.tsx`
8
+ - Rendered docs page: `/components/kbd`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Kbd } from 'src/components/atoms/kbd/Kbd'
14
+ ```
15
+
16
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `children` | `ReactNode` | | The key's name, for example "Esc" or "⌘". |
23
+ | `className` | `string` | | Merged with the component's classes. When Tailwind utility classes clash, the later one wins. |
24
+ | `ref` | `Ref<HTMLElement>` | | Passed through to the kbd element. |
25
+ | `...rest` | `HTMLAttributes<HTMLElement>` | | Every native attribute, passed onto the kbd element, such as a title, or an aria-label for a symbol key (aria-label is the name screen readers read out). |
26
+
27
+ ## Examples
28
+
29
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
30
+
31
+ ### Single keys
32
+
33
+ ```tsx
34
+ <Kbd>Esc</Kbd>
35
+ <Kbd>Tab</Kbd>
36
+ <Kbd>Enter</Kbd>
37
+ <Kbd>Space</Kbd>
38
+ <Kbd>/</Kbd>
39
+ ```
40
+
41
+ ### A chord: one cap per key
42
+
43
+ ```tsx
44
+ <span className="flex items-center gap-1">
45
+ <Kbd>Ctrl</Kbd>
46
+ <span className="text-[var(--color-text-muted)] text-sm">+</span>
47
+ <Kbd>K</Kbd>
48
+ </span>
49
+ <span className="text-sm text-[var(--color-text-secondary)]">Open the command palette</span>
50
+ ```
51
+
52
+ ## Keyboard
53
+
54
+ Not focusable. No keyboard behaviour of its own.
55
+
56
+ ## Do
57
+
58
+ - Use one Kbd per physical key, and put them side by side for a chord (keys pressed together).
59
+ - Put it next to the action it triggers, in the same row or the same tooltip.
60
+ - Write the key name the way the keyboard prints it.
61
+
62
+ ## Don't
63
+
64
+ - Don’t bevel it. Cut corners are for surfaces and controls, not 24px chrome.
65
+ - Don’t put a whole chord inside one Kbd ("Ctrl+K" in a single cap).
66
+ - Don’t use it for anything that isn’t a key. It’s not a badge.
67
+
68
+ ## Anatomy
69
+
70
+ - **kbd element**: The HTML element made for keys. Screen readers announce it as keyboard input.
71
+ - **Hairline border**: A one-pixel border in border.hairline-strong, with no bevel (cut corners).
72
+ - **Mono legend**: JetBrains Mono, uppercase, 11px (pixels): the tertiary type role.
@@ -0,0 +1,98 @@
1
+ # Link
2
+
3
+ > A link inside text. It's underlined, because in a paragraph colour alone isn't enough to show that something is a link.
4
+
5
+ - **Best for:** Text that takes you somewhere: another page, a section or another site.
6
+ - **Not for:** Something that does an action. Use Button for that (its link variant if it's inside running text).
7
+ - Atom. Source: `src/components/atoms/link/Link.tsx`
8
+ - Rendered docs page: `/components/link`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { Link, linkVariants } from 'src/components/atoms/link/Link'
14
+ ```
15
+
16
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `tone` | `'signal' \| 'quiet'` | `'signal'` | Which colour treatment to use. |
23
+ | `external` | `boolean` | `false` | Opens the link in a new tab. It adds target="_blank", the rel hardening (attributes that stop the new page reaching back into yours), a trailing icon and the hidden "(opens in a new tab)" warning. Using it together with target or rel is a type error, so it can't fail silently. external already sets those values, so those props are removed from the type instead of being quietly overwritten while the page runs. |
24
+ | `href` | `string` | | Where the link goes. |
25
+ | `className` | `string` | | Merged with the component classes. If two clash, the later Tailwind utility wins (Tailwind is the CSS framework this system styles with). |
26
+ | `ref` | `Ref<HTMLAnchorElement>` | | Passed through to the anchor element. |
27
+ | `...rest` | `AnchorHTMLAttributes<HTMLAnchorElement>` | | Every native anchor prop. |
28
+
29
+ ## Types and helpers
30
+
31
+ Exported beside the component, from the same file. Import a type with `import type { ... }`.
32
+
33
+ ```ts
34
+ export const linkVariants = cva( …
35
+ ```
36
+
37
+ ## Examples
38
+
39
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
40
+
41
+ ### In running text
42
+
43
+ ```tsx
44
+ <p className="text-sm text-[var(--color-text-secondary)] max-w-md">
45
+ Check the <Link href="/kits">field kit index</Link> before you head out, and read the{' '}
46
+ <Link href="https://example.com" external tone="quiet">
47
+ supplier notes
48
+ </Link>{' '}
49
+ if the hop-up looks off.
50
+ </p>
51
+ ```
52
+
53
+ ### Tones
54
+
55
+ ```tsx
56
+ <Link href="/kits" tone="signal">
57
+ Field kits
58
+ </Link>
59
+ <Link href="/kits" tone="quiet">
60
+ Field kits
61
+ </Link>
62
+ ```
63
+
64
+ ### External
65
+
66
+ ```tsx
67
+ <Link href="https://example.com" external>
68
+ Supplier
69
+ </Link>
70
+ ```
71
+
72
+ ## Keyboard
73
+
74
+ | Keys | Result |
75
+ |---|---|
76
+ | `Tab` | Moves focus to it. The focus ring (the outline that shows which element the keyboard is on) is drawn on the link itself, and nothing clips it here. |
77
+ | `Enter` | Follows the link. |
78
+
79
+ ## Do
80
+
81
+ - Keep the underline on links inside running text.
82
+ - Put the destination in the link text: 'the field kit index', not 'click here'.
83
+ - Set external on anything that leaves the app, so screen readers announce the new tab.
84
+ - Use quiet in dense lists where every row is a link anyway.
85
+
86
+ ## Don't
87
+
88
+ - Don't use a link for something that does an action. That's a Button.
89
+ - Don't open a new tab without external. The rel hardening and the warning both come with it.
90
+ - Don't remove the underline to make a paragraph look tidier.
91
+ - Don't pass target or rel together with external. Those props are removed from the type, so you can't override them. If you need different values, render a plain anchor yourself.
92
+
93
+ ## Anatomy
94
+
95
+ - **Anchor**: The browser's own <a> element. You can focus it and use it from the keyboard without any extra work.
96
+ - **Underline**: 1px (pixel) thick, sitting 2px below the text. It gets thicker on hover in the signal tone.
97
+ - **External icon**: Hidden from screen readers with aria-hidden (an ARIA attribute, from Accessible Rich Internet Applications). It only shows when external is set.
98
+ - **Hidden warning**: ‘(opens in a new tab)’, added to the accessible name, which is what screen readers announce for the link.
@@ -0,0 +1,241 @@
1
+ # ListItem
2
+
3
+ > One row of a list: something at the start (a picture or icon), a title and description, extra details on the right, and actions at the end. A row that goes somewhere is one real link or button stretched over the whole row. Its actions sit beside it, never inside it.
4
+
5
+ - **Best for:** A row people scan in a List: media, a title, meta and one or two actions.
6
+ - **Not for:** Data in a table that people compare across columns: use DataTable.
7
+ - **Also on this page:** `List`
8
+ - Molecule. Source: `src/components/molecules/list-item/ListItem.tsx`
9
+ - Rendered docs page: `/components/list-item`
10
+
11
+ ## Import
12
+
13
+ ```tsx
14
+ import { List, ListItem } from 'src/components/molecules/list-item/ListItem'
15
+ ```
16
+
17
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
18
+
19
+ ## Requires
20
+
21
+ - A <List> around the rows (a ul with role="list"), or as="div" on a row that stands alone.
22
+
23
+ ## Props
24
+
25
+ | Prop | Type | Default | Description |
26
+ |---|---|---|---|
27
+ | `title` | `ReactNode` | | Required. The main line, and the name screen readers give the row’s link or button. |
28
+ | `description` | `ReactNode` | | A second line under the title, in secondary text. |
29
+ | `media` | `ReactNode` | | Something at the start of the row: an Avatar, an icon (hidden from screen readers with aria-hidden), a thumbnail. |
30
+ | `meta` | `ReactNode` | | Right-aligned monospaced text: a time, a price, a count. |
31
+ | `actions` | `ReactNode` | | Controls at the end. Each one can still be focused and clicked on its own. |
32
+ | `href` | `string` | | Makes the row one link to this address, named by the title. |
33
+ | `renderLink` | `(props: LinkRenderProps) => ReactNode` | `a plain <a>` | Draws the row link when href is set. Pass your router's link here. It has to pass className through. |
34
+ | `onActivate` | `() => void` | | Makes the row one button, named by the title. Ignored when href is set. |
35
+ | `as` | `'li' \| 'div'` | `'li'` | li inside a List. div for a row on its own. |
36
+ | `dividers (List)` | `boolean` | `true` | A thin line between rows. |
37
+ | `className` | `string` | | Extra CSS classes, merged with the outer element's own classes on List or ListItem. |
38
+ | `ref` | `Ref<HTMLElement>` | | Passed through to the ul (List) or the row element (ListItem). |
39
+
40
+ ## Examples
41
+
42
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
43
+
44
+ ### Sessions: plain rows with one action each
45
+
46
+ ```tsx
47
+ function DeviceIcon({ icon: Icon }: { icon: typeof Monitor }) {
48
+ return (
49
+ <span className="flex size-9 items-center justify-center border border-[var(--color-border-hairline-strong)] text-[var(--color-text-secondary)]">
50
+ <Icon aria-hidden="true" className="size-4" />
51
+ </span>
52
+ )
53
+ }
54
+
55
+ function SessionsExample() {
56
+ const [sessions, setSessions] = useState([
57
+ { id: 'win', device: 'Chrome on Windows', where: 'Brasov, Romania', when: 'Now', icon: Monitor },
58
+ { id: 'ios', device: 'Safari on iPhone', where: 'Brasov, Romania', when: '2 h ago', icon: Smartphone },
59
+ { id: 'tab', device: 'Firefox on Android tablet', where: 'Cluj-Napoca, Romania', when: '3 d ago', icon: Tablet },
60
+ ])
61
+ return (
62
+ <div className="w-full max-w-xl">
63
+ <List aria-label="Signed-in sessions">
64
+ {sessions.map(session => (
65
+ <ListItem
66
+ key={session.id}
67
+ media={<DeviceIcon icon={session.icon} />}
68
+ title={session.device}
69
+ description={session.where}
70
+ meta={session.when}
71
+ actions={
72
+ session.id === 'win' ? (
73
+ <Badge tone="ok">This device</Badge>
74
+ ) : (
75
+ <Button
76
+ size="sm"
77
+ variant="secondary"
78
+ aria-label={`Sign out of ${session.device}`}
79
+ onClick={() => setSessions(all => all.filter(s => s.id !== session.id))}
80
+ >
81
+ Sign out
82
+ </Button>
83
+ )
84
+ }
85
+ />
86
+ ))}
87
+ </List>
88
+ </div>
89
+ )
90
+ }
91
+
92
+ <SessionsExample />
93
+ ```
94
+
95
+ ### Notifications: each row one link, with an archive action beside it
96
+
97
+ ```tsx
98
+ <div className="w-full max-w-xl">
99
+ <List aria-label="Notifications">
100
+ <ListItem
101
+ className="px-2"
102
+ title="Order 1042 shipped"
103
+ description="Your carbine replica left the depot."
104
+ meta="09:14"
105
+ href="#order-1042"
106
+ actions={
107
+ <IconButton label="Archive: Order 1042 shipped" variant="ghost" size="sm">
108
+ <Archive />
109
+ </IconButton>
110
+ }
111
+ />
112
+ <ListItem
113
+ className="px-2"
114
+ title="Back in stock"
115
+ description="The holo sight on your wishlist is available again."
116
+ meta="Yesterday"
117
+ href="#wishlist"
118
+ actions={
119
+ <IconButton label="Archive: Back in stock" variant="ghost" size="sm">
120
+ <Archive />
121
+ </IconButton>
122
+ }
123
+ />
124
+ <ListItem
125
+ className="px-2"
126
+ title="Training moved"
127
+ description="Saturday’s training starts at 10:00, not 09:00."
128
+ meta="Mon"
129
+ href="#events"
130
+ actions={
131
+ <IconButton label="Archive: Training moved" variant="ghost" size="sm">
132
+ <Archive />
133
+ </IconButton>
134
+ }
135
+ />
136
+ </List>
137
+ </div>
138
+ ```
139
+
140
+ ### Team members: avatars, the whole row a button
141
+
142
+ ```tsx
143
+ function TeamExample() {
144
+ const [chosen, setChosen] = useState<string | null>(null)
145
+ const team = [
146
+ { name: 'Ilse Varga', email: 'ilse@hexline.example', role: 'Captain' },
147
+ { name: 'Tomas Reyes', email: 'tomas@hexline.example', role: 'Medic' },
148
+ { name: 'Ana Popescu', email: 'ana@hexline.example', role: 'Scout' },
149
+ ]
150
+ return (
151
+ <div className="w-full max-w-xl">
152
+ <List aria-label="Squad members">
153
+ {team.map(member => (
154
+ <ListItem
155
+ key={member.email}
156
+ className="px-2"
157
+ media={<Avatar name={member.name} />}
158
+ title={member.name}
159
+ description={member.email}
160
+ meta={member.role}
161
+ onActivate={() => setChosen(member.name)}
162
+ />
163
+ ))}
164
+ </List>
165
+ <p className="mt-3 text-sm text-[var(--color-text-secondary)]" aria-live="polite">
166
+ {chosen ? `Opened ${chosen}.` : 'Choose a member to open their profile.'}
167
+ </p>
168
+ </div>
169
+ )
170
+ }
171
+
172
+ <TeamExample />
173
+ ```
174
+
175
+ ### A standalone row, outside a List
176
+
177
+ ```tsx
178
+ <div className="w-full max-w-xl">
179
+ <ListItem as="div" title="Spare magazine, 120 rounds" description="Mid-cap, steel" meta="€14.90" />
180
+ </div>
181
+ ```
182
+
183
+ ## Keyboard
184
+
185
+ | Keys | Result |
186
+ |---|---|
187
+ | `Tab` | Moves to the row’s link or button, then to each of its actions, then to the next row. |
188
+ | `Enter` | Follows the row’s link, or presses its button. |
189
+ | `Space` | Presses the row’s button (a link row ignores Space, like any link). |
190
+
191
+ ## Do
192
+
193
+ - Put rows in a List, and give the List an aria-label (a name only screen readers hear) when no heading names it.
194
+ - Use href when the row opens a page, and onActivate when it does something right there.
195
+ - Keep actions to one or two. Put the rest in a DropdownMenu.
196
+ - When an action repeats on every row, name it after its row ("Sign out of Chrome on Windows"), with aria-label or the IconButton label.
197
+
198
+ ## Don't
199
+
200
+ - Don't make a row clickable with onClick on the row. Use href or onActivate.
201
+ - Don't wrap a ListItem in a link or a button.
202
+ - Don't use it for data people compare across columns. That's what DataTable is for.
203
+ - Don't put long paragraphs in description. People scan a row, they don't read it.
204
+
205
+ ## Common mistakes
206
+
207
+ Wrong:
208
+
209
+ ```tsx
210
+ <li onClick={open}>…<Button>Remove</Button></li>
211
+ ```
212
+
213
+ Right:
214
+
215
+ ```tsx
216
+ <ListItem title="Ilse Varga" onActivate={open} actions={<Button>Remove</Button>} />
217
+ ```
218
+
219
+ A clickable li isn't a control. It can't take keyboard focus, and screen readers read it as plain text. onActivate (or href) turns the title into a real button (or link) that covers the row, and Remove keeps its own tab stop.
220
+
221
+ Wrong:
222
+
223
+ ```tsx
224
+ <a href="/orders/1"><ListItem title="Order shipped" actions={<Button>Archive</Button>} /></a>
225
+ ```
226
+
227
+ Right:
228
+
229
+ ```tsx
230
+ <ListItem title="Order shipped" href="/orders/1" actions={<Button>Archive</Button>} />
231
+ ```
232
+
233
+ A button inside a link is invalid HTML, and screen readers announce the two as one merged control. href stretches the link over the row without wrapping the actions.
234
+
235
+ ## Anatomy
236
+
237
+ - **List**: A ul with role="list" written out, so screen readers announce how many items there are even without bullets. Thin dividers between rows by default.
238
+ - **Media**: Optional, at the start: an Avatar, an icon, a thumbnail. Centred against the text.
239
+ - **Title and description**: The title is primary text, semibold. With href or onActivate it becomes the row’s one link or button, stretched over the row and underlined on hover. The description is secondary text, 14px (pixels).
240
+ - **Meta**: Optional, right-aligned monospaced text with tabular figures (digits all the same width, so numbers line up): a time, a price, a count.
241
+ - **Actions**: Optional controls at the end, layered above the stretched link, each with its own tab stop. On a narrow screen, meta and actions wrap under the text.
@@ -0,0 +1,72 @@
1
+ # LoadingAnimation
2
+
3
+ > A loading state that fills a whole panel, drawn as an animated instrument dial in WebGL (the browser's graphics engine for drawing with the graphics card). The three dials follow the same rules. They draw themselves from the outside in, and their structure drifts. One mechanism clicks to its next detent using the system's snap easing (the motion where things arrive a little large and settle), and the signal accent colour only appears on the part that shows progress.
4
+
5
+ - **Best for:** A wait that takes over a whole panel or page, like a route loading, a report building or a first sync.
6
+ - **Not for:** A wait inside a button or a line of text: use Spinner. A wait with a known end: use Progress.
7
+ - Atom. Source: `src/components/atoms/loading-animation/LoadingAnimation.tsx`
8
+ - Rendered docs page: `/components/loading-animation`
9
+
10
+ ## Import
11
+
12
+ ```tsx
13
+ import { LoadingAnimation } from 'src/components/atoms/loading-animation/LoadingAnimation'
14
+ ```
15
+
16
+ Paths are from the Division DS repository root. Installed with shadcn they start `src/division-ds/` instead of `src/` (`src/division-ds/components/...`); import them relative to your file.
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ |---|---|---|---|
22
+ | `variant` | `'aperture' \| 'gyro' \| 'reticle'` | `'aperture'` | Aperture: shaded blades stepping round a spinning core. Gyro: nested rings turning against each other. Reticle: a progress ring filling up from a marker at 12 o’clock. |
23
+ | `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | A 96px, 160px or 240px square (px means pixels). It shrinks with its container below that size, but never grows. |
24
+ | `label` | `string` | | Status text that is hidden on screen. Passing it turns the loader into a live region, an area screen readers announce when it changes. |
25
+ | `className` | `string` | | Merged with the component classes. If two clash, the later Tailwind utility wins (Tailwind is the CSS framework this system styles with). |
26
+ | `ref` | `Ref<HTMLDivElement>` | | Passed through to the root div. |
27
+ | `...rest` | `Omit<HTMLAttributes<HTMLDivElement>, 'role'>` | | Every native div prop except role, for the same reason as Spinner: label is the only thing that sets role="status". |
28
+
29
+ ## Examples
30
+
31
+ Each is compiled and rendered on the docs page. Imports are omitted; see above.
32
+
33
+ ### Three dials, md
34
+
35
+ ```tsx
36
+ <LoadingAnimation variant="aperture" label="Loading field kits" />
37
+ <LoadingAnimation variant="gyro" label="Syncing manifest" />
38
+ <LoadingAnimation variant="reticle" label="Acquiring position" />
39
+ ```
40
+
41
+ ### Aperture, three sizes
42
+
43
+ ```tsx
44
+ <LoadingAnimation variant="aperture" size="sm" label="Loading field kits" />
45
+ <LoadingAnimation variant="aperture" size="md" label="Loading field kits" />
46
+ <LoadingAnimation variant="aperture" size="lg" label="Loading field kits" />
47
+ ```
48
+
49
+ ## Keyboard
50
+
51
+ Not focusable. No keyboard behaviour of its own.
52
+
53
+ ## Do
54
+
55
+ - Use it for a wait that takes over a panel or a page, like a route loading, a report building or a first sync.
56
+ - Pass label, and say what is loading: ‘Loading field kits’, not ‘Loading’.
57
+ - Pick one dial for each product surface and stick with it. The three are a family. They aren't meant to rotate.
58
+
59
+ ## Don't
60
+
61
+ - Don't put it in a button or a line of text. That's Spinner, which doesn't use up a WebGL context.
62
+ - Don't show more than a handful at once. Each one uses a WebGL context (one slot of the browser's graphics engine), and browsers cap how many you get.
63
+ - Don't rely on the motion. When someone has reduced motion turned on, it shows one still frame, so the label has to carry the meaning.
64
+ - Don't use it for a wait with a known end. That's Progress.
65
+
66
+ ## Anatomy
67
+
68
+ - **div**: The root: a square box at the size you pick. It gets role="status", which makes screen readers announce it, only when it has a label.
69
+ - **Stage**: A layer that holds the WebGL canvas, hidden from screen readers with aria-hidden (an ARIA attribute, from Accessible Rich Internet Applications). It fades in once the first frame is drawn.
70
+ - **Dial**: Rings, tick crowns and arcs in text.primary, text.secondary, border.control and border.hairline.strong. One moving part is in accent.signal.fill.
71
+ - **Fallback**: If WebGL isn't available, the Conveyor spinner shows in the same box. It's aria-hidden, so the label is still read only once.
72
+ - **sr-only label**: The text screen readers announce (sr-only means it's for screen readers only). It's only there when you pass label.