@ngrr/ds 0.1.4 → 0.1.6
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 +451 -0
- package/AI.md +1976 -0
- package/CLAUDE.md +451 -0
- package/dist/ds-nagarro.es.js +14181 -17616
- package/dist/ds-nagarro.umd.js +80 -85
- package/dist/style.css +1 -1
- package/dist/tokens.css +3 -0
- package/package.json +6 -3
package/AGENTS.md
ADDED
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
# DS-Nagarro Component Library — Agent Briefing
|
|
2
|
+
|
|
3
|
+
> **READ THIS FIRST.** This file is the entry point for every coding session.
|
|
4
|
+
> Do not write any code until you have read and understood this document.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Project Purpose
|
|
9
|
+
|
|
10
|
+
This is the React component library for **DS-Nagarro**, an enterprise design system optimised for AI-agent code generation. The primary goal is machine-readable fidelity: components must implement exactly what the design system specifies, using exactly the tokens named in the documentation. No creative interpretation.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Stack
|
|
15
|
+
|
|
16
|
+
| Layer | Technology |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Framework | React 18 + TypeScript |
|
|
19
|
+
| Build | Vite |
|
|
20
|
+
| Styling | Styled Components v6 |
|
|
21
|
+
| Token consumption | CSS custom properties via `var()` |
|
|
22
|
+
| Documentation | Storybook 8 (CSF 3.0 + autodocs) |
|
|
23
|
+
| Testing | Vitest + Testing Library + Storybook play functions |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Repository Structure
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
ds-nagarro/
|
|
31
|
+
├── AGENTS.md ← You are here
|
|
32
|
+
├── tokens.css ← All design tokens as CSS custom properties
|
|
33
|
+
├── src/
|
|
34
|
+
│ ├── index.ts ← Public API — export all components
|
|
35
|
+
│ ├── styles/
|
|
36
|
+
│ │ └── global.css ← Imports tokens.css, sets base styles
|
|
37
|
+
│ ├── components/
|
|
38
|
+
│ │ ├── atoms/
|
|
39
|
+
│ │ │ ├── Button/
|
|
40
|
+
│ │ │ │ ├── Button.tsx
|
|
41
|
+
│ │ │ │ ├── Button.stories.tsx
|
|
42
|
+
│ │ │ │ ├── Button.test.tsx
|
|
43
|
+
│ │ │ │ └── index.ts
|
|
44
|
+
│ │ │ └── [other atoms...]
|
|
45
|
+
│ │ ├── molecules/
|
|
46
|
+
│ │ └── organisms/
|
|
47
|
+
│ └── types/
|
|
48
|
+
│ └── common.ts ← Shared TypeScript interfaces
|
|
49
|
+
├── .storybook/
|
|
50
|
+
│ ├── main.ts
|
|
51
|
+
│ └── preview.ts ← Imports tokens.css globally
|
|
52
|
+
└── docs/ ← Design system documentation (read-only)
|
|
53
|
+
└── [copied from /mnt/project/ — see Documentation section]
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Component Documentation
|
|
59
|
+
|
|
60
|
+
All component specs live in the `docs/` directory. Each file is authoritative for its component(s). Read the relevant doc **before** generating any component.
|
|
61
|
+
|
|
62
|
+
| File | Components Covered |
|
|
63
|
+
|---|---|
|
|
64
|
+
| `button.md` | Button (Main, Destructive, Toggle, Vertical) |
|
|
65
|
+
| `avatar.md` | Avatar, AvatarGroup |
|
|
66
|
+
| `badge.md` | Badge |
|
|
67
|
+
| `tag.md` | Tag |
|
|
68
|
+
| `chip.md` | Chip, ChipGroup |
|
|
69
|
+
| `separator.md` | Separator |
|
|
70
|
+
| `switcher.md` | Switcher (Toggle Switch) |
|
|
71
|
+
| `checkbox.md` | Checkbox |
|
|
72
|
+
| `radio.md` | Radio |
|
|
73
|
+
| `tab.md` | Tab (Navigation, Custom View) |
|
|
74
|
+
| `segment-control.md` | SegmentControl |
|
|
75
|
+
| `breadcrumbs.md` | Breadcrumbs, BreadcrumbItem |
|
|
76
|
+
| `pagination.md` | Pagination, PageSelector |
|
|
77
|
+
| `toast.md` | Toast (Inline, Rich) |
|
|
78
|
+
| `tooltip.md` | Tooltip |
|
|
79
|
+
| `popover.md` | Popover |
|
|
80
|
+
| `modal.md` | Modal |
|
|
81
|
+
| `input-anatomy.md` | Input anatomy (shared across all input types) |
|
|
82
|
+
| `input-text-textarea-search.md` | TextInput, TextArea, SearchInput |
|
|
83
|
+
| `input-password-otp-number-phone.md` | PasswordInput, OTPInput, NumberInput, PhoneInput |
|
|
84
|
+
| `input-select-autocomplete-horizontal.md` | Select, AutocompleteMulti, HorizontalInput |
|
|
85
|
+
| `input-slider-upload-chips.md` | Slider, UploadArea, ChipsInput |
|
|
86
|
+
| `input-date-time-picker.md` | DatePicker, DateRangePicker, DateTimePicker |
|
|
87
|
+
| `lightweight-loading-scrollbar-shortcut.md` | Spinner, SkeletonLoading, ProgressBar, Scrollbar, Shortcut |
|
|
88
|
+
| `patterns.md` | Layout patterns (not components) |
|
|
89
|
+
| `foundations.md` | Token system, colour rules, typography rules |
|
|
90
|
+
| `ds-guidelines.md` | Global rules |
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## Token System
|
|
95
|
+
|
|
96
|
+
### Where tokens live
|
|
97
|
+
All tokens are CSS custom properties defined in `tokens.css`. They are imported globally via `src/styles/global.css`. Every Styled Component must consume tokens via `var(--token-name)`.
|
|
98
|
+
|
|
99
|
+
### Naming convention
|
|
100
|
+
Figma uses slash-separated paths. These map directly to hyphen-separated CSS custom properties:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
Figma: background/interactive/cta/default
|
|
104
|
+
CSS: var(--background-interactive-cta-default)
|
|
105
|
+
|
|
106
|
+
Figma: font/size/body-l
|
|
107
|
+
CSS: var(--font-size-body-l)
|
|
108
|
+
|
|
109
|
+
Figma: borders/focus/primary
|
|
110
|
+
CSS: var(--borders-focus-primary)
|
|
111
|
+
|
|
112
|
+
Figma: color-text-primary
|
|
113
|
+
CSS: var(--color-text-primary)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Three-tier model
|
|
117
|
+
```
|
|
118
|
+
Layer 1: Primitives --primitive-neutral-900 (never use in components)
|
|
119
|
+
Layer 2: Semantic --color-text-primary (use in components)
|
|
120
|
+
Layer 3: Component --color-surface-button-* (component-specific overrides)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**Always use Layer 2 (semantic) tokens in components. Never reference Layer 1 (primitive) tokens directly in component styles.**
|
|
124
|
+
|
|
125
|
+
### Key token families
|
|
126
|
+
- `--color-text-*` → CSS `color` property
|
|
127
|
+
- `--background-*` → CSS `background-color` property
|
|
128
|
+
- `--borders-*` → CSS `border-color`, `outline-color`
|
|
129
|
+
- `--color-surface-*` → component-specific background fills
|
|
130
|
+
- `--color-dataviz-*` → all chart and data visualisation colors (series, axes, labels, backgrounds). Never use generic semantic tokens or hardcoded hex values for dataviz.
|
|
131
|
+
- `--font-size-*`, `--font-weight-*`, `--font-line-height-*` → typography
|
|
132
|
+
- `--space-*` → layout gaps (margin, gap) — between elements
|
|
133
|
+
- `--inset-*` → component padding — inside components
|
|
134
|
+
- `--page-margin-x` → horizontal spacing for all containers (pages, cards, modals, drawers, tables, lists, form sections). Always use this token. Never hardcode `padding-left`, `padding-right`, `margin-left`, or `margin-right`.
|
|
135
|
+
- `--radius-*` → border-radius
|
|
136
|
+
- `--border-width-*` → border-width
|
|
137
|
+
- `--effects-elevation-*` → box-shadow
|
|
138
|
+
- `--transition-*` → transition
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Component Generation Rules
|
|
143
|
+
|
|
144
|
+
### Rule 1: Props = Figma variant properties
|
|
145
|
+
Figma variant property names map directly to React prop names. Match them exactly.
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
Figma: Size=Medium, Variant=Primary, State=Default, Disabled=False, Loading=False
|
|
149
|
+
React: size="md" variant="primary" state="default" disabled={false} loading={false}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Size name mapping (Figma → prop value):
|
|
153
|
+
- `Small` → `"sm"`
|
|
154
|
+
- `Medium` → `"md"`
|
|
155
|
+
- `Large` → `"lg"`
|
|
156
|
+
- `xLarge` → `"xl"`
|
|
157
|
+
- `xxLarge` → `"2xl"`
|
|
158
|
+
|
|
159
|
+
Boolean props extracted from Figma booleans:
|
|
160
|
+
- `Disabled=True/False` → `disabled?: boolean`
|
|
161
|
+
- `Loading=True/False` → `loading?: boolean`
|
|
162
|
+
- `Selected=True/False` → `selected?: boolean`
|
|
163
|
+
|
|
164
|
+
State is internal — never accept `state` as a prop. States are expressed via pseudo-classes and boolean props only.
|
|
165
|
+
|
|
166
|
+
### Rule 2: TypeScript interfaces — explicit union types
|
|
167
|
+
Every prop that corresponds to a Figma variant must be a TypeScript union type (not `string`).
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// CORRECT
|
|
171
|
+
export type ButtonVariant = 'primary' | 'secondary' | 'ghost' | 'plain';
|
|
172
|
+
export type ButtonSize = 'sm' | 'md';
|
|
173
|
+
|
|
174
|
+
// WRONG
|
|
175
|
+
variant: string;
|
|
176
|
+
size: string;
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### Rule 3: Styled Components — tokens only, no hardcoded values
|
|
180
|
+
No hex codes, no pixel values, no hardcoded rgba. Everything comes from tokens.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// CORRECT
|
|
184
|
+
background-color: var(--background-interactive-cta-default);
|
|
185
|
+
font-size: var(--font-size-body-l);
|
|
186
|
+
padding-inline: var(--page-margin-x);
|
|
187
|
+
|
|
188
|
+
// WRONG
|
|
189
|
+
background-color: #6941C6;
|
|
190
|
+
font-size: 16px;
|
|
191
|
+
padding: 0 24px;
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Rule 4: State implementation
|
|
195
|
+
Interactive states use CSS pseudo-classes + `data-*` attributes. Never conditionally render different DOM elements for states.
|
|
196
|
+
|
|
197
|
+
```typescript
|
|
198
|
+
// Hover, press, focus — CSS pseudo-classes
|
|
199
|
+
&:hover:not(:disabled) {
|
|
200
|
+
background-color: var(--background-interactive-cta-hover);
|
|
201
|
+
}
|
|
202
|
+
&:active:not(:disabled) {
|
|
203
|
+
background-color: var(--background-interactive-cta-pressed);
|
|
204
|
+
}
|
|
205
|
+
&:focus-visible {
|
|
206
|
+
outline: var(--border-width-focus) solid var(--borders-focus-primary);
|
|
207
|
+
outline-offset: 2px;
|
|
208
|
+
}
|
|
209
|
+
&:disabled {
|
|
210
|
+
opacity: 1; /* Never use opacity for disabled — use explicit disabled tokens */
|
|
211
|
+
background-color: var(--background-disabled);
|
|
212
|
+
color: var(--color-text-disabled);
|
|
213
|
+
cursor: not-allowed;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Selected state — controlled via prop → data attribute
|
|
217
|
+
const StyledButton = styled.button<{ $selected?: boolean }>`
|
|
218
|
+
background-color: ${({ $selected }) =>
|
|
219
|
+
$selected
|
|
220
|
+
? 'var(--background-selected-strong)'
|
|
221
|
+
: 'var(--background-interactive-default-default)'
|
|
222
|
+
};
|
|
223
|
+
`;
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Use `$` prefix (transient props) for all Styled Components props that should not reach the DOM.
|
|
227
|
+
|
|
228
|
+
### Rule 5: Focus states — always `:focus-visible`, never `:focus`
|
|
229
|
+
```typescript
|
|
230
|
+
// CORRECT
|
|
231
|
+
&:focus-visible { outline: ... }
|
|
232
|
+
|
|
233
|
+
// WRONG
|
|
234
|
+
&:focus { outline: ... }
|
|
235
|
+
// NEVER
|
|
236
|
+
outline: none; /* without explicit replacement */
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Rule 6: Semantic HTML and ARIA
|
|
240
|
+
Every component must use the correct HTML element and ARIA attributes. The Figma file cannot express this — the component docs are the source of truth.
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
// Buttons
|
|
244
|
+
<button type="button" disabled={disabled} aria-pressed={selected} aria-busy={loading}>
|
|
245
|
+
|
|
246
|
+
// Form inputs — every input is mandatory by default
|
|
247
|
+
// Do NOT mark required fields with an asterisk
|
|
248
|
+
// Only add an "Optional" label (in var(--text-tertiary)) for fields that are not mandatory
|
|
249
|
+
<input
|
|
250
|
+
type="text"
|
|
251
|
+
id={id}
|
|
252
|
+
aria-label={ariaLabel}
|
|
253
|
+
aria-describedby={helpTextId}
|
|
254
|
+
aria-invalid={error}
|
|
255
|
+
aria-required={true} // all inputs mandatory by default; omit or set false for optional
|
|
256
|
+
/>
|
|
257
|
+
<label htmlFor={id}>{label}</label>
|
|
258
|
+
|
|
259
|
+
// Loading state
|
|
260
|
+
aria-busy="true"
|
|
261
|
+
aria-label="Loading..."
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
### Rule 7: Storybook — CSF 3.0 format
|
|
265
|
+
```typescript
|
|
266
|
+
// Button.stories.tsx pattern
|
|
267
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
268
|
+
import { Button } from './Button';
|
|
269
|
+
|
|
270
|
+
const meta: Meta<typeof Button> = {
|
|
271
|
+
title: 'Atoms/Button',
|
|
272
|
+
component: Button,
|
|
273
|
+
tags: ['autodocs'],
|
|
274
|
+
argTypes: {
|
|
275
|
+
variant: {
|
|
276
|
+
control: 'select',
|
|
277
|
+
options: ['primary', 'secondary', 'ghost', 'plain'],
|
|
278
|
+
},
|
|
279
|
+
size: {
|
|
280
|
+
control: 'select',
|
|
281
|
+
options: ['sm', 'md'],
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
export default meta;
|
|
286
|
+
type Story = StoryObj<typeof Button>;
|
|
287
|
+
|
|
288
|
+
// One story per meaningful variant combination
|
|
289
|
+
export const Primary: Story = { args: { variant: 'primary', size: 'md', children: 'Button' } };
|
|
290
|
+
export const Secondary: Story = { args: { variant: 'secondary', size: 'md', children: 'Button' } };
|
|
291
|
+
export const Disabled: Story = { args: { variant: 'primary', size: 'md', disabled: true, children: 'Button' } };
|
|
292
|
+
export const Loading: Story = { args: { variant: 'primary', size: 'md', loading: true, children: 'Button' } };
|
|
293
|
+
|
|
294
|
+
// Interaction test example
|
|
295
|
+
export const FocusInteraction: Story = {
|
|
296
|
+
args: { variant: 'primary', children: 'Focus me' },
|
|
297
|
+
play: async ({ canvasElement }) => {
|
|
298
|
+
const canvas = within(canvasElement);
|
|
299
|
+
const button = canvas.getByRole('button');
|
|
300
|
+
await userEvent.tab();
|
|
301
|
+
expect(button).toHaveFocus();
|
|
302
|
+
},
|
|
303
|
+
};
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Rule 8: File structure — one directory per component
|
|
307
|
+
```
|
|
308
|
+
Button/
|
|
309
|
+
├── Button.tsx ← Component implementation
|
|
310
|
+
├── Button.stories.tsx ← All stories for this component
|
|
311
|
+
├── Button.test.tsx ← Unit tests
|
|
312
|
+
└── index.ts ← Re-exports: export { Button } from './Button';
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Rule 9: Component generation order (atoms first)
|
|
316
|
+
1. **Button** (covers: Main, Destructive, Toggle, Vertical)
|
|
317
|
+
2. **Avatar** + **AvatarGroup**
|
|
318
|
+
3. **Badge**
|
|
319
|
+
4. **Tag**
|
|
320
|
+
5. **Chip** + **ChipGroup**
|
|
321
|
+
6. **Separator**
|
|
322
|
+
7. **Switcher**
|
|
323
|
+
8. **Checkbox**
|
|
324
|
+
9. **Radio**
|
|
325
|
+
10. **TextInput** (and shared Input anatomy)
|
|
326
|
+
11. **TextArea**
|
|
327
|
+
12. **SearchInput**
|
|
328
|
+
13. **Select**
|
|
329
|
+
14. **Slider**
|
|
330
|
+
15. **Tab** (Navigation + Custom View)
|
|
331
|
+
16. **SegmentControl**
|
|
332
|
+
17. **Breadcrumbs**
|
|
333
|
+
18. **Pagination**
|
|
334
|
+
19. **Toast**
|
|
335
|
+
20. **Tooltip**
|
|
336
|
+
21. **Popover**
|
|
337
|
+
22. **Modal**
|
|
338
|
+
23. Remaining input types (Password, OTP, Number, Phone, DatePicker, etc.)
|
|
339
|
+
|
|
340
|
+
### Rule 10: Dropdown/popover positioning contract
|
|
341
|
+
All dropdown/select/popover menus must use the shared anchored behavior (via `PopoverWrapper`):
|
|
342
|
+
- Default placement: below trigger
|
|
343
|
+
- Gap: `var(--space-tiny)`
|
|
344
|
+
- Flip: only when not enough room below
|
|
345
|
+
- Viewport safe margin: `var(--space-medium)` from all edges
|
|
346
|
+
- Horizontal clamping: keep popovers within safe viewport bounds
|
|
347
|
+
- Width contract: match trigger width with a minimum popover width of `192px`
|
|
348
|
+
- Overflow behavior: truncate menu labels when width is constrained
|
|
349
|
+
|
|
350
|
+
Do not implement one-off absolute positioning per component. Reuse the shared `PopoverWrapper` contract for `Select`, `PhoneInput`, `DateTimePicker`, and future menu triggers.
|
|
351
|
+
|
|
352
|
+
### Rule 11: Accessibility guardrails and CI gates
|
|
353
|
+
Every contribution must preserve accessibility contracts:
|
|
354
|
+
- For custom-role widgets (`menuitem`, `switch`, `checkbox`, `radio`, `slider`, `tab`, `gridcell`), add/maintain at least one keyboard interaction test.
|
|
355
|
+
- Disabled components must not be focusable (`disabled` for native controls; otherwise `tabIndex={-1}` + `aria-disabled="true"`).
|
|
356
|
+
- Nested interactive controls inside clickable rows must stop propagation so child actions do not trigger parent actions.
|
|
357
|
+
- Focus rings must be implemented with `:focus-visible` (never `:focus` as the visible focus trigger).
|
|
358
|
+
- Popup/dropdown tests must cover Arrow keys, Enter/Space selection, Escape close, and focus return to trigger.
|
|
359
|
+
|
|
360
|
+
Storybook signal-quality policy:
|
|
361
|
+
- Disabled/demo-only stories may use `parameters: { a11y: { disable: true } }` for intentional exceptions.
|
|
362
|
+
- Keep a11y opt-outs story-scoped only; never disable at component meta/global level for interactive components.
|
|
363
|
+
|
|
364
|
+
CI enforcement:
|
|
365
|
+
- Run `npm run test:a11y:keyboard-widgets`.
|
|
366
|
+
- Run `npm run test:a11y:stories:light`.
|
|
367
|
+
- Run `npm run test:a11y:stories:dark`.
|
|
368
|
+
|
|
369
|
+
### Rule 12: Icons — Lucide only
|
|
370
|
+
The only permitted icon library is `lucide-react`. Never use browser/OS-native icons, emoji, or any other icon library anywhere in the component library.
|
|
371
|
+
|
|
372
|
+
```typescript
|
|
373
|
+
// CORRECT
|
|
374
|
+
import { ArrowUpDown, X, Check } from 'lucide-react';
|
|
375
|
+
|
|
376
|
+
// WRONG
|
|
377
|
+
// Any icon not from lucide-react
|
|
378
|
+
// System/OS native icons (e.g. ↕ ⬆ ⬇)
|
|
379
|
+
// Emoji used as icons
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Rule 13: Placeholder content — never leave it in place
|
|
383
|
+
Component slots for icons, help text, and hint icons must never ship with placeholder values.
|
|
384
|
+
Either populate with real, meaningful content or hide the slot entirely.
|
|
385
|
+
|
|
386
|
+
- **Icons** → replace with a relevant Lucide icon, or omit the slot
|
|
387
|
+
- **Help text** → replace with genuinely useful guidance, or omit
|
|
388
|
+
- **Hint icons** → replace with meaningful tooltip content, or omit
|
|
389
|
+
|
|
390
|
+
### Rule 14: Form behavior
|
|
391
|
+
- Every form field is **mandatory by default**. Never use a red asterisk to mark required fields.
|
|
392
|
+
- Only mark optional fields, using an `"Optional"` label in `var(--text-tertiary)` placed inline after the label text.
|
|
393
|
+
- The primary submit CTA must be **disabled** until all mandatory fields contain a value.
|
|
394
|
+
- Validate all fields on submit only — not in real time as the user types.
|
|
395
|
+
- Exceptions: password strength, character count limits, search/filter fields, OTP.
|
|
396
|
+
- The primary submit button must always display the `⌘↵` shortcut using the `Shortcut` component.
|
|
397
|
+
|
|
398
|
+
```tsx
|
|
399
|
+
// CORRECT — optional field
|
|
400
|
+
<label>Team <span style={{ color: 'var(--text-tertiary)' }}>Optional</span></label>
|
|
401
|
+
|
|
402
|
+
// CORRECT — primary submit button
|
|
403
|
+
<Button variant="primary">Add user <Shortcut>⌘↵</Shortcut></Button>
|
|
404
|
+
|
|
405
|
+
// WRONG
|
|
406
|
+
<label>First name *</label> // never use asterisk
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
---
|
|
410
|
+
|
|
411
|
+
## Accessibility Requirements
|
|
412
|
+
|
|
413
|
+
- WCAG AA minimum: 4.5:1 contrast for normal text, 3:1 for large text
|
|
414
|
+
- All interactive elements must be keyboard operable
|
|
415
|
+
- Focus order must follow logical reading order (top to bottom, left to right)
|
|
416
|
+
- Colour alone must never convey meaning — pair with text, icon, or pattern
|
|
417
|
+
- Every form input must have an associated `<label>` (visible or `aria-label`)
|
|
418
|
+
- All inputs carry `aria-required="true"` by default. Optional fields omit `aria-required` or set it to `false`. Never use a visual asterisk.
|
|
419
|
+
- Loading states must use `aria-busy="true"` and a descriptive `aria-label`
|
|
420
|
+
- Disabled elements must NOT be focusable (use `disabled` attribute on native elements, `tabIndex={-1}` + `aria-disabled="true"` on custom elements)
|
|
421
|
+
- Closed disclosure components (Accordion, Popover, Dropdown) must not expose their children to keyboard focus until open
|
|
422
|
+
|
|
423
|
+
---
|
|
424
|
+
|
|
425
|
+
## Common Mistakes to Avoid
|
|
426
|
+
|
|
427
|
+
1. **Using primitive tokens in components.** `var(--primitive-neutral-900)` in a component style is wrong. Use `var(--color-text-primary)`.
|
|
428
|
+
2. **Using `:focus` instead of `:focus-visible`.** Always `:focus-visible`.
|
|
429
|
+
3. **Hardcoding hex values or pixel sizes.** Everything comes from tokens.
|
|
430
|
+
4. **Accepting `state` as a prop.** States are pseudo-classes + boolean props, not a state prop.
|
|
431
|
+
5. **Omitting aria attributes.** Always add ARIA from the component docs.
|
|
432
|
+
6. **Not exporting from `index.ts`.** Every component needs `export { X } from './X'` in its own index.ts, and re-exported from `src/index.ts`.
|
|
433
|
+
7. **Using opacity for disabled state.** Use explicit disabled tokens (`--background-disabled`, `--color-text-disabled`). Never `opacity: 0.5`.
|
|
434
|
+
8. **Passing styled props directly to DOM.** Always use transient props (`$propName`) in Styled Components to prevent DOM attribute forwarding.
|
|
435
|
+
9. **Using asterisks for required fields.** Every field is mandatory by default. Only mark optional fields with an `"Optional"` label in `var(--text-tertiary)`.
|
|
436
|
+
10. **Leaving placeholder icons, help text, or hint icons in components.** Replace with real content or hide the slot entirely.
|
|
437
|
+
11. **Using icons from any library other than Lucide.** Only `lucide-react` is permitted. No system icons, no emoji, no other libraries.
|
|
438
|
+
12. **Hardcoding horizontal padding instead of `var(--page-margin-x)`.** All horizontal container spacing must use this token.
|
|
439
|
+
13. **Enabling the primary CTA before all mandatory fields have a value.** The submit button must be disabled until the form is fillable.
|
|
440
|
+
14. **Omitting `⌘↵` from the primary form submit button.** Always include the `Shortcut` component on the primary CTA.
|
|
441
|
+
15. **Using dataviz colors from generic semantic tokens or hardcoded hex.** Always use `--color-dataviz-*` tokens for all chart and graph colors.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
## Before Starting Each Component
|
|
446
|
+
|
|
447
|
+
1. Read the component's doc file in `docs/`
|
|
448
|
+
2. Identify all Figma variants → TypeScript props
|
|
449
|
+
3. Identify all tokens needed (colour, typography, spacing, sizing)
|
|
450
|
+
4. Implement component → stories → tests in that order
|
|
451
|
+
5. Export from component `index.ts` and from `src/index.ts`
|