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
Copy file name to clipboardExpand all lines: .claude/rules/emcn-components.md
+7-16Lines changed: 7 additions & 16 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -19,22 +19,13 @@ Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value
19
19
20
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.
21
21
22
-
## Component catalogue
23
-
24
-
-**`Chip` / `ChipLink`** — the pill button (`<button>` / Next `<Link>`). Variants: `primary`, `destructive`, `border-shadow`, `border`, `outline` (a true `--border` border, no shadow or hover fill); the bare chip is implicit (omit `variant`). `filled` is deliberately NOT a `Chip` variant — it is reserved for chip fields/triggers. For a selected/toggle chip use the `active` prop, never a variant. `leftIcon`/`rightIcon`, `active`, `fullWidth`. Chips carry **no outer margin** — space between them is the parent's `gap`. The old `mx-0.5` default and its `flush` opt-out are gone; do not reintroduce either, and never add a margin to a chip through `className`.
25
-
-**`ChipInput`** — single-line text field. `icon`, `endAdornment`, `error`, `inputClassName` (inner `<input>`); `className` styles the chrome wrapper.
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
-
-**`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
-
-**`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`.
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
-
-**`ChipSwitch`** — segmented pill control (built from `chipVariants`).
32
-
-**`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
33
-
-**`ChipDatePicker`** — chip-styled date field.
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.
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
-
- **`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.
22
+
## Public components and design behavior
23
+
24
+
The current public exports, variants, defaults, styling slots, and relationships are generated from source in `scripts/design-conformance/contracts.generated.json`. Run `bun run design:generate` when EMCN or `globals.css` changes. Browse and interact with the visual exports in the local Design Studio after `bun run studio:refresh`; do not maintain an export list here.
25
+
26
+
Use chip fields and triggers for product forms, and `DropdownMenu` for context and action lists. Prefer a supported prop to a `className` chrome override. A selected chip uses `active`; outer spacing belongs to its parent. A view-only value stays at full opacity and uses the read-only copy field or `ChipModalField type='copy'`, not a disabled input. `ChipModalField` owns its inner control chrome; use `type='custom'` with a title for a control it does not cover. A chip dropdown owns its chevron and supports single or multi-select through `multiple`; use chip select or combobox for searchable or grouped lists. Inline tags are labels, not pill triggers.
27
+
28
+
For scroll-region edge fading use `useScrollEdges` with `scrollFadeClass` or `scrollFadeXClass` and `scrollFadeAttributes`; do not hand-roll a mask. For a constrained, single-line, read-only human label use `OverflowText` or `DropdownMenuItemLabel` in a menu; keep its fade and full-value tooltip instead of adding an ellipsis. Keep editable values, code, logs, paths, dense grids, and composite content on their appropriate overflow treatments. Source and variants in EMCN remain the authority if these examples change.
Copy file name to clipboardExpand all lines: .cursor/rules/emcn-components.mdc
+7-16Lines changed: 7 additions & 16 deletions
Original file line number
Diff line number
Diff line change
@@ -20,22 +20,13 @@ Canonical look: normal font-weight (never `font-medium`/`font-semibold`), value
20
20
21
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.
22
22
23
-
## Component catalogue
24
-
25
-
- **`Chip` / `ChipLink`** — the pill button (`<button>` / Next `<Link>`). Variants: `primary`, `destructive`, `border-shadow`, `border`, `outline` (a true `--border` border, no shadow or hover fill); the bare chip is implicit (omit `variant`). `filled` is deliberately NOT a `Chip` variant — it is reserved for chip fields/triggers. For a selected/toggle chip use the `active` prop, never a variant. `leftIcon`/`rightIcon`, `active`, `fullWidth`. Chips carry **no outer margin** — space between them is the parent's `gap`. The old `mx-0.5` default and its `flush` opt-out are gone; do not reintroduce either, and never add a margin to a chip through `className`.
26
-
- **`ChipInput`** — single-line text field. `icon`, `endAdornment`, `error`, `inputClassName` (inner `<input>`); `className` styles the chrome wrapper.
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
-
- **`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
-
- **`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`.
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
-
- **`ChipSwitch`** — segmented pill control (built from `chipVariants`).
33
-
- **`ChipTag`** — 20px inline tag/badge (`mono`/`gray`/`invite`), not a pill trigger.
34
-
- **`ChipDatePicker`** — chip-styled date field.
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.
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
-
- **`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.
23
+
## Public components and design behavior
24
+
25
+
The current public exports, variants, defaults, styling slots, and relationships are generated from source in `scripts/design-conformance/contracts.generated.json`. Run `bun run design:generate` when EMCN or `globals.css` changes. Browse and interact with the visual exports in the local Design Studio after `bun run studio:refresh`; do not maintain an export list here.
26
+
27
+
Use chip fields and triggers for product forms, and `DropdownMenu` for context and action lists. Prefer a supported prop to a `className` chrome override. A selected chip uses `active`; outer spacing belongs to its parent. A view-only value stays at full opacity and uses the read-only copy field or `ChipModalField type='copy'`, not a disabled input. `ChipModalField` owns its inner control chrome; use `type='custom'` with a title for a control it does not cover. A chip dropdown owns its chevron and supports single or multi-select through `multiple`; use chip select or combobox for searchable or grouped lists. Inline tags are labels, not pill triggers.
28
+
29
+
For scroll-region edge fading use `useScrollEdges` with `scrollFadeClass` or `scrollFadeXClass` and `scrollFadeAttributes`; do not hand-roll a mask. For a constrained, single-line, read-only human label use `OverflowText` or `DropdownMenuItemLabel` in a menu; keep its fade and full-value tooltip instead of adding an ellipsis. Keep editable values, code, logs, paths, dense grids, and composite content on their appropriate overflow treatments. Source and variants in EMCN remain the authority if these examples change.
Copy file name to clipboardExpand all lines: CLAUDE.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -106,7 +106,7 @@ The `'use client'` server boundary, the app/worker runtime env split, and featur
106
106
107
107
## Styling and EMCN
108
108
109
-
- Tailwind only. Inline `style` only for a genuinely dynamic value or a CSS variable. Never update global styles; keep styling local to the component. `cn()` from `@sim/emcn` for conditional classes. `size-*` for equal height and width (icons default `size-[14px]`), never `h-N w-N`.
109
+
- Tailwind only. Inline `style` only for a genuinely dynamic value or a CSS variable. Keep component-specific styling local. Change tokens in `globals.css` deliberately when the shared design decision changes, then run `bun run design:generate`. `cn()` from `@sim/emcn` for conditional classes. `size-*` for equal height and width (icons default `size-[14px]`), never `h-N w-N`.
110
110
- Import components, `cn`, and tokens from the `@sim/emcn` barrel; icons from `@sim/emcn/icons`; CSS modules by file path. Never deep-import other component subpaths.
111
111
- The chip family is the canonical chrome: `ChipInput`, `ChipTextarea`, `ChipModal`/`ChipModalField`, `ChipSelect`/`ChipCombobox`/`ChipDropdown`, `ChipSwitch`, `ChipDatePicker`, `Chip`/`ChipLink`, `ChipTag`; `DropdownMenu` for context/action menus. Components own their chrome: consumers pass props (`error`, `icon`, `endAdornment`, `inputClassName`) and `className` carries only layout/sizing. Every labeled field inside a `ChipModalBody` is a `ChipModalField`.
112
112
- Consumer rules, tokens, text scale, and modal rhythm: `.claude/rules/sim-styling.md`. Authoring components in `packages/emcn`: `.claude/rules/emcn-components.md`. Product UI copy: `.claude/rules/sim-ui-copy.md`. Marketing copy and positioning: `.claude/rules/constitution.md`.
0 commit comments