@eocrm/design-system 0.6.21 → 0.6.23
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/AI-PRIMER.md +167 -289
- package/README.md +28 -5
- package/docs/accessible-names.md +22 -0
- package/docs/components/CircularProgress.md +4 -0
- package/docs/components/CurrencySelect.md +1 -1
- package/docs/components/Kbd.md +1 -1
- package/docs/components/Link.md +2 -2
- package/docs/components/MoneyInput.md +1 -1
- package/docs/components/PhoneInput.md +1 -1
- package/docs/components/Progress.md +7 -0
- package/docs/components/Text.md +8 -0
- package/docs/components/Title.md +4 -0
- package/docs/components/index.json +652 -0
- package/docs/i18n.md +74 -0
- package/docs/setup.md +1 -1
- package/docs/typescript.md +9 -0
- package/package.json +2 -2
- package/src/components/Link/Link.tokens.scss +4 -3
- package/src/components/Link/Link.tsx +1 -1
- package/src/components/LiquidEditor/useLiquidAutocomplete.ts +1 -1
- package/src/components/RichText/RichTextAttachment.tsx +1 -1
- package/src/i18n/I18nProvider.tsx +1 -1
package/AI-PRIMER.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Primer for AI coding agents building UI on top of this package.
|
|
4
4
|
|
|
5
|
-
Read this first. Each component's contract is
|
|
5
|
+
Read this first. Each component's contract is `docs/components/<Name>.md`. Agents: install the MCP server so these docs match your installed version — see `@eocrm/design-system-mcp`.
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -20,263 +20,163 @@ Read this first. Each component's contract is its `docs/components/<Name>.md`; t
|
|
|
20
20
|
|
|
21
21
|
See [docs/setup.md](docs/setup.md).
|
|
22
22
|
|
|
23
|
-
Accent: blue by default; `<html data-accent="
|
|
23
|
+
Accent: blue by default; `<html data-accent="…">` switches to `tealpine`, `indigo`, `grape`, `berry`, `slate`, `ocean` or `moss` (see [README.md](./README.md#accent)).
|
|
24
24
|
|
|
25
25
|
## Localization (i18n)
|
|
26
26
|
|
|
27
|
-
Every user-facing string the library renders
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
import { I18nProvider } from '@eocrm/design-system';
|
|
31
|
-
|
|
32
|
-
<I18nProvider locale="ru">
|
|
33
|
-
<App />
|
|
34
|
-
</I18nProvider>;
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
**Overrides** are a deep-partial of the messages tree:
|
|
38
|
-
|
|
39
|
-
```tsx
|
|
40
|
-
<I18nProvider
|
|
41
|
-
locale="ru"
|
|
42
|
-
overrides={{
|
|
43
|
-
pagination: { next: 'Дальше' },
|
|
44
|
-
badge: { modified: 'Изменено!' },
|
|
45
|
-
}}
|
|
46
|
-
>
|
|
47
|
-
<App />
|
|
48
|
-
</I18nProvider>
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
- Missing override keys fall back to the locale defaults.
|
|
52
|
-
- Missing locale keys do NOT fall back to `en` — `I18nProvider` uses the one locale catalogue and `useTranslation` warns and returns the literal key string. The real guard is the type: `en` and `ru` are both annotated `: Messages` (total, not partial), so a missing key is a `tsc` error and cannot ship.
|
|
53
|
-
- No provider at all = `en` defaults.
|
|
54
|
-
|
|
55
|
-
**Available locales:** `'en'` (default), `'ru'`. v1.
|
|
56
|
-
|
|
57
|
-
**`I18nProvider` vs `LocaleProvider` — pair them.** `I18nProvider` carries the message catalog (translated strings). `LocaleProvider` carries the BCP-47 tag used by `Intl.*` formatters inside Calendar / DatePicker / DateRangePicker (month names, weekday order, date-range formatting). They are independent — a Russian app should wrap with BOTH so visible strings AND Intl-rendered dates speak the right language:
|
|
58
|
-
|
|
59
|
-
```tsx
|
|
60
|
-
<I18nProvider locale="ru">
|
|
61
|
-
<LocaleProvider locale="ru-RU">
|
|
62
|
-
<App />
|
|
63
|
-
</LocaleProvider>
|
|
64
|
-
</I18nProvider>
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
**Adding a new string in a library component**:
|
|
68
|
-
|
|
69
|
-
1. Add the key to `src/i18n/messages.ts` (`Messages` interface).
|
|
70
|
-
2. Add the English value to `src/i18n/en.ts`.
|
|
71
|
-
3. Add the Russian value to `src/i18n/ru.ts` (use `ruPlural()` from `i18n/format.ts` for count-varying strings).
|
|
72
|
-
4. In the component, `const t = useTranslation();` then `aria-label={t('component.key')}`. For array messages (months / weekdays), use `useTranslationArray()`.
|
|
73
|
-
5. Never inline an English string — `aria-label="Close"` is a Hard rule 9 violation.
|
|
74
|
-
|
|
75
|
-
**Drag-and-drop announcements** are localized too. Every drag surface (`Sortable`,
|
|
76
|
-
`SortableGroup`, `Kanban`, `DataTable` column reorder, `RichTextEditor` block
|
|
77
|
-
gutter) overrides dnd-kit's English defaults with the `drag.*` messages, and
|
|
78
|
-
names what it drags by its **rendered text** rather than its id — "Amount due,
|
|
79
|
-
position 3 of 7", not "col_amount was moved over droppable area col_name". Text
|
|
80
|
-
is read from the DOM, so a closed `DropdownMenu` / `Tooltip` / `Popover` inside
|
|
81
|
-
a card contributes nothing, and `aria-hidden` decoration is skipped.
|
|
82
|
-
|
|
83
|
-
Two things are worth doing yourself:
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
{/* Name the CONTAINER — the library can't invent one. Either attribute works. */}
|
|
87
|
-
<Kanban.Column id="qualified" aria-label="Qualified"> {/* → "…in Qualified" */}
|
|
88
|
-
<Kanban.Column id="qualified" aria-labelledby="qualified-h"> {/* same, from the heading */}
|
|
89
|
-
<SortableGroup.Container id="review" items={ids} aria-label="In review" />
|
|
90
|
-
|
|
91
|
-
{/* Override a chatty ITEM. Without this the whole card is read out. */}
|
|
92
|
-
<Kanban.Card id="d-1" aria-label="Acme renewal">…title, owner, due date, badges…</Kanban.Card>
|
|
93
|
-
<Sortable.Item id="f-1" aria-label="Company name">…</Sortable.Item>
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
An unnamed container falls back to its position ("column 2 of 3").
|
|
27
|
+
Every user-facing string the library renders flows through `I18nProvider` (`'en'` default, `'ru'`); pair it with `LocaleProvider` for `Intl` dates. There are NO `labels` / `cancelLabel` / `searchPlaceholder` props on any component. Overrides, adding a string, drag-and-drop announcements: see [docs/i18n.md](docs/i18n.md).
|
|
97
28
|
|
|
98
29
|
---
|
|
99
30
|
|
|
100
31
|
## An empty label prop means _unset_, never _blank_
|
|
101
32
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```tsx
|
|
105
|
-
// ✅ Fine. `row.title` missing → the component's own default copy.
|
|
106
|
-
<CursorPagination previousLabel={row.title ?? ''} … />
|
|
107
|
-
|
|
108
|
-
// ❌ Not a way to hide the label. It renders "Previous" all the same.
|
|
109
|
-
<CursorPagination previousLabel="" … />
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
**Two exceptions, both because the empty string already has a meaning of its own there.** An `<img>`'s `alt` — a RichText attachment block's `alt` field — is honoured when empty, because `alt=""` is HTML's marker for a decorative image. And a text input's `placeholder` (`DatePicker`, `DateRangePicker`) is honoured when empty, because wanting no placeholder is a real request and a placeholder is not an accessible name. Nothing else.
|
|
113
|
-
|
|
114
|
-
If you are writing a component _in_ this library, the rule behind that is an operator choice, and it is not optional:
|
|
115
|
-
|
|
116
|
-
- **Use `||`, never `??`, for any fallback that produces an accessible name** — whether the name comes from an attribute (`aria-label={x || t('key')}`) or from content (`<span>{label || t('key')}</span>`). `??` falls back only on `null`/`undefined`, so `''` reaches the DOM. Per the accname spec an empty `aria-label` contributes no name, so the computation does not stop there — it continues to name-from-content and then to `title`, and where the siblings are `aria-hidden` (a chevron, a spinner) the control ends up anonymous.
|
|
117
|
-
- The same applies to a fallback that exists so there is _always_ something readable — `col.visibilityLabel || header || col.id`, `v.label || v.code`. An empty string defeats the safeguard and renders a blank, unidentifiable row.
|
|
118
|
-
|
|
119
|
-
Two tests in `structure.test.ts` gate parts of this: one rejects `??` in `aria-label` / `aria-valuetext`, the other rejects `x ?? t(…)` anywhere in the source. **Neither implements the rule.** They cannot see whether an element is a control, whether its siblings are hidden, or that `v.label ?? v.code` and `node.label ?? id` are the same defect with a non-`t()` fallback — every one of those had to be found by reading. A green run is not coverage; the rule above is, and it is enforced by review.
|
|
33
|
+
`''` in a label prop (`aria-label`, `previousLabel`, `confirmLabel`, …) is treated exactly like omitting it, so it cannot blank out a name; only an `<img>` `alt` and a text input's `placeholder` honour `''`. Library authors: use `||`, never `??`, for an accessible-name fallback. See [docs/accessible-names.md](docs/accessible-names.md).
|
|
120
34
|
|
|
121
35
|
---
|
|
122
36
|
|
|
123
|
-
##
|
|
37
|
+
## More docs
|
|
124
38
|
|
|
125
|
-
|
|
39
|
+
- Dark theme: [docs/theming.md](docs/theming.md#dark-theme)
|
|
40
|
+
- Theming via component tokens: [docs/theming.md](docs/theming.md#theming-via-component-tokens)
|
|
41
|
+
- Transient state and screen readers: [docs/transient-state.md](docs/transient-state.md)
|
|
42
|
+
- TypeScript (no types in your editor?): [docs/typescript.md](docs/typescript.md)
|
|
126
43
|
|
|
127
44
|
---
|
|
128
45
|
|
|
129
46
|
## Components — TL;DR
|
|
130
47
|
|
|
131
|
-
Each
|
|
132
|
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
-
|
|
137
|
-
-
|
|
138
|
-
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
-
|
|
145
|
-
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
-
|
|
149
|
-
-
|
|
150
|
-
-
|
|
151
|
-
-
|
|
152
|
-
-
|
|
153
|
-
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
-
|
|
157
|
-
-
|
|
158
|
-
-
|
|
159
|
-
-
|
|
160
|
-
-
|
|
161
|
-
-
|
|
162
|
-
-
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
166
|
-
-
|
|
167
|
-
-
|
|
168
|
-
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
-
|
|
172
|
-
-
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
176
|
-
-
|
|
177
|
-
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
-
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
-
|
|
189
|
-
-
|
|
190
|
-
-
|
|
191
|
-
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
-
|
|
199
|
-
-
|
|
200
|
-
-
|
|
201
|
-
-
|
|
202
|
-
-
|
|
203
|
-
-
|
|
204
|
-
-
|
|
205
|
-
-
|
|
206
|
-
-
|
|
207
|
-
-
|
|
208
|
-
-
|
|
209
|
-
-
|
|
210
|
-
-
|
|
211
|
-
-
|
|
212
|
-
-
|
|
213
|
-
-
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
-
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
-
|
|
229
|
-
-
|
|
230
|
-
-
|
|
231
|
-
-
|
|
232
|
-
-
|
|
233
|
-
-
|
|
234
|
-
-
|
|
235
|
-
-
|
|
236
|
-
-
|
|
237
|
-
-
|
|
238
|
-
-
|
|
239
|
-
-
|
|
240
|
-
-
|
|
241
|
-
-
|
|
242
|
-
-
|
|
243
|
-
-
|
|
244
|
-
-
|
|
245
|
-
-
|
|
246
|
-
-
|
|
247
|
-
-
|
|
248
|
-
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
-
|
|
253
|
-
-
|
|
254
|
-
-
|
|
255
|
-
-
|
|
256
|
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
-
|
|
262
|
-
-
|
|
263
|
-
- [`DataTable`](docs/components/DataTable.md) — server-driven data table with column features
|
|
264
|
-
- [`Pagination`](docs/components/Pagination.md) — numbered nav with windowing
|
|
265
|
-
- [`CursorPagination`](docs/components/CursorPagination.md) — prev / next for streams without total
|
|
266
|
-
- [`LocaleProvider`](docs/components/LocaleProvider.md) — locale Context
|
|
267
|
-
- [`Calendar`](docs/components/Calendar.md) — month / week / day / agenda views
|
|
268
|
-
- [`DatePicker`](docs/components/DatePicker.md) — single-date input + popover
|
|
269
|
-
- [`DateRangePicker`](docs/components/DateRangePicker.md) — date-range input + two-month popover
|
|
270
|
-
- [`RelativeDateInput`](docs/components/RelativeDateInput.md) — editor for a relative date / date-range expression string (`today+7d`, `last:30d`)
|
|
271
|
-
- [`InlineDatePicker`](docs/components/InlineDatePicker.md) — single-date calendar in flow
|
|
272
|
-
- [`InlineDateRangePicker`](docs/components/InlineDateRangePicker.md) — date-range calendar in flow
|
|
273
|
-
- [`DateStrip`](docs/components/DateStrip.md) — week of selectable day tiles
|
|
274
|
-
- [`SlotGrid`](docs/components/SlotGrid.md) — grouped time-slot tiles
|
|
275
|
-
- [`TimeField`](docs/components/TimeField.md) — standalone time-of-day input
|
|
276
|
-
- [`useMonth`](docs/components/useMonth.md) — `useMonth`, `useWeek`, `useDay`, `useAgenda`
|
|
277
|
-
- [`useBelowBreakpoint`](docs/components/useBelowBreakpoint.md) — viewport breakpoint hook
|
|
278
|
-
- [`VisuallyHidden`](docs/components/VisuallyHidden.md) — content for assistive tech only
|
|
279
|
-
- [`LiveRegion`](docs/components/LiveRegion.md) — announcement region for consumer-level outcomes
|
|
48
|
+
Each `<Name>` below is documented in `docs/components/<Name>.md`.
|
|
49
|
+
|
|
50
|
+
- `Title` — semantic heading (typography hard rule: `Text` doc)
|
|
51
|
+
- `Text` — body / inline text; typography hard rule in its doc
|
|
52
|
+
- `Code` — inline `<code>` chip
|
|
53
|
+
- `CodeBlock` — multi-line, highlighted, copyable code
|
|
54
|
+
- `HighlightedText` — `Text` with matched runs in `<mark>` (search excerpts)
|
|
55
|
+
- `Kbd` — keyboard-shortcut chips
|
|
56
|
+
- `RichText` — read-only rich-text renderer
|
|
57
|
+
- `Button` — action triggers
|
|
58
|
+
- `SocialButton` — provider sign-in button
|
|
59
|
+
- `ButtonGroup` — joined Buttons + segmented control
|
|
60
|
+
- `Link` — polymorphic styled anchor
|
|
61
|
+
- `LinkCard` — clickable, full-surface Card
|
|
62
|
+
- `Input` — single-line text, optional free-text `suggestions`
|
|
63
|
+
- `Textarea` — multi-line text
|
|
64
|
+
- `PasswordInput` — password field with eye toggle + optional warnings
|
|
65
|
+
- `PasswordStrengthMeter` — 4-segment strength visualization
|
|
66
|
+
- `CurrencySelect` — ISO 4217 currency picker over Select
|
|
67
|
+
- `MoneyInput` — currency picker + formatted decimal-string amount (`MoneyValue`)
|
|
68
|
+
- `PhoneInput` — E.164 phone field with country picker
|
|
69
|
+
- `OtpInput` — one-time-code field
|
|
70
|
+
- `Checkbox` — checkbox with native input + custom paint
|
|
71
|
+
- `ColorPicker` — controlled HEX color picker (popover + inline)
|
|
72
|
+
- `IconPicker` — choose one glyph (optionally + palette colour) from a consumer catalog
|
|
73
|
+
- `Switch` — binary toggle
|
|
74
|
+
- `Radio` — single radio button
|
|
75
|
+
- `RadioGroup` — fieldset wrapper for related radios
|
|
76
|
+
- `Field` — labeled-control unit
|
|
77
|
+
- `SettingRow` — one row of a settings screen
|
|
78
|
+
- `FormSection` — titled group of fields
|
|
79
|
+
- `FormRow` — fields side by side
|
|
80
|
+
- `FileUpload` — controlled file picker with dropzone
|
|
81
|
+
- `Slider` — controlled slider (single + range, horizontal + vertical)
|
|
82
|
+
- `Sortable` — drag-to-reorder list (single column)
|
|
83
|
+
- `SortableGroup` — multi-container sortable (drag between lists)
|
|
84
|
+
- `Kanban` — multi-column board (drag-to-reorder + cross-column drag with live reflow)
|
|
85
|
+
- `FlowCanvas` — pan/zoom canvas for directed node-edge diagrams
|
|
86
|
+
- `DashboardCanvas` — 2D snap-grid dashboard
|
|
87
|
+
- `DashboardWidget` — DashboardCanvas cell card: standard / list / kpi / chart / stats (StatGroup strip), with loading skeletons
|
|
88
|
+
- `StatGroup` — responsive grid of KPI tiles for a dashboard widget, with loading skeletons
|
|
89
|
+
- `StatTile` — one KPI tile (label, value, trend, footnote, icon, sparkline, quota `progress`) inside a StatGroup (`filled` or `card` surface)
|
|
90
|
+
- `Sparkline` — compact static line / area / bar trend for KPI cards (StatTile slot, kpi DashboardWidget child); no axes, summary in the accessible name
|
|
91
|
+
- `Chart` — line / bar / stacked bar / area time-series chart for a chart DashboardWidget (smoothed lines, static markers, legend, tooltip, keyboard, data table, empty state)
|
|
92
|
+
- `TreeView` — lazily loaded hierarchy (WAI-ARIA tree) as a file-explorer list or a top-down org chart, with loading / error-retry / load-more rows
|
|
93
|
+
- `LiquidEditor` — Liquid template editor
|
|
94
|
+
- `RichTextEditor` — controlled rich-text editor (contentEditable)
|
|
95
|
+
- `ImageCrop` — controlled image cropper
|
|
96
|
+
- `Card` — bordered container
|
|
97
|
+
- `DefinitionList` — semantic key/value pairs (dl / dt / dd)
|
|
98
|
+
- `List` — bulleted / numbered prose lists (ul / ol), optional badge step numbers
|
|
99
|
+
- `Stack` — vertical layout
|
|
100
|
+
- `Page` — page-root layout primitive
|
|
101
|
+
- `Cluster` — horizontal layout that wraps
|
|
102
|
+
- `Constrain` — size / flex constraint
|
|
103
|
+
- `Indent` — indent nested content by depth
|
|
104
|
+
- `Screen` — full-bleed / centered screen layout
|
|
105
|
+
- `AppLayout` — viewport-filling app shell
|
|
106
|
+
- `Grid` — 2D layout primitive
|
|
107
|
+
- `Split` — master–detail two-pane layout
|
|
108
|
+
- `Sticky` — sticky-positioning primitive
|
|
109
|
+
- `ScrollArea` — height-capped vertical scroll region
|
|
110
|
+
- `Masonry` — height-balanced masonry layout
|
|
111
|
+
- `Divider` — separator primitive
|
|
112
|
+
- `PageHeader` — top-of-page heading area
|
|
113
|
+
- `Avatar` — profile circle
|
|
114
|
+
- `AgentAvatar` — AI agent tile with the acting member badge (`decorative` beside a byline that names both)
|
|
115
|
+
- `AvatarGroup` — Slack-style stacked row of avatars
|
|
116
|
+
- `PersonDisplay` — Avatar + name (+ optional description lines; `AgentAvatar` + `OnBehalfOf` for an agent acting for a member)
|
|
117
|
+
- `Badge` — status / category pill
|
|
118
|
+
- `EntityChip` — inline entity-link chip
|
|
119
|
+
- `PillMenu` — coloured value menu (status, type, priority…)
|
|
120
|
+
- `Dot` — bare palette/tone colored circle
|
|
121
|
+
- `Timeline` — vertical activity-feed primitive
|
|
122
|
+
- `StagePath` — chevron row of record stages (done / current / upcoming)
|
|
123
|
+
- `Thread` — nested-reply threading primitive
|
|
124
|
+
- `BrandIcon` — third-party brand marks
|
|
125
|
+
- `Logo` — brand logo lockup
|
|
126
|
+
- `Palette` — categorical color set
|
|
127
|
+
- `FilterChip` — dismissible "active filter" pill
|
|
128
|
+
- `Tabs` — tab strip (horizontal or vertical)
|
|
129
|
+
- `Accordion` — vertically-stacked collapsible panels
|
|
130
|
+
- `Breadcrumb` — navigation trail
|
|
131
|
+
- `Rail` — collapsible left-side navigation
|
|
132
|
+
- `TableOfContents` — "On this page" h2/h3 navigation with scroll-spy
|
|
133
|
+
- `TopBar` — sticky application top bar; `TopBar.Search` with `results` is the global-search combobox
|
|
134
|
+
- `DropdownMenu` — action menus from a trigger
|
|
135
|
+
- `Tooltip` — small floating label on hover / keyboard focus
|
|
136
|
+
- `Popover` — non-modal floating panel for interactive content
|
|
137
|
+
- `ToastViewport` — transient notifications
|
|
138
|
+
- `Alert` — persistent in-flow notification
|
|
139
|
+
- `Banner` — full-width system / app message bar
|
|
140
|
+
- `ChatBubble` — chat message bubble by speaker (`ChatBubble.List` = `role="log"` announcing container; `pending` typing state)
|
|
141
|
+
- `ChatLauncher` — fixed bottom-end launcher toggling a non-modal chat panel (header/scrolling body/pinned `footer` composer; stays mounted, sticks to bottom)
|
|
142
|
+
- `Highlight` — temporary attention ring on any block (+ optional scroll/focus)
|
|
143
|
+
- `ConfirmationPopover` — opinionated "Are you sure?" preset
|
|
144
|
+
- `Tour` — guided tour / onboarding walkthrough
|
|
145
|
+
- `Modal` — focus-locked dialog
|
|
146
|
+
- `Lightbox` — full-screen image & document gallery overlay
|
|
147
|
+
- `Drawer` — edge-anchored slide-in panel
|
|
148
|
+
- `Select` — value picker (single, multi, searchable, async, creatable)
|
|
149
|
+
- `OptionsPicker` — filter picker (multi/single, grouped, searchable; async server search via `loadOptions`)
|
|
150
|
+
- `EmojiPicker` — searchable emoji grid (reactions + input)
|
|
151
|
+
- `CatalogPicker` — searchable, category-filtered card picker for choosing one catalog item (Add widget)
|
|
152
|
+
- `EmptyState` — "nothing here" container
|
|
153
|
+
- `ErrorState` — page-level status / result screen
|
|
154
|
+
- `IconTile` — palette-colored icon frame
|
|
155
|
+
- `Progress` — linear progress bar; progress hard rule in its doc
|
|
156
|
+
- `CircularProgress` — circular progress / spinner (progress hard rule: `Progress` doc)
|
|
157
|
+
- `Skeleton` — loading placeholder
|
|
158
|
+
- `Image` — image with loading + error states
|
|
159
|
+
- `QrCode` — scannable QR code
|
|
160
|
+
- `MediaTile` — media tile with caption, revealed actions and selection
|
|
161
|
+
- `WidgetPreview` — decorative data-free miniature of a widget kind (for CatalogPicker)
|
|
162
|
+
- `Table` — tabular data primitive; also count grids (`layout="fixed"`, toned columns, whole-cell `Table.CellLink`)
|
|
163
|
+
- `DataTable` — server-driven data table with column features
|
|
164
|
+
- `Pagination` — numbered nav with windowing
|
|
165
|
+
- `CursorPagination` — prev / next for streams without total
|
|
166
|
+
- `LocaleProvider` — locale Context
|
|
167
|
+
- `Calendar` — month / week / day / agenda views
|
|
168
|
+
- `DatePicker` — single-date input + popover
|
|
169
|
+
- `DateRangePicker` — date-range input + two-month popover
|
|
170
|
+
- `RelativeDateInput` — editor for a relative date / date-range expression string (`today+7d`, `last:30d`)
|
|
171
|
+
- `InlineDatePicker` — single-date calendar in flow
|
|
172
|
+
- `InlineDateRangePicker` — date-range calendar in flow
|
|
173
|
+
- `DateStrip` — week of selectable day tiles
|
|
174
|
+
- `SlotGrid` — grouped time-slot tiles
|
|
175
|
+
- `TimeField` — standalone time-of-day input
|
|
176
|
+
- `useMonth` — `useMonth`, `useWeek`, `useDay`, `useAgenda`
|
|
177
|
+
- `useBelowBreakpoint` — viewport breakpoint hook
|
|
178
|
+
- `VisuallyHidden` — content for assistive tech only
|
|
179
|
+
- `LiveRegion` — announcement region for consumer-level outcomes
|
|
280
180
|
|
|
281
181
|
---
|
|
282
182
|
|
|
@@ -284,56 +184,34 @@ Each component's contract is its `docs/components/<Name>.md`; the props table th
|
|
|
284
184
|
|
|
285
185
|
Every visual value comes from a token (`var(--color-*)`, `var(--space-*)`, `var(--radius-*)`, …); never raw hex, px, rem or em. The full token reference is in [`docs/tokens.md`](docs/tokens.md) — read it before writing SCSS.
|
|
286
186
|
|
|
287
|
-
## Theming via component tokens
|
|
288
|
-
|
|
289
|
-
See [docs/theming.md](docs/theming.md#theming-via-component-tokens).
|
|
290
|
-
|
|
291
|
-
---
|
|
292
|
-
|
|
293
|
-
## Transient state and screen readers
|
|
294
|
-
|
|
295
|
-
See [docs/transient-state.md](docs/transient-state.md).
|
|
296
|
-
|
|
297
187
|
## Anti-patterns to never generate
|
|
298
188
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
---
|
|
327
|
-
|
|
328
|
-
## TypeScript
|
|
329
|
-
|
|
330
|
-
Every prop and variant type is typed. If your editor shows no types:
|
|
331
|
-
|
|
332
|
-
- Confirm `moduleResolution: "bundler"` (or `"node16"`) in your `tsconfig.json`.
|
|
333
|
-
- Confirm your IDE's TS server is running.
|
|
334
|
-
- The package's `types` field points at `./src/index.ts` — source distribution means the consumer's bundler compiles `.tsx`. Vite, Next.js, Webpack 5+ all support this; older configurations may need `transpilePackages: ['@eocrm/design-system']` (Next.js) or equivalent.
|
|
335
|
-
|
|
336
|
-
---
|
|
189
|
+
Each line: don't write → write instead.
|
|
190
|
+
|
|
191
|
+
- `color: #ffffff` in any SCSS → `color: var(--color-bg)` (or the right semantic token)
|
|
192
|
+
- `border: 1px solid var(--color-border)` → `border: var(--border-width) solid var(--color-border)`
|
|
193
|
+
- `opacity: 0.5` → `opacity: var(--opacity-disabled)` (or use the `disabled` attribute)
|
|
194
|
+
- `<button onClick={...}>Save</button>` → `<Button onClick={...}>Save</Button>`
|
|
195
|
+
- `<input value={...} onChange={...} />` → `<Input value={...} onChange={...} />`
|
|
196
|
+
- `<Card><Card>...</Card></Card>` → Use spacing or a divider inside one card
|
|
197
|
+
- `<Button style={{ marginLeft: 'auto' }}>` → `<Cluster justify="between">` or `<Cluster justify="end">`
|
|
198
|
+
- Two `<Button variant="primary">` in the same section → One primary, others `secondary`
|
|
199
|
+
- `<Button variant="success">Save</Button>` rendered on initial mount → `success` is transient — start as `primary`, flip to `success` for ~1.5s after the action resolves, flip back
|
|
200
|
+
- `<Avatar name="" />` → `name` is required and is the accessible label
|
|
201
|
+
- `import { Button } from '@eocrm/design-system/src/components/Button'` → `import { Button } from '@eocrm/design-system'`
|
|
202
|
+
- `<Badge onClick={...}>` → Badges are non-interactive — use a `Button`
|
|
203
|
+
- 3-digit hex (`#fff`) anywhere → Always 6-digit (`#ffffff`)
|
|
204
|
+
- `margin` on or around design-system components in your SCSS → Wrap in `<Stack>` / `<Cluster>` or set spacing on the parent's flex/grid
|
|
205
|
+
- `<DropdownMenu.Item disabled>--- Section ---</DropdownMenu.Item>` as a section header → Use `<DropdownMenu.Separator />` between groups
|
|
206
|
+
- `<DropdownMenu.RadioItem>` outside `<DropdownMenu.RadioGroup>` → Always wrap radio items in a RadioGroup; otherwise the value/onValueChange contract is broken
|
|
207
|
+
- `<DropdownMenu.ItemIndicator>` nested deeper than direct child → Detection is shallow; nest it directly under CheckboxItem/RadioItem
|
|
208
|
+
- Submenus 3+ levels deep → Discouraged — UX gets confusing fast; refactor to a different IA
|
|
209
|
+
- `<Tooltip><Button disabled>…</Button></Tooltip>` → `disabled` buttons don't fire pointer/focus events; render with `aria-disabled="true"` + intercept the click, or wrap in `<span>`.
|
|
210
|
+
- Putting essential info _only_ in a tooltip → Make it visible in copy or in the trigger's `aria-label`. Touch users will never see the tooltip.
|
|
211
|
+
- `<Tooltip content="">…</Tooltip>` expecting a no-op listener attach → Empty content **is** a no-op — listeners and aria are skipped entirely. That's by design.
|
|
212
|
+
- `<Popover.Trigger><Button disabled>…</Button></Popover.Trigger>` → `disabled` buttons don't fire click; render with `aria-disabled="true"` + intercept the click, or wrap in `<span>`.
|
|
213
|
+
- `<Popover.Content>` with no `<Popover.Close>` AND non-obvious outside-click dismissal → Keyboard / screen-reader users get no clear close affordance. Add a `<Popover.Close>` close button.
|
|
214
|
+
- `<ConfirmationPopover>` with `onConfirm` that never settles → v1 has no timeout — popover stays in pending state forever. Add a timeout/abort inside your `onConfirm` if relevant.
|
|
337
215
|
|
|
338
216
|
## Full reference
|
|
339
217
|
|
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ Its web tokens are generated and versioned by `@eocrm/design-tokens`. Existing
|
|
|
6
6
|
consumer imports remain unchanged: the design-system Sass entry points forward
|
|
7
7
|
to the token package.
|
|
8
8
|
|
|
9
|
-
Component contracts live in **[`docs/components/<Name>.md`](./docs/components)** (props table, anti-patterns). AI agents consuming this package should read **[AI-PRIMER.md](./AI-PRIMER.md)** first.
|
|
9
|
+
Component contracts live in **[`docs/components/<Name>.md`](./docs/components)** (props table, anti-patterns). AI agents consuming this package should read **[AI-PRIMER.md](./AI-PRIMER.md)** first, ideally through the MCP server (see [AI agents](#ai-agents)).
|
|
10
10
|
|
|
11
11
|
**Live playground** (every component, demoed against `main`): <https://eocrm.github.io/design-system/components>
|
|
12
12
|
|
|
@@ -63,17 +63,30 @@ If you need only the tokens (you're providing your own reset), import `@eocrm/de
|
|
|
63
63
|
|
|
64
64
|
## Accent
|
|
65
65
|
|
|
66
|
-
The accent family defaults to blue. To switch the whole app to
|
|
66
|
+
The accent family defaults to blue. To switch the whole app to another accent, set `data-accent` on `<html>` (omit the attribute for blue):
|
|
67
67
|
|
|
68
68
|
```html
|
|
69
69
|
<html data-accent="tealpine" data-theme="dark"></html>
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
|
|
72
|
+
| `data-accent` | Hue | Compose objects |
|
|
73
|
+
| ------------- | ---------------- | ------------------------------------------------------ |
|
|
74
|
+
| _(none)_ | Blue (default) | `EocrmLightTokens` / `EocrmDarkTokens` |
|
|
75
|
+
| `tealpine` | Teal pine | `EocrmTealPineLightTokens` / `EocrmTealPineDarkTokens` |
|
|
76
|
+
| `indigo` | Indigo | `EocrmIndigoLightTokens` / `EocrmIndigoDarkTokens` |
|
|
77
|
+
| `grape` | Purple | `EocrmGrapeLightTokens` / `EocrmGrapeDarkTokens` |
|
|
78
|
+
| `berry` | Magenta-pink | `EocrmBerryLightTokens` / `EocrmBerryDarkTokens` |
|
|
79
|
+
| `slate` | Neutral graphite | `EocrmSlateLightTokens` / `EocrmSlateDarkTokens` |
|
|
80
|
+
| `ocean` | Cyan-blue | `EocrmOceanLightTokens` / `EocrmOceanDarkTokens` |
|
|
81
|
+
| `moss` | Olive green | `EocrmMossLightTokens` / `EocrmMossDarkTokens` |
|
|
73
82
|
|
|
74
|
-
|
|
83
|
+
Each accent swaps `--color-accent`, `-hover`, `-pressed`, `-fg`, `-bg-subtle`, `-subtle-bg` and `--ring-accent`, plus the default `Link` colour (`--color-link-fg` / `-hover`), in light, forced dark (`data-theme="dark"`) and OS dark. They ship in the existing `tokens.scss` / `dark.scss`, so there is no new import. `AppProvider`'s `tokens` / `darkTokens` overrides win over `data-accent` in every theme (see [docs/setup.md](docs/setup.md)). Info colours stay blue under any accent; DropdownMenu checked items, the OptionsPicker selected row and StagePath done states follow the accent. In light theme, Teal pine's default `Link`s use a slightly brighter teal than the accent (`#007676`, AA on every grey surface) at medium weight (not inside headings, `<strong>` or `<th>`), because the accent alone sits too close to the navy body text to read as a link. Slate's default `Link`s are medium weight for the same reason. Indigo and Berry also set a slightly different link colour from their accent (`#4342c4`, `#a82c68`) to keep links at AA on every grey surface.
|
|
75
84
|
|
|
76
|
-
|
|
85
|
+
Compose: each accent's Light / Dark objects sit beside `EocrmLightTokens` / `EocrmDarkTokens`; only accent colours differ. The link colour tokens are web-only (Compose has no `Link`), so Compose links keep the accent colour.
|
|
86
|
+
|
|
87
|
+
There is deliberately no red, orange or yellow accent: a primary button in those hues reads as a danger or warning action.
|
|
88
|
+
|
|
89
|
+
**Known issue:** in light theme, Teal pine and Moss sit close to `--color-success` in hue. Review success/accent pairings visually before switching the CRM to either.
|
|
77
90
|
|
|
78
91
|
---
|
|
79
92
|
|
|
@@ -253,6 +266,16 @@ The playground (live component gallery) deploys to GitHub Pages automatically as
|
|
|
253
266
|
|
|
254
267
|
---
|
|
255
268
|
|
|
269
|
+
## AI agents
|
|
270
|
+
|
|
271
|
+
[`@eocrm/design-system-mcp`](../design-system-mcp/README.md) is an MCP server that serves the docs of the `@eocrm/design-system` version your project has installed. One-time, user-scope install for Claude Code:
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
claude mcp add --scope user eocrm-design-system -- npx -y @eocrm/design-system-mcp
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
Codex, Cursor, VS Code and generic setups, plus `npx -y @eocrm/design-system-mcp install-skill`: see its [README](../design-system-mcp/README.md).
|
|
278
|
+
|
|
256
279
|
## Where docs live (so you don't have to grep)
|
|
257
280
|
|
|
258
281
|
- **README.md** (this file) — install, setup, bundler notes, publishing/deploy ops.
|