You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 28abfc7
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: .claude/rules/emcn-components.md
+3-3Lines changed: 3 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -17,7 +17,7 @@ Never hand-roll the chip pill from raw class strings (they go stale). Compose fr
17
17
18
18
Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border` border (`chip-chrome.ts` still spells it through the legacy alias `--border-1`; new code writes `--border`).
19
19
20
-
The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items use `text-small` and `gap-2` (a menu convention, not the chip pill). Keep them distinct.
20
+
`MENU_STYLES` in `dropdown-menu/styles.ts` owns the shared dropdown, picker-list, and popover chrome. Its default menu rows are 28px at `text-small`; chip pills remain 30px at `text-sm`. Menus reuse the chip gap, row radius, and search-field surface and typography, with a 12px outer radius, 4px padding, and `shadow-medium`. Compose these tokens instead of overriding chrome at call sites. Within the package, shared initialization-time tokens use private imports to avoid public-barrel cycles; consumers import `MENU_STYLES` from `@sim/emcn`.
21
21
22
22
## Component catalogue
23
23
@@ -26,13 +26,13 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
26
26
-**`ChipCopyInput`** — the canonical view-only field: a read-only `ChipInput` at full opacity with a trailing copy-to-clipboard button. View-only is a display mode, not a disabled state — reach for it (or `ChipModalField type='copy'`) over a `disabled` (greyed) input for values the user cannot edit.
27
27
-**`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default), `viewOnly` (read-only at full opacity with the default cursor — the multi-line counterpart of `ChipCopyInput`).
28
28
-**`ChipDropdown`** — pill that opens a menu. Single OR multi-select via the discriminated `multiple` prop (one component, not two). Owns its trailing chevron — no `rightIcon`.
29
-
-**`ChipSelect` / `ChipCombobox`** — `Combobox`-backed pickers with search, groups, multi-select; for richer lists than `ChipDropdown`.
29
+
-**`ChipSelect` / `ChipCombobox`** — pickers with search, groups, and multi-select. `ChipSelect` uses `DropdownMenu`; `ChipCombobox` uses `Combobox`for editable input, async options, and virtualized lists. Both inherit the shared menu chrome.
30
30
-**`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `copy` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` (with a `title`) is the escape hatch for controls the field doesn't cover. Every body field is a `ChipModalField`; the gutter rhythm that makes this matter is in `sim-styling.md` → "Form / chip-modal layout rhythm".
31
31
-**`ChipSwitch`** — segmented pill control (built from `chipVariants`).
32
32
-**`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
33
33
-**`ChipDatePicker`** — chip-styled date field.
34
34
-**`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
35
-
-**`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
35
+
-**`DropdownMenu`** — the canonical context/action menu (Radix-backed), styled through `MENU_STYLES`. Reach for it instead of a hand-rolled popover for command/action lists. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
36
36
-**`useScrollEdges` + `scrollFadeClass` / `scrollFadeAttributes`** — the canonical scroll-region edge treatment. The hook reports which edges hide content (tracking scroll and resizes; pass the element itself, held in state, when the region mounts after its owner, e.g. inside a Radix portal); the class and attributes fade a fixed 12px band at an active edge only, so a list that fits or sits at its top is never fogged. A floating control over the top edge sets `--scroll-fade-inset` to its height. A region that scrolls sideways (a tab row, a chip strip) uses `useScrollEdges(ref, { axis: 'x' })` with `scrollFadeXClass`; the attributes helper is shared. Any divider beside the region belongs to the neighboring block (`border-b` above, `border-t` below), never to the masked element, and shows only while that edge is active. Never hand-roll a `mask-image` gradient for a scroll region.
37
37
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, fade-only clipping (never an ellipsis), the conditional 18px edge mask, and the full-value floating tooltip; consumers pass only layout/typography through `className`. `overflowTextClipClass` and `overflowTextFadeClass` are the complete base/faded treatments for the rare component that must own measurement itself; never pair either with `truncate`, `text-ellipsis`, or hover-time mask removal. Use `DropdownMenuItemLabel` for a menu label beside icons, checks, or actions. A non-editable `Combobox` passes the full visual value through `overlayLabel`; the combobox owns the visual overlay's fade and keeps its one accessible tooltip on the interactive layer. Keep ordinary `truncate` only for editable values, code/log/path content, dense or virtualized grids, and rich composite content that cannot supply a plain tooltip label. Multiline copy uses an intentional `line-clamp-*` treatment instead.
@@ -228,11 +229,14 @@ and — on activatable rows only — the hover band. Never hand-roll any of it,
228
229
renders the value paragraph itself; pass a node when the value needs its own
229
230
presentation (a control, an icon beside the value, status styling).
230
231
-**`SettingsEmptyState`** (`…/components/settings-empty-state`) — the canonical
231
-
muted status message, for empty lists, "no results", loading gates, **and
232
-
failed loads** (`tone='error'`). `variant='fill'` (default) centers in the
233
-
available height; `variant='inline'` sits in flow. Never hand-roll
234
-
`<div className='flex h-full items-center justify-center …'>` or
235
-
`<div className='py-4 text-center …'>`.
232
+
muted status message for empty lists, "no results", loading gates, and failed
233
+
reads. `variant='fill'` (default) centers in the available height;
234
+
`variant='inline'` sits in flow. Never hand-roll its layout.
235
+
-**`SettingsQueryErrorState`** (same module) — the muted read-failure message
236
+
with an accessible alert and retry control. Show an initial failure when data
237
+
is unavailable; preserve cached content, mounted editors, and unsaved drafts
238
+
during background failures, using an inline status. Validation and mutation
239
+
failures retain their error styling.
236
240
-**`RowActionsMenu`** (`…/components/row-actions-menu`) — the trailing `...`
237
241
actions menu for a list row. Pass `label` (aria-label) and
238
242
`actions: RowAction[]` (`{ label, onSelect, destructive?, disabled? }`); the
@@ -395,6 +399,6 @@ A settings page is design-system-clean when:
395
399
-[ ] Every **resource** list row (a thing with an identity — a tool, a server, a key, a credential) is a `SettingsResourceRow` in a `RESOURCE_LIST_STACK`/`RESOURCE_LIST_GRID` — no wrapper `<button>`/`<Link>`, no hand-passed arrow, no re-derived title/subtitle spans. Rows with a genuinely different shape stay bespoke, and draw their own arrow with `RESOURCE_ROW_ARROW_CLASSES`: multi-line bodies (inbox tasks), tabular columns (billing invoices, credit usage), grids (secrets), and the member rows (see the avatar note above).
396
400
-[ ] Rows that open a detail page use `navigable` + `clickLabel`; flat records use `RowActionsMenu`. Not both.
397
401
-[ ] Decorative trailing content is in `badge`, not `trailing`.
398
-
-[ ] Labeled sections use `SettingsSection`; read-only fields use `SettingsField`; empty/loading/error use `SettingsEmptyState`.
402
+
-[ ] Labeled sections use `SettingsSection`; read-only fields use `SettingsField`; empty/loading/read-failure messages use `SettingsEmptyState`, with `SettingsQueryErrorState` when offering retry.
399
403
-[ ] Delete is a plain `id:'delete'` header action behind a `ChipConfirmModal`; `destructive` is reserved for bulk actions.
400
404
-[ ] The local gate in the root `CLAUDE.md` ("How your work is checked") passes.
Copy file name to clipboardExpand all lines: .cursor/rules/emcn-components.mdc
+3-3Lines changed: 3 additions & 3 deletions
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ Never hand-roll the chip pill from raw class strings (they go stale). Compose fr
18
18
19
19
Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value text `--text-body`, icons `--text-icon` at `size-[14px]`, placeholder `--text-muted`, `transition-colors`, **no focus ring** (the caret marks focus). Filled surface is `--surface-5` light / `--surface-4` dark with a `--border` border (`chip-chrome.ts` still spells it through the legacy alias `--border-1`; new code writes `--border`).
20
20
21
-
The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items use `text-small` and `gap-2` (a menu convention, not the chip pill). Keep them distinct.
21
+
`MENU_STYLES` in `dropdown-menu/styles.ts` owns the shared dropdown, picker-list, and popover chrome. Its default menu rows are 28px at `text-small`; chip pills remain 30px at `text-sm`. Menus reuse the chip gap, row radius, and search-field surface and typography, with a 12px outer radius, 4px padding, and `shadow-medium`. Compose these tokens instead of overriding chrome at call sites. Within the package, shared initialization-time tokens use private imports to avoid public-barrel cycles; consumers import `MENU_STYLES` from `@sim/emcn`.
22
22
23
23
## Component catalogue
24
24
@@ -27,13 +27,13 @@ The menu surface intentionally diverges from the pill: `dropdown-menu.tsx` items
27
27
- **`ChipCopyInput`** — the canonical view-only field: a read-only `ChipInput` at full opacity with a trailing copy-to-clipboard button. View-only is a display mode, not a disabled state — reach for it (or `ChipModalField type='copy'`) over a `disabled` (greyed) input for values the user cannot edit.
28
28
- **`ChipTextarea`** — multi-line sibling. `error`, `resizable` (off by default), `viewOnly` (read-only at full opacity with the default cursor — the multi-line counterpart of `ChipCopyInput`).
29
29
- **`ChipDropdown`** — pill that opens a menu. Single OR multi-select via the discriminated `multiple` prop (one component, not two). Owns its trailing chevron — no `rightIcon`.
30
-
- **`ChipSelect` / `ChipCombobox`** — `Combobox`-backed pickers with search, groups, multi-select; for richer lists than `ChipDropdown`.
30
+
- **`ChipSelect` / `ChipCombobox`** — pickers with search, groups, and multi-select. `ChipSelect` uses `DropdownMenu`; `ChipCombobox` uses `Combobox` for editable input, async options, and virtualized lists. Both inherit the shared menu chrome.
31
31
- **`ChipModal` + `ChipModalField`** — declarative compact modal. The field's `type` (`input` | `email` | `textarea` | `dropdown` | `copy` | `file` | `emails` | `custom`) picks the control and **owns all chrome** — consumers describe intent, never pass `variant`/`className`/`id` to the inner control. `custom` (with a `title`) is the escape hatch for controls the field doesn't cover. Every body field is a `ChipModalField`; the gutter rhythm that makes this matter is in `sim-styling.md` → "Form / chip-modal layout rhythm".
32
32
- **`ChipSwitch`** — segmented pill control (built from `chipVariants`).
33
33
- **`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
34
34
- **`ChipDatePicker`** — chip-styled date field.
35
35
- **`ChipTimePicker`** — minute-granular time sibling of `ChipDatePicker`, a `ChipInput` that leniently parses typed input (`9:47`, `947`, `2:05pm`, `14:30`), commits on Enter/blur, and re-renders the canonical `9:47 AM` label.
36
-
- **`DropdownMenu`** — the canonical context/action menu (Radix-backed). Not a chip, but the standard menu for command/action lists; reach for it instead of a hand-rolled popover. Its surface intentionally diverges from the chip pill (`text-small`, `gap-2`) — keep them distinct. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
36
+
- **`DropdownMenu`** — the canonical context/action menu (Radix-backed), styled through `MENU_STYLES`. Reach for it instead of a hand-rolled popover for command/action lists. For a pill that opens a value picker, use `ChipDropdown`/`ChipSelect` instead.
37
37
- **`useScrollEdges` + `scrollFadeClass` / `scrollFadeAttributes`** — the canonical scroll-region edge treatment. The hook reports which edges hide content (tracking scroll and resizes; pass the element itself, held in state, when the region mounts after its owner, e.g. inside a Radix portal); the class and attributes fade a fixed 12px band at an active edge only, so a list that fits or sits at its top is never fogged. A floating control over the top edge sets `--scroll-fade-inset` to its height. A region that scrolls sideways (a tab row, a chip strip) uses `useScrollEdges(ref, { axis: 'x' })` with `scrollFadeXClass`; the attributes helper is shared. Any divider beside the region belongs to the neighboring block (`border-b` above, `border-t` below), never to the masked element, and shows only while that edge is active. Never hand-roll a `mask-image` gradient for a scroll region.
38
38
- **`OverflowText`** — the canonical single-line overflow treatment for read-only human labels and titles. It owns `min-w-0`, fade-only clipping (never an ellipsis), the conditional 18px edge mask, and the full-value floating tooltip; consumers pass only layout/typography through `className`. `overflowTextClipClass` and `overflowTextFadeClass` are the complete base/faded treatments for the rare component that must own measurement itself; never pair either with `truncate`, `text-ellipsis`, or hover-time mask removal. Use `DropdownMenuItemLabel` for a menu label beside icons, checks, or actions. A non-editable `Combobox` passes the full visual value through `overlayLabel`; the combobox owns the visual overlay's fade and keeps its one accessible tooltip on the interactive layer. Keep ordinary `truncate` only for editable values, code/log/path content, dense or virtualized grids, and rich composite content that cannot supply a plain tooltip label. Multiline copy uses an intentional `line-clamp-*` treatment instead.
0 commit comments