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.
- package/AGENTS.md +23 -3
- package/README.md +11 -0
- package/division-ds-0.2.1.tgz +0 -0
- package/package.json +2 -2
- package/public/docs/components/accordion.md +207 -0
- package/public/docs/components/ai-label.md +90 -0
- package/public/docs/components/alert-dialog.md +292 -0
- package/public/docs/components/alert.md +175 -0
- package/public/docs/components/approval-request.md +148 -0
- package/public/docs/components/auth-form.md +132 -0
- package/public/docs/components/avatar-group.md +152 -0
- package/public/docs/components/avatar.md +179 -0
- package/public/docs/components/badge.md +178 -0
- package/public/docs/components/breadcrumbs.md +112 -0
- package/public/docs/components/button.md +204 -0
- package/public/docs/components/card.md +257 -0
- package/public/docs/components/carousel.md +130 -0
- package/public/docs/components/cart.md +172 -0
- package/public/docs/components/chart-panel.md +358 -0
- package/public/docs/components/chat-message.md +157 -0
- package/public/docs/components/chat-thread.md +244 -0
- package/public/docs/components/checkbox.md +149 -0
- package/public/docs/components/checkout-form.md +128 -0
- package/public/docs/components/command-palette.md +147 -0
- package/public/docs/components/data-table.md +401 -0
- package/public/docs/components/description-list.md +103 -0
- package/public/docs/components/dialog.md +197 -0
- package/public/docs/components/divider.md +97 -0
- package/public/docs/components/dropdown-menu.md +222 -0
- package/public/docs/components/empty-state.md +145 -0
- package/public/docs/components/footer.md +397 -0
- package/public/docs/components/form-field.md +216 -0
- package/public/docs/components/icon-button.md +127 -0
- package/public/docs/components/input.md +200 -0
- package/public/docs/components/kanban-board.md +300 -0
- package/public/docs/components/kbd.md +72 -0
- package/public/docs/components/link.md +98 -0
- package/public/docs/components/list-item.md +241 -0
- package/public/docs/components/loading-animation.md +72 -0
- package/public/docs/components/multi-select.md +202 -0
- package/public/docs/components/navbar.md +144 -0
- package/public/docs/components/newsletter-form.md +112 -0
- package/public/docs/components/page-header.md +151 -0
- package/public/docs/components/pagination.md +212 -0
- package/public/docs/components/product-card.md +172 -0
- package/public/docs/components/progress.md +101 -0
- package/public/docs/components/prompt-input.md +151 -0
- package/public/docs/components/quantity-stepper.md +140 -0
- package/public/docs/components/radio.md +177 -0
- package/public/docs/components/rating.md +106 -0
- package/public/docs/components/score-ring.md +95 -0
- package/public/docs/components/search.md +116 -0
- package/public/docs/components/section-heading.md +113 -0
- package/public/docs/components/segmented-control.md +126 -0
- package/public/docs/components/select.md +224 -0
- package/public/docs/components/sheet.md +145 -0
- package/public/docs/components/sidebar.md +193 -0
- package/public/docs/components/skeleton.md +135 -0
- package/public/docs/components/slider.md +114 -0
- package/public/docs/components/sparkline.md +128 -0
- package/public/docs/components/spinner.md +98 -0
- package/public/docs/components/stat.md +106 -0
- package/public/docs/components/status-tracker.md +145 -0
- package/public/docs/components/stepper.md +147 -0
- package/public/docs/components/switch.md +153 -0
- package/public/docs/components/tabs.md +180 -0
- package/public/docs/components/tag.md +105 -0
- package/public/docs/components/textarea.md +148 -0
- package/public/docs/components/timeline.md +152 -0
- package/public/docs/components/toast.md +194 -0
- package/public/docs/components/tool-call.md +107 -0
- package/public/docs/components/tooltip.md +108 -0
- package/public/manifest/components.json +198 -15
- package/public/manifest/tokens.json +75 -5
- package/scripts/division-ds.mjs +78 -42
- package/scripts/lib/agent-docs.mjs +80 -6
- package/scripts/lib/ai-copy.mjs +120 -0
- package/scripts/lib/button-spacing.mjs +5 -5
- package/scripts/lib/check-text.mjs +57 -0
- package/scripts/lib/contrast.mjs +111 -0
- package/scripts/lib/host-inline.mjs +59 -0
- package/scripts/lib/mcp-server.mjs +116 -0
- package/scripts/lib/mcp-tools.mjs +266 -0
- package/scripts/lib/props-validator.mjs +3 -2
- package/scripts/lib/rules-installer.mjs +42 -4
- package/scripts/lib/token-discipline.mjs +10 -3
- package/skills/division-ds/SKILL.md +90 -0
- package/skills/division-ds/commands/add.md +25 -0
- package/skills/division-ds/commands/build.md +46 -0
- package/skills/division-ds/commands/fix.md +28 -0
- package/skills/division-ds/commands/review.md +25 -0
- package/skills/division-ds/topics/ai.md +8 -0
- package/skills/division-ds/topics/colour.md +11 -0
- package/skills/division-ds/topics/feedback.md +9 -0
- package/skills/division-ds/topics/forms.md +22 -0
- package/skills/division-ds/topics/layout.md +36 -0
- package/skills/division-ds/topics/motion.md +10 -0
- package/skills/division-ds/topics/overlays.md +9 -0
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Sidebar
|
|
2
|
+
|
|
3
|
+
> The navigation down the side of an app. It holds groups of pages, each page with an icon and an optional count. Groups can fold away, and there is room for a header and an account line.
|
|
4
|
+
|
|
5
|
+
- **Best for:** App navigation down the side: grouped pages with icons and counts.
|
|
6
|
+
- **Not for:** A site header with a few links: use Navbar.
|
|
7
|
+
- Organism. Source: `src/components/organisms/sidebar/Sidebar.tsx`
|
|
8
|
+
- Rendered docs page: `/components/sidebar`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Sidebar } from 'src/components/organisms/sidebar/Sidebar'
|
|
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
|
+
| `label` | `string` | | Required. The name screen readers give the navigation region. |
|
|
23
|
+
| `groups` | `SidebarGroup[]` | | Required. Each group has title, icon, items, collapsible and defaultOpen. |
|
|
24
|
+
| `header` | `ReactNode` | | Sits above the groups: a product name, a workspace switcher. |
|
|
25
|
+
| `footer` | `ReactNode` | | Pinned below the groups when the Sidebar has a height (h-full in a column layout): the signed-in account, a help link. |
|
|
26
|
+
| `renderLink` | `(props: LinkRenderProps) => ReactNode` | `a plain anchor` | Pass your router's link component here. Map href to its own prop, and pass className, aria-current and onClick through. |
|
|
27
|
+
| `className` | `string` | | Added to the root classes. Where two Tailwind utility classes clash, the later one wins. |
|
|
28
|
+
| `ref` | `Ref<HTMLElement>` | | The nav element. |
|
|
29
|
+
|
|
30
|
+
## Types and helpers
|
|
31
|
+
|
|
32
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
export type SidebarItem = {
|
|
36
|
+
label: string
|
|
37
|
+
href: string
|
|
38
|
+
/** An icon, drawn aria-hidden; the label carries the meaning. */
|
|
39
|
+
icon?: ReactNode
|
|
40
|
+
current?: boolean
|
|
41
|
+
/** A number shown at the end of the item and read as part of its name ("Orders 12"). */
|
|
42
|
+
count?: number
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export type SidebarGroup = {
|
|
48
|
+
/** Also the React key for this group; unique within a Sidebar. */
|
|
49
|
+
title: string
|
|
50
|
+
/** Drawn before the title, aria-hidden; the title alone names the group. */
|
|
51
|
+
icon?: ReactNode
|
|
52
|
+
items: SidebarItem[]
|
|
53
|
+
/** @default true */
|
|
54
|
+
collapsible?: boolean
|
|
55
|
+
/** Ignored when the group holds the current item, which always starts open. @default true */
|
|
56
|
+
defaultOpen?: boolean
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Examples
|
|
61
|
+
|
|
62
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
63
|
+
|
|
64
|
+
### Dashboard navigation
|
|
65
|
+
|
|
66
|
+
```tsx
|
|
67
|
+
const GROUPS: SidebarGroup[] = [
|
|
68
|
+
{
|
|
69
|
+
title: 'Operations',
|
|
70
|
+
icon: <Warehouse />,
|
|
71
|
+
items: [
|
|
72
|
+
{ label: 'Overview', href: '#overview', icon: <LayoutDashboard /> },
|
|
73
|
+
{ label: 'Orders', href: '#orders', icon: <Package />, current: true, count: 12 },
|
|
74
|
+
{ label: 'Shipments', href: '#shipments', icon: <Truck /> },
|
|
75
|
+
{ label: 'Returns', href: '#returns', icon: <Inbox />, count: 3 },
|
|
76
|
+
],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
title: 'Catalogue',
|
|
80
|
+
icon: <Boxes />,
|
|
81
|
+
defaultOpen: false,
|
|
82
|
+
items: [
|
|
83
|
+
{ label: 'Products', href: '#products', icon: <Box /> },
|
|
84
|
+
{ label: 'Inventory', href: '#inventory', icon: <Map /> },
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: 'Account',
|
|
89
|
+
icon: <UserCog />,
|
|
90
|
+
collapsible: false,
|
|
91
|
+
items: [
|
|
92
|
+
{ label: 'Settings', href: '#settings', icon: <Settings /> },
|
|
93
|
+
{ label: 'Team', href: '#team', icon: <Users /> },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
]
|
|
97
|
+
|
|
98
|
+
const HEADER = <p className="font-display font-semibold">Hexline ops</p>
|
|
99
|
+
|
|
100
|
+
const FOOTER = <p className="text-xs text-[var(--color-text-secondary)]">Signed in as Ana Pop</p>
|
|
101
|
+
|
|
102
|
+
<div className="max-w-64">
|
|
103
|
+
<Sidebar label="Dashboard navigation" groups={GROUPS} header={HEADER} footer={FOOTER} />
|
|
104
|
+
</div>
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### In a sheet on a narrow screen: picking a link closes the sheet, through renderLink
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
const GROUPS: SidebarGroup[] = [
|
|
111
|
+
{
|
|
112
|
+
title: 'Operations',
|
|
113
|
+
icon: <Warehouse />,
|
|
114
|
+
items: [
|
|
115
|
+
{ label: 'Overview', href: '#overview', icon: <LayoutDashboard /> },
|
|
116
|
+
{ label: 'Orders', href: '#orders', icon: <Package />, current: true, count: 12 },
|
|
117
|
+
{ label: 'Shipments', href: '#shipments', icon: <Truck /> },
|
|
118
|
+
{ label: 'Returns', href: '#returns', icon: <Inbox />, count: 3 },
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
title: 'Catalogue',
|
|
123
|
+
icon: <Boxes />,
|
|
124
|
+
defaultOpen: false,
|
|
125
|
+
items: [
|
|
126
|
+
{ label: 'Products', href: '#products', icon: <Box /> },
|
|
127
|
+
{ label: 'Inventory', href: '#inventory', icon: <Map /> },
|
|
128
|
+
],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
title: 'Account',
|
|
132
|
+
icon: <UserCog />,
|
|
133
|
+
collapsible: false,
|
|
134
|
+
items: [
|
|
135
|
+
{ label: 'Settings', href: '#settings', icon: <Settings /> },
|
|
136
|
+
{ label: 'Team', href: '#team', icon: <Users /> },
|
|
137
|
+
],
|
|
138
|
+
},
|
|
139
|
+
]
|
|
140
|
+
|
|
141
|
+
const HEADER = <p className="font-display font-semibold">Hexline ops</p>
|
|
142
|
+
|
|
143
|
+
const FOOTER = <p className="text-xs text-[var(--color-text-secondary)]">Signed in as Ana Pop</p>
|
|
144
|
+
|
|
145
|
+
const closeOnChoose = ({ onClick, ...props }: LinkRenderProps) => (
|
|
146
|
+
<SheetClose asChild type={undefined}>
|
|
147
|
+
<a {...props} onClick={onClick} />
|
|
148
|
+
</SheetClose>
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
<Sheet>
|
|
152
|
+
<SheetTrigger asChild>
|
|
153
|
+
<Button variant="secondary">Open navigation</Button>
|
|
154
|
+
</SheetTrigger>
|
|
155
|
+
<SheetContent side="left" title="Navigation">
|
|
156
|
+
<Sidebar
|
|
157
|
+
label="Dashboard navigation, in a sheet"
|
|
158
|
+
groups={GROUPS}
|
|
159
|
+
header={HEADER}
|
|
160
|
+
footer={FOOTER}
|
|
161
|
+
renderLink={closeOnChoose}
|
|
162
|
+
/>
|
|
163
|
+
</SheetContent>
|
|
164
|
+
</Sheet>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Keyboard
|
|
168
|
+
|
|
169
|
+
| Keys | Result |
|
|
170
|
+
|---|---|
|
|
171
|
+
| `Tab` | Moves through each group title that can fold, then each visible item. |
|
|
172
|
+
| `Enter` | Follows an item, or folds and unfolds a group. |
|
|
173
|
+
| `Space` | Folds and unfolds a group. |
|
|
174
|
+
|
|
175
|
+
## Do
|
|
176
|
+
|
|
177
|
+
- Only show counts for things that need attention: open orders, not total products.
|
|
178
|
+
- Keep the group that holds the current page open.
|
|
179
|
+
- On a narrow screen, put the whole sidebar in a Sheet behind a button.
|
|
180
|
+
|
|
181
|
+
## Don't
|
|
182
|
+
|
|
183
|
+
- Don't nest groups inside groups.
|
|
184
|
+
- Don't show a count of zero. Leave it off.
|
|
185
|
+
- Don't rely on icons alone. Every item keeps its label.
|
|
186
|
+
|
|
187
|
+
## Anatomy
|
|
188
|
+
|
|
189
|
+
- **nav**: The navigation region. Screen readers call it by the label prop.
|
|
190
|
+
- **Group title**: A disclosure button (one that shows and hides the list) when the group can fold, plain text when it can't. It's text.secondary, so it still reads on a raised panel. Its list opens and closes by animating its height. Opening also fades and slides each item in, each one a little later than the one before (capped, so a long group never feels slow). Closing fades everything out together, all at once. If someone has asked their system for less motion (prefers-reduced-motion: reduce), both happen instantly.
|
|
191
|
+
- **Item**: An icon, a label and an optional count. The current page gets the fill, plus aria-current, an ARIA attribute (Accessible Rich Internet Applications, the labels screen readers rely on) that tells screen readers this is the page you are on.
|
|
192
|
+
- **Header**: Sits above the groups: a product name, a workspace switcher.
|
|
193
|
+
- **Footer**: Pinned below the groups: the signed-in account, a help link.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Skeleton
|
|
2
|
+
|
|
3
|
+
> A loading placeholder shaped like the content it stands in for: lines of text, a cut-corner block, or an Avatar’s footprint. The shapes themselves are silent. SkeletonGroup is what tells people the region is loading.
|
|
4
|
+
|
|
5
|
+
- **Best for:** A region whose content is loading, shown as placeholders in its shape inside one SkeletonGroup.
|
|
6
|
+
- **Not for:** A busy button: use Button loading. A wait under a few hundred milliseconds: show nothing.
|
|
7
|
+
- Atom. Source: `src/components/atoms/skeleton/Skeleton.tsx`
|
|
8
|
+
- Rendered docs page: `/components/skeleton`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Skeleton, SkeletonGroup } from 'src/components/atoms/skeleton/Skeleton'
|
|
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
|
+
| `shape` | `'text' \| 'block' \| 'avatar'` | `'text'` | What the placeholder stands in for. |
|
|
23
|
+
| `lines` | `number` | `1` | Text only. The number of lines. Anything below 1 counts as 1. |
|
|
24
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Avatar only. Matches Avatar’s size prop. |
|
|
25
|
+
| `className` | `string` | | Merged with the component classes. A block takes its width and height from here. |
|
|
26
|
+
| `ref` | `Ref<HTMLDivElement>` | | Passed through to the root element. |
|
|
27
|
+
| `...rest` | `Omit<HTMLAttributes<HTMLDivElement>, 'children'>` | | Every native div prop except children, because a skeleton draws its own shapes. |
|
|
28
|
+
| `label (SkeletonGroup)` | `string` | `'Loading…'` | The hidden words a screen reader hears instead of the shapes. |
|
|
29
|
+
| `className (SkeletonGroup)` | `string` | | Lay the placeholders out here, the way the real content will be laid out. |
|
|
30
|
+
| `ref (SkeletonGroup)` | `Ref<HTMLDivElement>` | | Passed through to the container. |
|
|
31
|
+
| `...rest (SkeletonGroup)` | `HTMLAttributes<HTMLDivElement>` | | Every native div prop. |
|
|
32
|
+
|
|
33
|
+
## Examples
|
|
34
|
+
|
|
35
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
36
|
+
|
|
37
|
+
### Shapes: text lines, a block, the Avatar footprints
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
<div className="flex w-full flex-col gap-6">
|
|
41
|
+
<Skeleton shape="text" lines={3} className="max-w-md" />
|
|
42
|
+
<Skeleton shape="block" className="h-24 w-full max-w-md" />
|
|
43
|
+
<div className="flex items-center gap-3">
|
|
44
|
+
<Skeleton shape="avatar" size="sm" />
|
|
45
|
+
<Skeleton shape="avatar" size="md" />
|
|
46
|
+
<Skeleton shape="avatar" size="lg" />
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### A card loading: image, title, two lines of copy
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<SkeletonGroup label="Loading replicas" className="grid w-full max-w-md gap-4 sm:grid-cols-2">
|
|
55
|
+
{[0, 1].map(i => (
|
|
56
|
+
<Card key={i}>
|
|
57
|
+
<CardBody className="flex flex-col gap-4">
|
|
58
|
+
<Skeleton shape="block" className="aspect-[4/3] w-full" />
|
|
59
|
+
<Skeleton shape="text" className="w-2/3" />
|
|
60
|
+
<Skeleton shape="text" lines={2} />
|
|
61
|
+
</CardBody>
|
|
62
|
+
</Card>
|
|
63
|
+
))}
|
|
64
|
+
</SkeletonGroup>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Table rows loading
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<SkeletonGroup label="Loading orders" className="w-full max-w-xl">
|
|
71
|
+
<div className="flex flex-col divide-y divide-[var(--color-border-hairline)]">
|
|
72
|
+
{[0, 1, 2].map(i => (
|
|
73
|
+
<div key={i} className="grid grid-cols-[1fr_2fr_1fr] items-center gap-4 py-3">
|
|
74
|
+
<Skeleton shape="text" />
|
|
75
|
+
<Skeleton shape="text" className="w-4/5" />
|
|
76
|
+
<Skeleton shape="text" className="w-1/2 justify-self-end" />
|
|
77
|
+
</div>
|
|
78
|
+
))}
|
|
79
|
+
</div>
|
|
80
|
+
</SkeletonGroup>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### A profile row loading
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
<SkeletonGroup label="Loading the squad roster" className="flex w-full max-w-sm items-center gap-3">
|
|
87
|
+
<Skeleton shape="avatar" size="md" />
|
|
88
|
+
<div className="flex flex-1 flex-col gap-2">
|
|
89
|
+
<Skeleton shape="text" className="w-1/2" />
|
|
90
|
+
<Skeleton shape="text" className="w-1/3" />
|
|
91
|
+
</div>
|
|
92
|
+
</SkeletonGroup>
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Keyboard
|
|
96
|
+
|
|
97
|
+
Not focusable. No keyboard behaviour of its own.
|
|
98
|
+
|
|
99
|
+
## Do
|
|
100
|
+
|
|
101
|
+
- Wrap one region’s placeholders in one SkeletonGroup, and remove it when the content arrives.
|
|
102
|
+
- Name what’s loading in the group label: Loading orders, not just Loading.
|
|
103
|
+
- Match the layout of the real content, so nothing jumps when it replaces the placeholder.
|
|
104
|
+
- Use shape="avatar" with the size the real Avatar will have.
|
|
105
|
+
|
|
106
|
+
## Don't
|
|
107
|
+
|
|
108
|
+
- Don't round a skeleton or draw a circle. The system has square corners, and Avatar is a square too.
|
|
109
|
+
- Don't put a skeleton on a busy button or control. A busy Button takes loading.
|
|
110
|
+
- Don't nest one SkeletonGroup inside another. One busy region gets one announcement.
|
|
111
|
+
- Don't show a skeleton for a wait under a few hundred milliseconds. The flash is worse than the wait.
|
|
112
|
+
|
|
113
|
+
## Common mistakes
|
|
114
|
+
|
|
115
|
+
Wrong:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
<div><Skeleton lines={3} /></div>
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Right:
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
<SkeletonGroup label="Loading orders"><Skeleton lines={3} /></SkeletonGroup>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Every shape is aria-hidden, an ARIA (Accessible Rich Internet Applications) attribute that hides it from assistive tech. So without the group, a screen reader, the software that reads the page aloud, hears nothing at all while the region loads.
|
|
128
|
+
|
|
129
|
+
## Anatomy
|
|
130
|
+
|
|
131
|
+
- **Shape**: A border.hairline fill with a border.hairline-strong band sweeping across it every 1.6s. It’s see-through, so it reads on the page, a Card or an overlay. aria-hidden.
|
|
132
|
+
- **Text lines**: 12px (pixel) bars, 8px apart, full width. When there are several, the last one is three fifths wide.
|
|
133
|
+
- **Block**: Any rectangle, sized with className, with the 8px cut corner.
|
|
134
|
+
- **Avatar**: An Avatar’s exact footprint: 24, 36 or 48px, with the corner cut at md and lg only.
|
|
135
|
+
- **SkeletonGroup**: The container. It has aria-busy="true", which tells assistive tech the region is busy, and a visually hidden label that reads Loading… unless you name what’s loading.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Slider
|
|
2
|
+
|
|
3
|
+
> A value or a range picked on a track. Each thumb (the handle you drag) gets its own name. If a range slider shared one name, a screen-reader user (someone using software that reads the page aloud) would get two controls called the same thing.
|
|
4
|
+
|
|
5
|
+
- **Best for:** A value or range on a track, where the rough position matters and the value is shown.
|
|
6
|
+
- **Not for:** A precise value with no other way to enter it. Use Input.
|
|
7
|
+
- Atom. Source: `src/components/atoms/slider/Slider.tsx`
|
|
8
|
+
- Rendered docs page: `/components/slider`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Slider } from 'src/components/atoms/slider/Slider'
|
|
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
|
+
| `label` | `string` | | Required. Applied to each thumb. A single thumb gets it as-is, and a range adds "minimum" / "maximum" to the end. |
|
|
23
|
+
| `value` | `number[]` | | The value(s), when you control them from outside. One number makes a single slider; two make a range. |
|
|
24
|
+
| `defaultValue` | `number[]` | | The starting value(s), when the slider manages them itself (uncontrolled). |
|
|
25
|
+
| `onValueChange` | `(value: number[]) => void` | | Fires as the thumb moves. |
|
|
26
|
+
| `min` | `number` | `0` | The lowest value. |
|
|
27
|
+
| `max` | `number` | `100` | The highest value. |
|
|
28
|
+
| `step` | `number` | `1` | The increment for both snapping while dragging and the arrow keys. |
|
|
29
|
+
| `disabled` | `boolean` | | Takes it out of the tab order and blocks input. |
|
|
30
|
+
| `formatValue` | `(value: number) => string` | | One function feeds both the readout and aria-valuetext (the ARIA, or Accessible Rich Internet Applications, attribute a screen reader speaks as the value). So the spoken value and the printed one can’t drift apart, whether the slider is controlled or not. |
|
|
31
|
+
| `showValue` | `boolean` | `false` | Shows the current value(s) as a readout. |
|
|
32
|
+
| `className` | `string` | | Merged with the outer wrapper's classes. Later Tailwind utilities (Tailwind is the CSS framework the system uses) win. |
|
|
33
|
+
| `ref` | `Ref<HTMLSpanElement>` | | Passed through to the Radix Slider root. Radix is the library of accessible building blocks this sits on. |
|
|
34
|
+
| `...rest` | `Omit<Slider.Root props, 'aria-label' \| 'aria-labelledby' \| 'children'>` | | Every Radix Slider.Root prop except those three, spread onto the root. aria-label and aria-labelledby are left out because the accessible name lives on each thumb, where label puts it. One on the root would do nothing at all, not override anything. children is typed away because this component draws its own Track/Range/Thumbs, and a children prop would be thrown away without any warning. |
|
|
35
|
+
|
|
36
|
+
## Examples
|
|
37
|
+
|
|
38
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
39
|
+
|
|
40
|
+
### Single value
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
function SingleValueLive() {
|
|
44
|
+
const [weight, setWeight] = useState([8])
|
|
45
|
+
return (
|
|
46
|
+
<div className="w-full max-w-sm">
|
|
47
|
+
<Slider
|
|
48
|
+
label="Kit weight"
|
|
49
|
+
value={weight}
|
|
50
|
+
onValueChange={setWeight}
|
|
51
|
+
max={20}
|
|
52
|
+
formatValue={v => `${v} kg`}
|
|
53
|
+
showValue
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
<SingleValueLive />
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Range
|
|
63
|
+
|
|
64
|
+
```tsx
|
|
65
|
+
<div className="w-full max-w-sm">
|
|
66
|
+
<Slider label="Weight range" defaultValue={[4, 12]} max={20} />
|
|
67
|
+
</div>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Stepped
|
|
71
|
+
|
|
72
|
+
```tsx
|
|
73
|
+
<div className="w-full max-w-sm">
|
|
74
|
+
<Slider label="BB bottles" defaultValue={[5]} max={20} step={5} />
|
|
75
|
+
</div>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Disabled
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
<div className="w-full max-w-sm">
|
|
82
|
+
<Slider label="Locked ration" defaultValue={[8]} max={20} disabled />
|
|
83
|
+
</div>
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Keyboard
|
|
87
|
+
|
|
88
|
+
| Keys | Result |
|
|
89
|
+
|---|---|
|
|
90
|
+
| `→` `↑` | Goes up by one step. |
|
|
91
|
+
| `←` `↓` | Goes down by one step. |
|
|
92
|
+
| `Page Up` | Goes up by a bigger jump. |
|
|
93
|
+
| `Page Down` | Goes down by a bigger jump. |
|
|
94
|
+
| `Home` | Jumps to the minimum. |
|
|
95
|
+
| `End` | Jumps to the maximum. |
|
|
96
|
+
|
|
97
|
+
## Do
|
|
98
|
+
|
|
99
|
+
- Use formatValue whenever the number has a unit.
|
|
100
|
+
- Show the value. A slider with no readout asks people to guess.
|
|
101
|
+
- Put a number input beside it when the exact value matters.
|
|
102
|
+
|
|
103
|
+
## Don't
|
|
104
|
+
|
|
105
|
+
- Don't use a slider where a precise value is required and nothing else lets people enter one.
|
|
106
|
+
- Don't shrink the thumb below 24×24 of hit area.
|
|
107
|
+
- Don't rely on dragging alone. Every slider here works fully from the keyboard, and it should stay that way.
|
|
108
|
+
|
|
109
|
+
## Anatomy
|
|
110
|
+
|
|
111
|
+
- **Label and readout**: An 11px (pixel) mono label, in a monospaced font, and a 24px Oxanium readout with tabular figures, digits that all take the same width. Unlike Progress, there are no dimmed leading zeros. min and max belong to the caller and formatValue can return anything, so a fixed digit width would be one the value doesn’t actually have.
|
|
112
|
+
- **Track**: 4px tall, sunken, no border.
|
|
113
|
+
- **Range**: The filled span, in accent.signal.indicator.
|
|
114
|
+
- **Thumb**: A 20px square inside a 24px hit area, the region that responds to a click or tap. There’s one per value.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Sparkline
|
|
2
|
+
|
|
3
|
+
> A small trend line with no axes. It sits beside a Stat or in a table cell. It's plain SVG (Scalable Vector Graphics, the web's format for drawn shapes): one image named by a sentence, with an optional safe band behind it and a square on the last value.
|
|
4
|
+
|
|
5
|
+
- **Best for:** You want the shape of a recent series, not its values. Think of a reading over the last hour.
|
|
6
|
+
- **Not for:** Values people need to read, compare or hover over. Use ChartPanel for those.
|
|
7
|
+
- Atom. Source: `src/components/atoms/sparkline/Sparkline.tsx`
|
|
8
|
+
- Rendered docs page: `/components/sparkline`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Sparkline } from 'src/components/atoms/sparkline/Sparkline'
|
|
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
|
+
| `values` | `readonly number[]` | | Required. Oldest first. Non-finite values (such as NaN or Infinity) are dropped. |
|
|
23
|
+
| `label` | `string` | | Required. The chart as one sentence: "Temperature, last hour: 3.1 to 4.2 °C, rising". |
|
|
24
|
+
| `tone` | `'neutral' \| 'signal' \| 'ok' \| 'warning' \| 'hostile' \| 'info'` | `'neutral'` | The line's colour. Each tone means what it means on Badge. |
|
|
25
|
+
| `min` | `number` | | A fixed bottom for the scale. Without it, the scale follows the data (and the band). |
|
|
26
|
+
| `max` | `number` | | A fixed top for the scale. Without it, the scale follows the data (and the band). |
|
|
27
|
+
| `band` | `{ from: number; to: number }` | | A range drawn behind the line. |
|
|
28
|
+
| `showLast` | `boolean` | `false` | Puts a square on the last value. |
|
|
29
|
+
| `width` | `number` | `96` | In CSS px, the pixel unit of Cascading Style Sheets. className="w-full" overrides it. |
|
|
30
|
+
| `height` | `number` | `24` | In CSS px. |
|
|
31
|
+
| `className` | `string` | | Goes on the frame. |
|
|
32
|
+
| `ref` | `Ref<HTMLSpanElement>` | | Forwarded to the frame. |
|
|
33
|
+
|
|
34
|
+
## Types and helpers
|
|
35
|
+
|
|
36
|
+
Exported beside the component, from the same file. Import a type with `import type { ... }`.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
export type SparklineTone = 'neutral' | 'signal' | 'ok' | 'warning' | 'hostile' | 'info'
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Examples
|
|
43
|
+
|
|
44
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
45
|
+
|
|
46
|
+
### Beside the value it trends
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
<div className="flex items-center gap-3">
|
|
50
|
+
<span className="font-mono text-sm tabular-nums">4.2 °C</span>
|
|
51
|
+
<Sparkline
|
|
52
|
+
values={[3.1, 3.2, 3.2, 3.4, 3.3, 3.6, 3.8, 3.7, 4.0, 4.2]}
|
|
53
|
+
label="Cold room 2, last hour: 3.1 to 4.2 °C, rising"
|
|
54
|
+
band={{ from: 2, to: 8 }}
|
|
55
|
+
showLast
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
<div className="flex items-center gap-3">
|
|
59
|
+
<span className="font-mono text-sm tabular-nums">412</span>
|
|
60
|
+
<Sparkline
|
|
61
|
+
values={[380, 365, 402, 398, 371, 420, 412]}
|
|
62
|
+
label="Orders, last 7 days: 365 to 420, steady"
|
|
63
|
+
tone="signal"
|
|
64
|
+
showLast
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Tones
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
<Sparkline values={[5, 6, 5, 7, 6, 8]} label="Neutral trend, rising" />
|
|
73
|
+
<Sparkline values={[5, 6, 5, 7, 6, 8]} label="Healthy trend, rising" tone="ok" />
|
|
74
|
+
<Sparkline values={[5, 6, 5, 7, 6, 8]} label="Degraded trend, rising" tone="warning" />
|
|
75
|
+
<Sparkline values={[5, 6, 5, 7, 6, 8]} label="Failing trend, rising" tone="hostile" />
|
|
76
|
+
<Sparkline values={[5, 6, 5, 7, 6, 8]} label="Informational trend, rising" tone="info" />
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Stretched to its container, with a safe band from 2 to 8 °C
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
<div className="w-full max-w-md">
|
|
83
|
+
<Sparkline
|
|
84
|
+
values={[4.1, 4.3, 4.0, 5.2, 6.8, 8.4, 9.1, 8.7, 7.2, 5.9, 5.0, 4.6]}
|
|
85
|
+
label="Cold room 3, last 12 hours: 4.0 to 9.1 °C, above the safe band for 3 hours, now back inside it"
|
|
86
|
+
tone="warning"
|
|
87
|
+
band={{ from: 2, to: 8 }}
|
|
88
|
+
min={0}
|
|
89
|
+
max={10}
|
|
90
|
+
height={40}
|
|
91
|
+
className="w-full"
|
|
92
|
+
showLast
|
|
93
|
+
/>
|
|
94
|
+
</div>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### No values, one value, a flat run
|
|
98
|
+
|
|
99
|
+
```tsx
|
|
100
|
+
<Sparkline values={[]} label="No readings yet" />
|
|
101
|
+
<Sparkline values={[12]} label="One reading: 12" showLast />
|
|
102
|
+
<Sparkline values={[7, 7, 7, 7]} label="Flat at 7" showLast />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Keyboard
|
|
106
|
+
|
|
107
|
+
Not focusable. No keyboard behaviour of its own.
|
|
108
|
+
|
|
109
|
+
## Do
|
|
110
|
+
|
|
111
|
+
- Write the label as the reading a person takes from the shape: the range and the direction.
|
|
112
|
+
- Fix min and max when several sparklines sit side by side and have to share a scale.
|
|
113
|
+
- Put the current value beside it in text. The line shows the trend, not the number.
|
|
114
|
+
- Use a tone for what the trend means (hostile for out of range), and say it in text too.
|
|
115
|
+
|
|
116
|
+
## Don't
|
|
117
|
+
|
|
118
|
+
- Don't use a sparkline where someone has to read values off it. That's a job for ChartPanel.
|
|
119
|
+
- Don't leave the label as a title like "Temperature". It's the only thing a screen reader hears.
|
|
120
|
+
- Don't colour every sparkline on a page. Neutral is the default for a reason.
|
|
121
|
+
|
|
122
|
+
## Anatomy
|
|
123
|
+
|
|
124
|
+
- **Frame**: An inline box. It's 96 x 24 by default, or the container's width with className="w-full".
|
|
125
|
+
- **Image**: An svg with role="img", named by label. Screen readers (software that reads the page aloud) don't get the points one by one.
|
|
126
|
+
- **Band**: Optional. A hairline-filled range behind the line, such as a safe range.
|
|
127
|
+
- **Line**: 1.5px (pixels) in the tone colour. The stroke doesn't get thicker when the frame stretches.
|
|
128
|
+
- **Last value**: An optional 6px square on the newest point. It's aria-hidden, a setting that hides it from screen readers.
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Spinner
|
|
2
|
+
|
|
3
|
+
> A busy indicator for an indeterminate wait, meaning one with no known end or percentage. It comes in two forms. The ring has a lit arc that grows and slides as it turns. The conveyor is a row of shaded blocks, for a wait that sits inside a line of text.
|
|
4
|
+
|
|
5
|
+
- **Best for:** A short wait with no known end, inside a control or a line of text.
|
|
6
|
+
- **Not for:** Progress with a known amount (use Progress), or a wait for a whole page (use Skeleton or LoadingAnimation).
|
|
7
|
+
- Atom. Source: `src/components/atoms/spinner/Spinner.tsx`
|
|
8
|
+
- Rendered docs page: `/components/spinner`
|
|
9
|
+
|
|
10
|
+
## Import
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { Spinner, spinnerVariants } from 'src/components/atoms/spinner/Spinner'
|
|
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` | `'ring' \| 'conveyor'` | `'ring'` | The rotating ring, or the block conveyor. |
|
|
23
|
+
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Ring: a 14px, 20px or 24px (pixel) box. Conveyor: 12px, 14px or 16px glyphs. |
|
|
24
|
+
| `label` | `string` | | Status text that's hidden on screen. Passing it makes the spinner a live region, an area whose changes screen readers announce on their own. |
|
|
25
|
+
| `className` | `string` | | Merged with the component's classes. Later Tailwind utilities (the small styling classes this system uses) win. |
|
|
26
|
+
| `ref` | `Ref<HTMLSpanElement>` | | Forwarded to the span element. |
|
|
27
|
+
| `...rest` | `Omit<HTMLAttributes<HTMLSpanElement>, 'role'>` | | Every native span prop except role, spread onto the span. role is removed from the type instead of being quietly overridden at runtime. Here's why: label is the only source of role="status", and a role in rest would silence the live region a caller asked for when they passed label. |
|
|
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 spinnerVariants = cva(
|
|
35
|
+
// The arc takes `currentColor`, so a caller can recolour it with one text utility: Button does
|
|
36
|
+
// exactly that, so the arc matches whatever ink the button label is set in.
|
|
37
|
+
'inline-block shrink-0 text-[var(--color-accent-signal-indicator)]',
|
|
38
|
+
{
|
|
39
|
+
variants: {
|
|
40
|
+
size: { sm: 'w-3.5 h-3.5', md: 'w-5 h-5', lg: 'w-6 h-6' },
|
|
41
|
+
},
|
|
42
|
+
defaultVariants: { size: 'md' },
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Examples
|
|
48
|
+
|
|
49
|
+
Each is compiled and rendered on the docs page. Imports are omitted; see above.
|
|
50
|
+
|
|
51
|
+
### Ring, three sizes
|
|
52
|
+
|
|
53
|
+
```tsx
|
|
54
|
+
<Spinner size="sm" label="Loading field kits" />
|
|
55
|
+
<Spinner size="md" label="Loading field kits" />
|
|
56
|
+
<Spinner size="lg" label="Loading field kits" />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Conveyor, three sizes
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<Spinner variant="conveyor" size="sm" label="Syncing manifest" />
|
|
63
|
+
<Spinner variant="conveyor" size="md" label="Syncing manifest" />
|
|
64
|
+
<Spinner variant="conveyor" size="lg" label="Syncing manifest" />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Decorative, inside a control
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<span className="inline-flex items-center gap-2 border border-[var(--color-border-hairline-strong)] px-3 py-2">
|
|
71
|
+
<Spinner aria-hidden="true" />
|
|
72
|
+
<span className="text-sm">Dispatching…</span>
|
|
73
|
+
</span>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Keyboard
|
|
77
|
+
|
|
78
|
+
Not focusable. No keyboard behaviour of its own.
|
|
79
|
+
|
|
80
|
+
## Do
|
|
81
|
+
|
|
82
|
+
- Pass label whenever the spinner is the only sign that something's happening.
|
|
83
|
+
- Leave it decorative inside a control that already announces aria-busy, like Button. aria-busy is an ARIA (Accessible Rich Internet Applications) attribute that tells screen readers a part of the page is still loading.
|
|
84
|
+
- Name what's loading, not the fact of loading: ‘Loading field kits’, not ‘Loading’.
|
|
85
|
+
|
|
86
|
+
## Don't
|
|
87
|
+
|
|
88
|
+
- Don't use it for progress with a known amount. That's Progress.
|
|
89
|
+
- Don't rely on the rotation alone. It stops when someone has turned on reduced motion in their system settings, so the label carries the meaning.
|
|
90
|
+
- Don't scale it past lg. A page-level wait needs a skeleton, not a bigger spinner.
|
|
91
|
+
- Don't bevel it. It's the one round shape in the system. A cut corner on something that rotates would travel around the edge instead of marking one spot.
|
|
92
|
+
|
|
93
|
+
## Anatomy
|
|
94
|
+
|
|
95
|
+
- **span**: The root. It gets role="status" only when it's labelled.
|
|
96
|
+
- **Ring (ring)**: An SVG (Scalable Vector Graphics) track at 20% of currentColor, which is the surrounding text colour, plus an arc in currentColor. That's accent.signal.indicator by default.
|
|
97
|
+
- **Blocks (conveyor)**: Three block glyphs, from full to half shade, running over a ten-cell track in border.control.
|
|
98
|
+
- **sr-only label**: The text a screen reader announces. sr-only means it's hidden on screen but still read aloud. It's only there when label is passed.
|