@iress-oss/ids-components 6.0.0-beta.3 → 6.0.0-beta.4
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.
|
@@ -4,12 +4,53 @@
|
|
|
4
4
|
|
|
5
5
|
Translate Figma design properties and structures into IDS (Iress Design System) component implementations. This skill helps AI agents interpret Figma design metadata (from tools like Figma MCP or exported design specs) and produce accurate IDS code.
|
|
6
6
|
|
|
7
|
+
## Figma MCP Setup
|
|
8
|
+
|
|
9
|
+
AI agents need a Figma MCP server to read Figma files directly. Without one, you can still use this skill by pasting exported design specs or Figma component descriptions manually.
|
|
10
|
+
|
|
11
|
+
### Setup
|
|
12
|
+
|
|
13
|
+
1. **Get a Figma personal access token** — In Figma, go to *Settings → Account → Personal access tokens* and create a token with **File content (Read-only)** scope.
|
|
14
|
+
|
|
15
|
+
2. **Add the MCP server to your agent config.** The exact location depends on your tool:
|
|
16
|
+
|
|
17
|
+
| Tool | Config file |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| **Kiro CLI** | `~/.kiro/settings/mcp.json` (global) or `.kiro/settings/mcp.json` (workspace) |
|
|
20
|
+
| **Cursor** | `.cursor/mcp.json` |
|
|
21
|
+
| **Claude Code** | `.claude/mcp.json` or `~/.claude/mcp.json` |
|
|
22
|
+
| **VS Code (GitHub Copilot)** | `.vscode/mcp.json` |
|
|
23
|
+
|
|
24
|
+
Example configuration (using the community [`figma-developer-mcp`](https://github.com/nicholasgriffintn/figma-developer-mcp) server):
|
|
25
|
+
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"mcpServers": {
|
|
29
|
+
"Figma": {
|
|
30
|
+
"command": "npx",
|
|
31
|
+
"args": ["-y", "figma-developer-mcp", "--stdio"],
|
|
32
|
+
"env": {
|
|
33
|
+
"FIGMA_API_KEY": "<your-figma-token>"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
For Kiro CLI, you can also add it via the command line:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
kiro-cli mcp add --name Figma --command npx --args "-y figma-developer-mcp --stdio" --env "FIGMA_API_KEY=<your-figma-token>"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
3. **Verify** — Ask your agent to fetch data from a Figma file URL. It should return frame and component information.
|
|
47
|
+
|
|
7
48
|
## Process
|
|
8
49
|
|
|
9
50
|
1. **Analyse Figma structure** — Identify frames, auto-layout, and component instances
|
|
10
51
|
2. **Map components** — Match Figma component names/variants to IDS components
|
|
11
52
|
3. **Extract tokens** — Convert Figma design values to IDS design token references
|
|
12
|
-
4. **Generate code** — Produce clean React/TypeScript with proper IDS imports
|
|
53
|
+
4. **Generate code** — Produce clean, minimal React/TypeScript with proper IDS imports. Use the fewest components possible — check whether parent components already handle layout before adding `IressInline`/`IressStack` wrappers. Never wrap a single child in a layout component.
|
|
13
54
|
5. **Verify output** — Check that all imports resolve, no raw HTML is used where IDS components exist, grid layouts use responsive `span` values, and no common anti-patterns are present (disabled buttons, slot attributes, redundant textStyle)
|
|
14
55
|
|
|
15
56
|
> **Important:** IDS v6 is currently in beta. Install with the `@beta` tag:
|
|
@@ -49,11 +90,11 @@ When mapping Figma components to IDS, read references/component-mapping.md for t
|
|
|
49
90
|
| Input / Text | `IressField` + `IressInput` | label, placeholder |
|
|
50
91
|
| Input / Currency | `IressField` + `IressInputCurrency` | label |
|
|
51
92
|
| Select / Dropdown | `IressField` + `IressSelect` | label, options |
|
|
52
|
-
| Checkbox | `IressCheckbox` | label
|
|
53
|
-
| Checkbox Group | `IressCheckboxGroup` + `IressCheckbox`s | label
|
|
54
|
-
| Radio Group | `IressRadioGroup` + `IressRadio`s | label
|
|
55
|
-
| Toggle | `IressToggle` | label
|
|
56
|
-
| Card | `IressCard` |
|
|
93
|
+
| Checkbox | `IressCheckbox` | `children` for label |
|
|
94
|
+
| Checkbox Group | `IressCheckboxGroup` + `IressCheckbox`s | Wrap in `IressField` for label |
|
|
95
|
+
| Radio Group | `IressRadioGroup` + `IressRadio`s | Wrap in `IressField` for label |
|
|
96
|
+
| Toggle | `IressToggle` | `children` for label |
|
|
97
|
+
| Card | `IressCard` | `heading`, `footer` props; `children` for body |
|
|
57
98
|
| Panel | `IressPanel` | |
|
|
58
99
|
| Alert / Success | `IressAlert status="success"` | |
|
|
59
100
|
| Alert / Danger | `IressAlert status="danger"` | |
|
|
@@ -65,7 +106,7 @@ When mapping Figma components to IDS, read references/component-mapping.md for t
|
|
|
65
106
|
| Modal / Warning | `IressModal status="warning"` | actions, size sm/md only |
|
|
66
107
|
| Slideout / Drawer | `IressSlideout` | |
|
|
67
108
|
| Tabs | `IressTabSet` + `IressTab` | |
|
|
68
|
-
| Table | `IressTable` |
|
|
109
|
+
| Table | `IressTable` | Data-driven: `rows`, `columns`, `caption` props |
|
|
69
110
|
| Tag | `IressTag` | `bordered` for visible border; `element="button"` for clickable, `element="a"` for link; `onClick` alone also auto-renders as button |
|
|
70
111
|
| Pill | `IressPill` | |
|
|
71
112
|
| Tooltip | `IressTooltip` | |
|
|
@@ -132,19 +173,23 @@ IDS base spacing unit = 4px (0.25rem). Map Figma pixel values:
|
|
|
132
173
|
|
|
133
174
|
## Typography
|
|
134
175
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
|
138
|
-
|
|
|
139
|
-
| Heading /
|
|
140
|
-
| Heading /
|
|
141
|
-
| Heading /
|
|
142
|
-
|
|
|
143
|
-
|
|
|
144
|
-
| Body / MD
|
|
145
|
-
| Body /
|
|
146
|
-
| Body /
|
|
147
|
-
|
|
|
176
|
+
Prefer semantic HTML elements via the `element` prop — they convey meaning to screen readers. Only fall back to `textStyle` when no semantic element matches the visual treatment.
|
|
177
|
+
|
|
178
|
+
| Figma Text Style | IDS Token | Component |
|
|
179
|
+
| ---------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- |
|
|
180
|
+
| Heading / H1 (Ubuntu 24px/500) | `typography.heading.1` | `<IressText element="h1">` |
|
|
181
|
+
| Heading / H2 (Ubuntu 20px/500) | `typography.heading.2` | `<IressText element="h2">` |
|
|
182
|
+
| Heading / H3 (Ubuntu 18px/500) | `typography.heading.3` | `<IressText element="h3">` |
|
|
183
|
+
| Heading / H4 (Ubuntu 16px/500) | `typography.heading.4` | `<IressText element="h4">` |
|
|
184
|
+
| Heading / H5 (Ubuntu 16px/400) | `typography.heading.5` | `<IressText element="h5">` |
|
|
185
|
+
| Body / MD Regular (Inter 14px/400) | `typography.body.md.regular` | `<IressText>` (default) or `<IressText element="p">` for paragraph semantics |
|
|
186
|
+
| Body / MD Medium (Inter 14px/500) | `typography.body.md.medium` | `<IressText textStyle="typography.body.md.medium">` — use `textStyle` because there is no semantic medium element |
|
|
187
|
+
| Body / MD Strong (Inter 14px/600) | `typography.body.md.strong` | `<IressText element="strong">` — conveys emphasis to screen readers |
|
|
188
|
+
| Body / SM Regular (Inter 12px/400) | `typography.body.sm` | `<IressText element="small">` — conveys fine print / secondary text |
|
|
189
|
+
| Body / SM Strong (Inter 12px/600) | `typography.body.sm.strong` | `<IressText element="small"><strong>...</strong></IressText>` |
|
|
190
|
+
| Code (Space 16px/400) | `typography.code` | `<IressText element="code">` |
|
|
191
|
+
|
|
192
|
+
> **When to use `textStyle`:** Only when you need to visually override the default styling of a semantic element — e.g. making an `h2` look like an `h4` for visual hierarchy while keeping the correct heading level for accessibility: `<IressText element="h2" textStyle="typography.heading.4">`.
|
|
148
193
|
|
|
149
194
|
> **Tip:** When translating Figma frames that contain mixed or unstructured text (e.g. CMS content, markdown, rich text blocks), wrap the content in `IressText` and nest native HTML elements (`<p>`, `<strong>`, `<a>`, `<ul>`, etc.) inside it. This is an allowed pattern that lets `IressText` apply consistent typography while preserving the original content structure.
|
|
150
195
|
|
|
@@ -176,8 +221,8 @@ import {
|
|
|
176
221
|
|
|
177
222
|
function LoginForm() {
|
|
178
223
|
return (
|
|
179
|
-
<IressCard p="
|
|
180
|
-
<IressStack gap="
|
|
224
|
+
<IressCard p="lg">
|
|
225
|
+
<IressStack gap="md">
|
|
181
226
|
<IressText element="h2">Log In</IressText>
|
|
182
227
|
<IressField label="Email" htmlFor="email" required>
|
|
183
228
|
<IressInput id="email" type="email" />
|
|
@@ -256,39 +301,41 @@ import { IressModal } from '@iress-oss/ids-components';
|
|
|
256
301
|
|
|
257
302
|
```tsx
|
|
258
303
|
import { IressTable, IressTag, IressButton } from '@iress-oss/ids-components';
|
|
304
|
+
import type { TableColumn } from '@iress-oss/ids-components';
|
|
259
305
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
);
|
|
306
|
+
interface User {
|
|
307
|
+
name: string;
|
|
308
|
+
email: string;
|
|
309
|
+
status: string;
|
|
310
|
+
id: string;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const columns: TableColumn<User>[] = [
|
|
314
|
+
{ key: 'name', label: 'Name' },
|
|
315
|
+
{ key: 'email', label: 'Email' },
|
|
316
|
+
{
|
|
317
|
+
key: 'status',
|
|
318
|
+
label: 'Status',
|
|
319
|
+
format: (value) => <IressTag>{value}</IressTag>,
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
key: 'actions',
|
|
323
|
+
label: 'Actions',
|
|
324
|
+
format: (_, row) => (
|
|
325
|
+
<IressButton mode="tertiary" icon="edit">
|
|
326
|
+
Edit
|
|
327
|
+
</IressButton>
|
|
328
|
+
),
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
|
|
332
|
+
function UsersTable({ users }: { users: User[] }) {
|
|
333
|
+
return <IressTable caption="Users" rows={users} columns={columns} />;
|
|
289
334
|
}
|
|
290
335
|
```
|
|
291
336
|
|
|
337
|
+
> **Key insight:** `IressTable` is data-driven — pass `rows` and `columns` props instead of composing sub-components. Use the `format` function on columns to render custom cell content like tags or buttons.
|
|
338
|
+
|
|
292
339
|
## Responsive Layout
|
|
293
340
|
|
|
294
341
|
**Always produce responsive output**, even when Figma only provides a single desktop frame. IDS uses a 12-column grid with 6 breakpoints — every translation should consider how the layout adapts to smaller screens.
|
|
@@ -351,10 +398,12 @@ When Figma provides separate mobile and desktop frames for the same layout:
|
|
|
351
398
|
When Figma only provides a desktop frame with a sidebar + main content area, infer the mobile layout:
|
|
352
399
|
|
|
353
400
|
```tsx
|
|
401
|
+
import { useState } from 'react';
|
|
354
402
|
import {
|
|
355
403
|
useBreakpoint,
|
|
356
404
|
IressSlideout,
|
|
357
405
|
IressButton,
|
|
406
|
+
IressStack,
|
|
358
407
|
IressRow,
|
|
359
408
|
IressCol,
|
|
360
409
|
} from '@iress-oss/ids-components';
|
|
@@ -368,7 +417,7 @@ function Page() {
|
|
|
368
417
|
<>
|
|
369
418
|
{isMobile ? (
|
|
370
419
|
// Mobile: primary content first, secondary content in slideout
|
|
371
|
-
<IressStack gap="
|
|
420
|
+
<IressStack gap="md">
|
|
372
421
|
<IressButton
|
|
373
422
|
mode="secondary"
|
|
374
423
|
icon="filter_list"
|
|
@@ -425,17 +474,42 @@ function Navigation() {
|
|
|
425
474
|
|
|
426
475
|
## Best Practices
|
|
427
476
|
|
|
428
|
-
1. **Use
|
|
429
|
-
2. **
|
|
430
|
-
3. **
|
|
431
|
-
4. **
|
|
432
|
-
5. **
|
|
433
|
-
6. **
|
|
434
|
-
7. **
|
|
435
|
-
8. **
|
|
477
|
+
1. **Minimise component nesting** — Use the fewest components possible. Every wrapper must earn its place. Before adding `IressInline` or `IressStack`, check whether the parent already handles layout (e.g. `IressCard` has `heading` and `footer` props; `IressModal` has `actions`; `IressButtonGroup` handles horizontal button layout). Don't wrap a single child in a layout component.
|
|
478
|
+
2. **Use IDS components, not raw elements** — IDS components encapsulate correct spacing, colours, border radius, and accessibility
|
|
479
|
+
3. **Don't recreate component internals** — If Figma shows a button with specific padding/radius, use `IressButton` with the right `mode` — the styling is built in
|
|
480
|
+
4. **Map Figma gap/padding to spacing tokens** — Divide pixel value by 4 to get the token number, then use the full token: 16px → `"spacing.4"`, 24px → `"spacing.6"`. Alias tokens (`"xs"`, `"sm"`, `"md"`, `"lg"`, `"xl"`) are also valid. Never use bare numbers like `gap="4"`.
|
|
481
|
+
5. **Prefer semantic props over manual styling** — Use `status="danger"` instead of `bg="colour.system.danger.fill"`
|
|
482
|
+
6. **Use IressField for all form inputs** — It provides the label, hint, and validation layout
|
|
483
|
+
7. **Respect responsive patterns** — Use `hideFrom`/`hideBelow` props or the `useBreakpoint` hook for responsive visibility; use responsive `span` on `IressCol` for adaptive grid layouts
|
|
484
|
+
8. **Always make grid layouts responsive** — When translating Figma multi-column layouts, use responsive `span` values (e.g. `span={{ xs: 12, md: 6 }}`) so columns stack on mobile
|
|
485
|
+
9. **Check the component docs** — Read the specific component doc for detailed props and patterns (`node_modules/@iress-oss/ids-components/.ai/components/`)
|
|
436
486
|
|
|
437
487
|
## Common Mistakes
|
|
438
488
|
|
|
489
|
+
### Unnecessary layout wrappers
|
|
490
|
+
|
|
491
|
+
Don't add `IressInline` or `IressStack` when it adds no value. Every Figma auto-layout frame does NOT need its own layout wrapper — check the IDS component first.
|
|
492
|
+
|
|
493
|
+
```tsx
|
|
494
|
+
// ❌ Unnecessary nesting — IressStack wrapping a single child
|
|
495
|
+
<IressStack gap="md">
|
|
496
|
+
<IressInline gap="sm">
|
|
497
|
+
<IressButton mode="primary">Save</IressButton>
|
|
498
|
+
<IressButton mode="secondary">Cancel</IressButton>
|
|
499
|
+
</IressInline>
|
|
500
|
+
</IressStack>
|
|
501
|
+
|
|
502
|
+
// ✅ Single group of buttons only needs IressInline
|
|
503
|
+
<IressInline gap="sm">
|
|
504
|
+
<IressButton mode="primary">Save</IressButton>
|
|
505
|
+
<IressButton mode="secondary">Cancel</IressButton>
|
|
506
|
+
</IressInline>
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
**Rule of thumb:** When Figma shows an auto-layout frame, check if the corresponding IDS component already provides that layout before adding a wrapper. Components like `IressModal` (with `actions`) and `IressButtonGroup` already handle their internal layout. For `IressCard`, use the `heading` and `footer` props to structure content — but note the `footer` slot does not auto-layout its children, so use `IressInline` inside `footer` when you need horizontal button layout.
|
|
510
|
+
|
|
511
|
+
### Other common anti-patterns
|
|
512
|
+
|
|
439
513
|
For the full list of common anti-patterns (disabled buttons, redundant textStyle, legacy slot attributes, raw HTML, hardcoded values), read the Common Mistakes guide at `node_modules/@iress-oss/ids-components/.ai/guides/foundations-common-mistakes.md` (requires `@iress-oss/ids-components` to be installed).
|
|
440
514
|
|
|
441
515
|
**Figma-specific addition:** When Figma shows named content areas ("prepend", "append", "footer"), map them to the corresponding **React prop**, not to a `slot` attribute. When Figma shows a greyed-out or disabled button state, do not use `disabled` — see the guide for alternatives.
|
|
@@ -31,10 +31,10 @@ Translate natural language UI descriptions into IDS (Iress Design System) compon
|
|
|
31
31
|
| Select dropdown (static or async) | `IressField` + `IressSelect` | `<IressField label="Country"><IressSelect>...</IressSelect></IressField>` — supports static options and async loading via an `options` function |
|
|
32
32
|
| Freetext input with suggestions | `IressField` + `IressAutocomplete` | `<IressField label="Search"><IressAutocomplete /></IressField>` — allows any text input; suggestions are optional |
|
|
33
33
|
| Currency input | `IressField` + `IressInputCurrency` | `<IressField label="Amount"><IressInputCurrency /></IressField>` |
|
|
34
|
-
| Checkbox | `IressCheckbox` | `<IressCheckbox
|
|
35
|
-
| Checkbox group | `IressCheckboxGroup` | `<
|
|
36
|
-
| Radio buttons | `IressRadioGroup` + `IressRadio` | `<
|
|
37
|
-
| Toggle switch | `IressToggle` | `<IressToggle
|
|
34
|
+
| Checkbox | `IressCheckbox` | `<IressCheckbox value="agree">I agree</IressCheckbox>` |
|
|
35
|
+
| Checkbox group | `IressCheckboxGroup` | `<IressField label="Options"><IressCheckboxGroup name="opts"><IressCheckbox value="a">A</IressCheckbox><IressCheckbox value="b">B</IressCheckbox></IressCheckboxGroup></IressField>` |
|
|
36
|
+
| Radio buttons | `IressRadioGroup` + `IressRadio` | `<IressField label="Choice"><IressRadioGroup><IressRadio value="yes">Yes</IressRadio><IressRadio value="no">No</IressRadio></IressRadioGroup></IressField>` |
|
|
37
|
+
| Toggle switch | `IressToggle` | `<IressToggle>Enable</IressToggle>` |
|
|
38
38
|
| Slider / range | `IressSlider` | `<IressSlider min={0} max={100} />` |
|
|
39
39
|
| Read-only display | `IressReadonly` | `<IressReadonly label="Status" value="Active" />` — supports `actions` prop for inline action buttons (e.g. edit toggle). Use `variant="locked"` when the value is read-only due to permissions |
|
|
40
40
|
|
|
@@ -47,8 +47,8 @@ Translate natural language UI descriptions into IDS (Iress Design System) compon
|
|
|
47
47
|
|
|
48
48
|
| Description | IDS Component | Example |
|
|
49
49
|
| -------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
50
|
-
| Vertical stack (items stacked top-to-bottom) | `IressStack` | `<IressStack gap="
|
|
51
|
-
| Horizontal row (items side-by-side) | `IressInline` | `<IressInline gap="
|
|
50
|
+
| Vertical stack (items stacked top-to-bottom) | `IressStack` | `<IressStack gap="md">...</IressStack>` |
|
|
51
|
+
| Horizontal row (items side-by-side) | `IressInline` | `<IressInline gap="sm">...</IressInline>` |
|
|
52
52
|
| Grid columns | `IressRow` + `IressCol` | `<IressRow><IressCol span={{ xs: 12, md: 6 }}>...</IressCol><IressCol span={{ xs: 12, md: 6 }}>...</IressCol></IressRow>` |
|
|
53
53
|
| Container with max-width | `IressContainer` | `<IressContainer>...</IressContainer>` |
|
|
54
54
|
| Divider / separator | `IressDivider` | `<IressDivider />` |
|
|
@@ -60,7 +60,7 @@ Translate natural language UI descriptions into IDS (Iress Design System) compon
|
|
|
60
60
|
| -------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
61
61
|
| Text / paragraph | `IressText` | `<IressText>Body text</IressText>` |
|
|
62
62
|
| Heading | `IressText element="h2"` | `<IressText element="h2">Heading</IressText>` |
|
|
63
|
-
| Card / panel | `IressCard` or `IressPanel` | `<IressCard
|
|
63
|
+
| Card / panel | `IressCard` or `IressPanel` | `<IressCard heading={<h3>Title</h3>}>Content</IressCard>` |
|
|
64
64
|
| Alert / notification | `IressAlert` | `<IressAlert status="success">Saved!</IressAlert>` |
|
|
65
65
|
| Loading spinner | `IressSpinner` | `<IressSpinner />` |
|
|
66
66
|
| Skeleton loader | `IressSkeleton` | `<IressSkeleton height="20px" width="200px" />` |
|
|
@@ -89,11 +89,11 @@ Translate natural language UI descriptions into IDS (Iress Design System) compon
|
|
|
89
89
|
|
|
90
90
|
| Description | IDS Component | Example |
|
|
91
91
|
| ----------- | ------------- | ------------------------------------------------------------------------------------------------------- |
|
|
92
|
-
| Data table | `IressTable` | `<IressTable
|
|
92
|
+
| Data table | `IressTable` | `<IressTable caption="Users" rows={users} columns={columns} />` — data-driven via `rows` and `columns` props |
|
|
93
93
|
3. **Verify component capabilities** — Before recommending a component, read its `.ai/components/<name>.md` doc (in `node_modules/@iress-oss/ids-components/.ai/components/`) to verify it supports the required features (async, filtering, validation, etc.)
|
|
94
|
-
4. **Apply layout** — Wrap elements in `IressStack` (vertical), `IressInline` (horizontal), or `IressRow`/`IressCol` (grid). Always make grids responsive with `span={{ xs: 12, md: ... }}`
|
|
94
|
+
4. **Apply layout** — Wrap elements in `IressStack` (vertical), `IressInline` (horizontal), or `IressRow`/`IressCol` (grid) only when needed. Check whether the parent component already provides layout (e.g. card footer, modal actions, button group) before adding wrappers. Never wrap a single child in a layout component. Always make grids responsive with `span={{ xs: 12, md: ... }}`
|
|
95
95
|
5. **Add responsive behaviour** — Even if the description only mentions desktop, stack columns on mobile and relocate secondary content to `IressSlideout` or collapsible sections
|
|
96
|
-
6. **Apply styling** — Use styling props for spacing, colour, and typography.
|
|
96
|
+
6. **Apply styling** — Use styling props for spacing, colour, and typography. Spacing tokens must include the category prefix: `gap="spacing.4"`, `p="spacing.6"`. Alias tokens (`"xs"`, `"sm"`, `"md"`, `"lg"`, `"xl"`) are also valid. Never use bare numbers like `gap="4"`.
|
|
97
97
|
|
|
98
98
|
# Styling Props (IressCSSProps)
|
|
99
99
|
|
|
@@ -190,10 +190,10 @@ When you need to find the right IDS component for a UI element, read references/
|
|
|
190
190
|
| Select dropdown (static or async) | `IressField` + `IressSelect` | `<IressField label="Country"><IressSelect>...</IressSelect></IressField>` — supports static options and async loading via an `options` function |
|
|
191
191
|
| Freetext input with suggestions | `IressField` + `IressAutocomplete` | `<IressField label="Search"><IressAutocomplete /></IressField>` — allows any text input; suggestions are optional |
|
|
192
192
|
| Currency input | `IressField` + `IressInputCurrency` | `<IressField label="Amount"><IressInputCurrency /></IressField>` |
|
|
193
|
-
| Checkbox | `IressCheckbox` | `<IressCheckbox
|
|
194
|
-
| Checkbox group | `IressCheckboxGroup` | `<
|
|
195
|
-
| Radio buttons | `IressRadioGroup` + `IressRadio` | `<
|
|
196
|
-
| Toggle switch | `IressToggle` | `<IressToggle
|
|
193
|
+
| Checkbox | `IressCheckbox` | `<IressCheckbox value="agree">I agree</IressCheckbox>` |
|
|
194
|
+
| Checkbox group | `IressCheckboxGroup` | `<IressField label="Options"><IressCheckboxGroup name="opts"><IressCheckbox value="a">A</IressCheckbox><IressCheckbox value="b">B</IressCheckbox></IressCheckboxGroup></IressField>` |
|
|
195
|
+
| Radio buttons | `IressRadioGroup` + `IressRadio` | `<IressField label="Choice"><IressRadioGroup><IressRadio value="yes">Yes</IressRadio><IressRadio value="no">No</IressRadio></IressRadioGroup></IressField>` |
|
|
196
|
+
| Toggle switch | `IressToggle` | `<IressToggle>Enable</IressToggle>` |
|
|
197
197
|
| Slider / range | `IressSlider` | `<IressSlider min={0} max={100} />` |
|
|
198
198
|
| Read-only display | `IressReadonly` | `<IressReadonly label="Status" value="Active" />` — supports `actions` prop for inline action buttons (e.g. edit toggle). Use `variant="locked"` when the value is read-only due to permissions |
|
|
199
199
|
|
|
@@ -206,8 +206,8 @@ When you need to find the right IDS component for a UI element, read references/
|
|
|
206
206
|
|
|
207
207
|
| Description | IDS Component | Example |
|
|
208
208
|
| -------------------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
209
|
-
| Vertical stack (items stacked top-to-bottom) | `IressStack` | `<IressStack gap="
|
|
210
|
-
| Horizontal row (items side-by-side) | `IressInline` | `<IressInline gap="
|
|
209
|
+
| Vertical stack (items stacked top-to-bottom) | `IressStack` | `<IressStack gap="md">...</IressStack>` |
|
|
210
|
+
| Horizontal row (items side-by-side) | `IressInline` | `<IressInline gap="sm">...</IressInline>` |
|
|
211
211
|
| Grid columns | `IressRow` + `IressCol` | `<IressRow><IressCol span={{ xs: 12, md: 6 }}>...</IressCol><IressCol span={{ xs: 12, md: 6 }}>...</IressCol></IressRow>` |
|
|
212
212
|
| Container with max-width | `IressContainer` | `<IressContainer>...</IressContainer>` |
|
|
213
213
|
| Divider / separator | `IressDivider` | `<IressDivider />` |
|
|
@@ -219,7 +219,7 @@ When you need to find the right IDS component for a UI element, read references/
|
|
|
219
219
|
| -------------------- | --------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
220
220
|
| Text / paragraph | `IressText` | `<IressText>Body text</IressText>` |
|
|
221
221
|
| Heading | `IressText element="h2"` | `<IressText element="h2">Heading</IressText>` |
|
|
222
|
-
| Card / panel | `IressCard` or `IressPanel` | `<IressCard
|
|
222
|
+
| Card / panel | `IressCard` or `IressPanel` | `<IressCard heading={<h3>Title</h3>}>Content</IressCard>` |
|
|
223
223
|
| Alert / notification | `IressAlert` | `<IressAlert status="success">Saved!</IressAlert>` |
|
|
224
224
|
| Loading spinner | `IressSpinner` | `<IressSpinner />` |
|
|
225
225
|
| Skeleton loader | `IressSkeleton` | `<IressSkeleton height="20px" width="200px" />` |
|
|
@@ -248,7 +248,7 @@ When you need to find the right IDS component for a UI element, read references/
|
|
|
248
248
|
|
|
249
249
|
| Description | IDS Component | Example |
|
|
250
250
|
| ----------- | ------------- | ------------------------------------------------------------------------------------------------------- |
|
|
251
|
-
| Data table | `IressTable` | `<IressTable
|
|
251
|
+
| Data table | `IressTable` | `<IressTable caption="Users" rows={users} columns={columns} />` — data-driven via `rows` and `columns` props |
|
|
252
252
|
|
|
253
253
|
## Styling Props
|
|
254
254
|
|
|
@@ -355,7 +355,7 @@ import {
|
|
|
355
355
|
|
|
356
356
|
function LoginForm() {
|
|
357
357
|
return (
|
|
358
|
-
<IressStack gap="
|
|
358
|
+
<IressStack gap="md">
|
|
359
359
|
<IressField label="Email" htmlFor="email" required>
|
|
360
360
|
<IressInput id="email" type="email" placeholder="Enter your email" />
|
|
361
361
|
</IressField>
|
|
@@ -377,30 +377,20 @@ function LoginForm() {
|
|
|
377
377
|
### "A card with a title, description, and two action buttons"
|
|
378
378
|
|
|
379
379
|
```tsx
|
|
380
|
-
import {
|
|
381
|
-
IressCard,
|
|
382
|
-
IressButton,
|
|
383
|
-
IressInline,
|
|
384
|
-
IressText,
|
|
385
|
-
} from '@iress-oss/ids-components';
|
|
380
|
+
import { IressCard, IressButton, IressInline } from '@iress-oss/ids-components';
|
|
386
381
|
|
|
387
382
|
function ActionCard() {
|
|
388
383
|
return (
|
|
389
|
-
<IressCard
|
|
390
|
-
<
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
<IressCard.Body>
|
|
394
|
-
<IressText>
|
|
395
|
-
This is the card description with supporting details.
|
|
396
|
-
</IressText>
|
|
397
|
-
</IressCard.Body>
|
|
398
|
-
<IressCard.Footer>
|
|
399
|
-
<IressInline gap="2">
|
|
384
|
+
<IressCard
|
|
385
|
+
heading={<h3>Card Title</h3>}
|
|
386
|
+
footer={
|
|
387
|
+
<IressInline gap="sm">
|
|
400
388
|
<IressButton mode="primary">Confirm</IressButton>
|
|
401
389
|
<IressButton mode="secondary">Cancel</IressButton>
|
|
402
390
|
</IressInline>
|
|
403
|
-
|
|
391
|
+
}
|
|
392
|
+
>
|
|
393
|
+
This is the card description with supporting details.
|
|
404
394
|
</IressCard>
|
|
405
395
|
);
|
|
406
396
|
}
|
|
@@ -417,19 +407,22 @@ import {
|
|
|
417
407
|
IressButton,
|
|
418
408
|
IressText,
|
|
419
409
|
IressDivider,
|
|
410
|
+
IressField,
|
|
420
411
|
} from '@iress-oss/ids-components';
|
|
421
412
|
|
|
422
413
|
function SettingsPage() {
|
|
423
414
|
return (
|
|
424
|
-
<IressStack gap="
|
|
415
|
+
<IressStack gap="lg">
|
|
425
416
|
<IressText element="h2">Settings</IressText>
|
|
426
|
-
<IressToggle
|
|
417
|
+
<IressToggle>Enable notifications</IressToggle>
|
|
427
418
|
<IressDivider />
|
|
428
|
-
<
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
419
|
+
<IressField label="Notification types">
|
|
420
|
+
<IressCheckboxGroup name="notification-types">
|
|
421
|
+
<IressCheckbox value="email">Email</IressCheckbox>
|
|
422
|
+
<IressCheckbox value="sms">SMS</IressCheckbox>
|
|
423
|
+
<IressCheckbox value="push">Push</IressCheckbox>
|
|
424
|
+
</IressCheckboxGroup>
|
|
425
|
+
</IressField>
|
|
433
426
|
<IressDivider />
|
|
434
427
|
<IressButton mode="primary">Save settings</IressButton>
|
|
435
428
|
</IressStack>
|
|
@@ -442,8 +435,8 @@ function SettingsPage() {
|
|
|
442
435
|
Note: even though the description doesn't mention mobile, the sidebar is secondary content — on mobile it should move into a slideout so the main content gets focus.
|
|
443
436
|
|
|
444
437
|
```tsx
|
|
438
|
+
import { useState } from 'react';
|
|
445
439
|
import {
|
|
446
|
-
useState,
|
|
447
440
|
IressRow,
|
|
448
441
|
IressCol,
|
|
449
442
|
IressStack,
|
|
@@ -460,18 +453,13 @@ function Dashboard() {
|
|
|
460
453
|
const [navOpen, setNavOpen] = useState(false);
|
|
461
454
|
|
|
462
455
|
const nav = (
|
|
463
|
-
<IressCard>
|
|
464
|
-
|
|
465
|
-
<IressStack gap="2">
|
|
466
|
-
<IressText weight="strong">Navigation</IressText>
|
|
467
|
-
<IressText>Menu items here</IressText>
|
|
468
|
-
</IressStack>
|
|
469
|
-
</IressCard.Body>
|
|
456
|
+
<IressCard heading={<h3>Navigation</h3>}>
|
|
457
|
+
Menu items here
|
|
470
458
|
</IressCard>
|
|
471
459
|
);
|
|
472
460
|
|
|
473
461
|
return isMobile ? (
|
|
474
|
-
<IressStack gap="
|
|
462
|
+
<IressStack gap="md">
|
|
475
463
|
<IressButton
|
|
476
464
|
mode="secondary"
|
|
477
465
|
icon="menu"
|
|
@@ -480,9 +468,7 @@ function Dashboard() {
|
|
|
480
468
|
Menu
|
|
481
469
|
</IressButton>
|
|
482
470
|
<IressCard>
|
|
483
|
-
<
|
|
484
|
-
<IressText element="h2">Main Content</IressText>
|
|
485
|
-
</IressCard.Body>
|
|
471
|
+
<IressText element="h2">Main Content</IressText>
|
|
486
472
|
</IressCard>
|
|
487
473
|
<IressSlideout
|
|
488
474
|
heading="Navigation"
|
|
@@ -497,9 +483,7 @@ function Dashboard() {
|
|
|
497
483
|
<IressCol span={3}>{nav}</IressCol>
|
|
498
484
|
<IressCol span={9}>
|
|
499
485
|
<IressCard>
|
|
500
|
-
<
|
|
501
|
-
<IressText element="h2">Main Content</IressText>
|
|
502
|
-
</IressCard.Body>
|
|
486
|
+
<IressText element="h2">Main Content</IressText>
|
|
503
487
|
</IressCard>
|
|
504
488
|
</IressCol>
|
|
505
489
|
</IressRow>
|
|
@@ -509,18 +493,60 @@ function Dashboard() {
|
|
|
509
493
|
|
|
510
494
|
## Best Practices
|
|
511
495
|
|
|
512
|
-
1. **
|
|
513
|
-
2. **
|
|
514
|
-
3. **Use
|
|
515
|
-
4. **Use
|
|
516
|
-
5. **Use
|
|
517
|
-
6. **
|
|
518
|
-
7. **
|
|
519
|
-
8. **
|
|
520
|
-
9. **
|
|
521
|
-
10. **
|
|
522
|
-
11. **
|
|
496
|
+
1. **Minimise component nesting** — Use the fewest components possible to achieve the layout. Every wrapper component should earn its place. Before adding `IressInline` or `IressStack`, check whether the parent component already handles the layout (e.g. `IressCard` has `heading` and `footer` props; `IressModal` has `actions`; `IressButtonGroup` handles horizontal button layout). See the "Unnecessary layout wrappers" section in Common Mistakes below.
|
|
497
|
+
2. **Always wrap in IressProvider** — Required at the root of your app for fonts and CSS variables. `IressProvider` already includes `IressModalProvider`, `IressSlideoutProvider`, `IressToasterProvider`, and `IressIconProvider` — do not add these separately. If using `IressShadow`, no additional providers are needed as it includes `IressProvider` internally.
|
|
498
|
+
3. **Use IressField for all form inputs** — Provides consistent labels, hints, and validation display
|
|
499
|
+
4. **Use IressStack/IressInline only when needed** — Prefer these over custom CSS flex/grid, but don't add them when the parent already provides spacing or layout
|
|
500
|
+
5. **Use correct spacing token format** — Always prefix with the token category: `gap="spacing.4"`, `p="spacing.6"`. Alias tokens (`"xs"`, `"sm"`, `"md"`, `"lg"`, `"xl"`) are also valid. Never use bare numbers like `gap="4"`.
|
|
501
|
+
6. **Use semantic button modes** — One `primary` per section, `secondary` for most actions
|
|
502
|
+
7. **Always include labels** — All form inputs need accessible labels via `IressField`
|
|
503
|
+
8. **Use status for feedback** — `IressAlert` for inline messages, `IressModal status="danger"` for confirmation dialogs, `status` prop on buttons for danger/success
|
|
504
|
+
9. **Prefer IDS components** — Use `IressText` instead of raw `<p>`, `IressButton` instead of `<button>`
|
|
505
|
+
10. **Native elements inside `IressText` are OK** — When rendering CMS content, markdown output, or other unstructured data sources, it is acceptable to nest native HTML elements (e.g. `<p>`, `<strong>`, `<a>`, `<ul>`) inside `IressText`. This lets `IressText` provide consistent typography while allowing flexible inner content structure.
|
|
506
|
+
11. **Always make grid layouts responsive** — When using `IressRow`/`IressCol`, use responsive `span` values (e.g. `span={{ xs: 12, md: 6 }}`) so columns stack on mobile instead of overflowing
|
|
507
|
+
12. **Check the component docs** — Read the specific component doc for detailed props and patterns (`node_modules/@iress-oss/ids-components/.ai/components/`)
|
|
523
508
|
|
|
524
509
|
## Common Mistakes
|
|
525
510
|
|
|
511
|
+
### Unnecessary layout wrappers
|
|
512
|
+
|
|
513
|
+
The most common mistake is wrapping children in `IressInline` or `IressStack` when it adds no value. Every wrapper must serve a purpose — if removing it produces the same result, remove it.
|
|
514
|
+
|
|
515
|
+
```tsx
|
|
516
|
+
// ❌ Unnecessary nesting — IressStack wrapping a single child
|
|
517
|
+
<IressStack gap="md">
|
|
518
|
+
<IressInline gap="sm">
|
|
519
|
+
<IressButton mode="primary">Save</IressButton>
|
|
520
|
+
<IressButton mode="secondary">Cancel</IressButton>
|
|
521
|
+
</IressInline>
|
|
522
|
+
</IressStack>
|
|
523
|
+
|
|
524
|
+
// ✅ Single group of buttons only needs IressInline
|
|
525
|
+
<IressInline gap="sm">
|
|
526
|
+
<IressButton mode="primary">Save</IressButton>
|
|
527
|
+
<IressButton mode="secondary">Cancel</IressButton>
|
|
528
|
+
</IressInline>
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
```tsx
|
|
532
|
+
// ❌ Wrapping content that's already a single block
|
|
533
|
+
<IressStack gap="md">
|
|
534
|
+
<IressText element="h2">Title</IressText>
|
|
535
|
+
</IressStack>
|
|
536
|
+
|
|
537
|
+
// ✅ No wrapper needed for a single element
|
|
538
|
+
<IressText element="h2">Title</IressText>
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**When to use layout wrappers:**
|
|
542
|
+
- `IressStack` — when you have 2+ block-level siblings that need vertical spacing between them
|
|
543
|
+
- `IressInline` — when you have 2+ elements that need to sit side-by-side (e.g. buttons in a card `footer`, action bars)
|
|
544
|
+
|
|
545
|
+
**When NOT to use them:**
|
|
546
|
+
- The parent component already handles layout (modal `actions` prop, button group)
|
|
547
|
+
- There's only one child — a wrapper around a single child adds nothing
|
|
548
|
+
- You're nesting `IressInline` inside `IressInline` or `IressStack` inside `IressStack` without changing gap/alignment — flatten instead
|
|
549
|
+
|
|
550
|
+
### Other common anti-patterns
|
|
551
|
+
|
|
526
552
|
For the full list of common anti-patterns (disabled buttons, redundant textStyle, legacy slot attributes, raw HTML, hardcoded values), read the Common Mistakes guide at `node_modules/@iress-oss/ids-components/.ai/guides/foundations-common-mistakes.md` (requires `@iress-oss/ids-components` to be installed).
|
|
@@ -47,4 +47,4 @@ export interface IressContextualMenuProps extends Omit<IressPopoverProps, 'activ
|
|
|
47
47
|
*/
|
|
48
48
|
onAction?: (item: ContextualMenuItem) => void;
|
|
49
49
|
}
|
|
50
|
-
export declare const IressContextualMenu: ({ ariaLabel, align, bordered, children, className, "data-testid": dataTestId, items, offset, onAction, size, textStyle, theme, ...restProps }: IressContextualMenuProps) => import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare const IressContextualMenu: ({ ariaLabel, align, bordered, children, className, container, "data-testid": dataTestId, items, offset, onAction, size, textStyle, theme, ...restProps }: IressContextualMenuProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -13,46 +13,47 @@ import { contextualMenu as d } from "./ContextualMenu.styles.js";
|
|
|
13
13
|
import { createElement as f, useMemo as p } from "react";
|
|
14
14
|
import { jsx as m, jsxs as h } from "react/jsx-runtime";
|
|
15
15
|
//#region src/patterns/ContextualMenu/ContextualMenu.tsx
|
|
16
|
-
var g = ({ ariaLabel: g = "More options", align: _ = "bottom-end", bordered: v = !1, children: y, className: b, "data-testid":
|
|
16
|
+
var g = ({ ariaLabel: g = "More options", align: _ = "bottom-end", bordered: v = !1, children: y, className: b, container: x, "data-testid": S, items: C, offset: w = {
|
|
17
17
|
mainAxis: -6,
|
|
18
18
|
crossAxis: 0
|
|
19
|
-
}, onAction:
|
|
20
|
-
let [
|
|
19
|
+
}, onAction: T, size: E = "small", textStyle: D, theme: O = "light", ...k }) => {
|
|
20
|
+
let [A, j] = p(() => n(k), [k]), M = d({
|
|
21
21
|
bordered: v,
|
|
22
|
-
size:
|
|
23
|
-
theme:
|
|
22
|
+
size: E,
|
|
23
|
+
theme: O
|
|
24
24
|
});
|
|
25
25
|
return /* @__PURE__ */ m(r.div, {
|
|
26
|
-
className: t(e(
|
|
27
|
-
"data-size":
|
|
28
|
-
"data-theme":
|
|
29
|
-
"data-testid":
|
|
26
|
+
className: t(e(A), b, M.root, i.ContextualMenu),
|
|
27
|
+
"data-size": E,
|
|
28
|
+
"data-theme": O,
|
|
29
|
+
"data-testid": S,
|
|
30
30
|
children: /* @__PURE__ */ h(c, {
|
|
31
|
-
...
|
|
31
|
+
...j,
|
|
32
32
|
align: _,
|
|
33
|
+
container: x,
|
|
33
34
|
activator: /* @__PURE__ */ m(o, {
|
|
34
35
|
"aria-label": g,
|
|
35
|
-
className:
|
|
36
|
-
"data-testid": s(
|
|
36
|
+
className: M.trigger,
|
|
37
|
+
"data-testid": s(S, "activator"),
|
|
37
38
|
mode: "quaternary",
|
|
38
39
|
icon: "more_vert"
|
|
39
40
|
}),
|
|
40
41
|
contentStyle: {
|
|
41
|
-
className:
|
|
42
|
+
className: M.menu,
|
|
42
43
|
p: "none"
|
|
43
44
|
},
|
|
44
|
-
"data-testid": s(
|
|
45
|
-
offset:
|
|
45
|
+
"data-testid": s(S, "popover"),
|
|
46
|
+
offset: w,
|
|
46
47
|
type: "menu",
|
|
47
|
-
children: [!!
|
|
48
|
-
"data-testid": s(
|
|
49
|
-
children:
|
|
50
|
-
textStyle:
|
|
48
|
+
children: [!!C?.length && /* @__PURE__ */ m(l, {
|
|
49
|
+
"data-testid": s(S, "menu"),
|
|
50
|
+
children: C.map(({ icon: e, ...n }) => /* @__PURE__ */ f(u, {
|
|
51
|
+
textStyle: D ?? "typography.body.sm",
|
|
51
52
|
...n,
|
|
52
|
-
className: t(
|
|
53
|
+
className: t(M.item, n.className),
|
|
53
54
|
key: n.key,
|
|
54
55
|
onClick: (t) => {
|
|
55
|
-
|
|
56
|
+
T?.({
|
|
56
57
|
icon: e,
|
|
57
58
|
...n
|
|
58
59
|
}), n.onClick?.(t);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iress-oss/ids-components",
|
|
3
|
-
"version": "6.0.0-beta.
|
|
3
|
+
"version": "6.0.0-beta.4",
|
|
4
4
|
"description": "Iress React Component Library",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"@types/react": "^19.2.14",
|
|
45
45
|
"@types/react-dom": "^19.2.3",
|
|
46
46
|
"@vitejs/plugin-react": "^6.0.1",
|
|
47
|
-
"@vitest/coverage-v8": "4.1.
|
|
48
|
-
"@vitest/ui": "4.1.
|
|
47
|
+
"@vitest/coverage-v8": "4.1.2",
|
|
48
|
+
"@vitest/ui": "4.1.2",
|
|
49
49
|
"concurrently": "^9.2.1",
|
|
50
50
|
"dompurify": "^3.4.0",
|
|
51
51
|
"eslint": "10.2.0",
|
|
@@ -68,11 +68,11 @@
|
|
|
68
68
|
"rimraf": "^6.1.3",
|
|
69
69
|
"storybook": "10.3.4",
|
|
70
70
|
"typescript": "5.9.3",
|
|
71
|
-
"vite": "8.0.
|
|
71
|
+
"vite": "8.0.8",
|
|
72
72
|
"vite-bundle-visualizer": "^1.2.1",
|
|
73
73
|
"vite-plugin-dts": "4.5.4",
|
|
74
74
|
"vite-plugin-static-copy": "3.3.0",
|
|
75
|
-
"vitest": "4.1.
|
|
75
|
+
"vitest": "4.1.2"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
78
|
"@floating-ui/react": "0.27.19",
|