@ngrr/ds 0.1.29 → 0.1.31

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