@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 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`