@trading-game/design-intelligence-layer 0.17.4 → 1.0.0

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.
Files changed (77) hide show
  1. package/AGENTS.md +104 -231
  2. package/README.md +46 -746
  3. package/dist/index.cjs +2753 -2986
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +247 -261
  6. package/dist/index.d.ts +247 -261
  7. package/dist/index.js +2648 -2856
  8. package/dist/index.js.map +1 -1
  9. package/docs/components/accordion.md +85 -0
  10. package/docs/components/alert-dialog.md +98 -0
  11. package/docs/components/aspect-ratio.md +55 -0
  12. package/docs/components/avatar.md +88 -0
  13. package/docs/components/badge.md +75 -0
  14. package/docs/components/banner.md +84 -0
  15. package/docs/components/bottom-navigation.md +90 -0
  16. package/docs/components/breadcrumb.md +85 -0
  17. package/docs/components/button.md +94 -0
  18. package/docs/components/calendar.md +74 -0
  19. package/docs/components/card.md +79 -0
  20. package/docs/components/carousel.md +82 -0
  21. package/docs/components/checkbox.md +66 -0
  22. package/docs/components/chip.md +72 -0
  23. package/docs/components/command.md +86 -0
  24. package/docs/components/context-menu.md +90 -0
  25. package/docs/components/dialog.md +95 -0
  26. package/docs/components/drawer.md +97 -0
  27. package/docs/components/dropdown-menu.md +92 -0
  28. package/docs/components/empty.md +87 -0
  29. package/docs/components/field.md +117 -0
  30. package/docs/components/hover-card.md +77 -0
  31. package/docs/components/input-group.md +105 -0
  32. package/docs/components/input-otp.md +87 -0
  33. package/docs/components/input.md +71 -0
  34. package/docs/components/item.md +105 -0
  35. package/docs/components/label.md +56 -0
  36. package/docs/components/link.md +66 -0
  37. package/docs/components/menubar.md +102 -0
  38. package/docs/components/native-select.md +71 -0
  39. package/docs/components/navigation-button.md +68 -0
  40. package/docs/components/navigation-menu.md +99 -0
  41. package/docs/components/numpad.md +78 -0
  42. package/docs/components/pagination.md +84 -0
  43. package/docs/components/popover.md +89 -0
  44. package/docs/components/profile-photo.md +81 -0
  45. package/docs/components/progress.md +60 -0
  46. package/docs/components/radio-group.md +82 -0
  47. package/docs/components/resizable.md +79 -0
  48. package/docs/components/scroll-area.md +66 -0
  49. package/docs/components/section-message.md +99 -0
  50. package/docs/components/select.md +105 -0
  51. package/docs/components/separator.md +55 -0
  52. package/docs/components/sheet.md +91 -0
  53. package/docs/components/sidebar.md +125 -0
  54. package/docs/components/skeleton.md +51 -0
  55. package/docs/components/slider.md +61 -0
  56. package/docs/components/spinner.md +52 -0
  57. package/docs/components/stepper.md +68 -0
  58. package/docs/components/switch.md +57 -0
  59. package/docs/components/table.md +86 -0
  60. package/docs/components/tabs.md +95 -0
  61. package/docs/components/textarea.md +58 -0
  62. package/docs/components/toast.md +66 -0
  63. package/docs/components/toggle-group.md +77 -0
  64. package/docs/components/toggle.md +60 -0
  65. package/docs/components/tooltip.md +83 -0
  66. package/docs/foundations/colors.md +109 -0
  67. package/docs/foundations/motion.md +63 -0
  68. package/docs/foundations/shape-layout.md +56 -0
  69. package/docs/foundations/typography.md +83 -0
  70. package/docs/patterns/forms.md +70 -0
  71. package/docs/patterns/menus.md +50 -0
  72. package/docs/patterns/on-brand.md +43 -0
  73. package/guides/audits/design-system-audit-2026-07.md +135 -0
  74. package/guides/rules/design-system-consuming-project.mdc +56 -0
  75. package/package.json +5 -6
  76. package/src/styles.css +1616 -251
  77. package/guides/design-system-guide/trading-game-ds-guide.md +0 -933
@@ -0,0 +1,87 @@
1
+ # Input OTP
2
+
3
+ Segmented one-time-code entry — one visual slot per character over a single hidden input.
4
+
5
+ ## When to use
6
+
7
+ - Verification codes: SMS/email OTPs, 2FA codes, withdrawal confirmation codes.
8
+
9
+ **When not:** For ordinary short text use [Input](./input.md). OTP slots are for fixed-length codes only.
10
+
11
+ ## Anatomy
12
+
13
+ - `InputOTP` — wrapper around the `input-otp` library's `OTPInput`; owns `maxLength`, value, and the hidden real input. `data-slot="input-otp"`.
14
+ - `InputOTPGroup` — visually joins consecutive slots (first slot `rounded-l-sm`, last `rounded-r-sm`). `data-slot="input-otp-group"`.
15
+ - `InputOTPSlot` — one character cell, 40px (`size-10`, the md rail); renders the char and a fake blinking caret. `data-slot="input-otp-slot"`, `data-active`.
16
+ - `InputOTPSeparator` — `role="separator"` dash (`MinusIcon`) between groups. `data-slot="input-otp-separator"`.
17
+
18
+ ## API
19
+
20
+ ### InputOTP
21
+
22
+ | Prop | Type | Default | Notes |
23
+ | --- | --- | --- | --- |
24
+ | `containerClassName` | `string` | — | Class for the flex container (`gap-2`, 50% opacity when disabled). |
25
+ | …rest | `OTPInput` props | — | `maxLength`, `value`, `onChange`, `pattern`, etc. from the `input-otp` package. |
26
+
27
+ ### InputOTPSlot
28
+
29
+ | Prop | Type | Default | Notes |
30
+ | --- | --- | --- | --- |
31
+ | `index` | `number` | required | Which character of the code this cell shows. |
32
+
33
+ ## Variants & sizes
34
+
35
+ No variants; one 40px slot size.
36
+
37
+ ```tsx
38
+ import {
39
+ InputOTP,
40
+ InputOTPGroup,
41
+ InputOTPSlot,
42
+ InputOTPSeparator,
43
+ } from "@trading-game/design-intelligence-layer"
44
+
45
+ <InputOTP maxLength={6}>
46
+ <InputOTPGroup>
47
+ <InputOTPSlot index={0} />
48
+ <InputOTPSlot index={1} />
49
+ <InputOTPSlot index={2} />
50
+ </InputOTPGroup>
51
+ <InputOTPSeparator />
52
+ <InputOTPGroup>
53
+ <InputOTPSlot index={3} />
54
+ <InputOTPSlot index={4} />
55
+ <InputOTPSlot index={5} />
56
+ </InputOTPGroup>
57
+ </InputOTP>
58
+ ```
59
+
60
+ ## Tokens
61
+
62
+ **Colour (semantic):**
63
+ - slot: `bg-background-primary-surface` + `border-border-default-default` (hairline), ink `text-text-prominent-default`
64
+ - active slot: `border-ring-focus-default` + 3px `ring-ring-focus-soft` (the soft text-field ring), lifted with `z-10`
65
+ - caret: `bg-text-prominent-default` (`animate-caret-blink`, 1s)
66
+ - invalid (`aria-invalid`): `border-border-error-default` + `ring-ring-error-soft`, active or not
67
+
68
+ **Type (private):** `--input-otp-*`: 14 / regular / 20 (`type-input-otp`).
69
+
70
+ ## Behaviour
71
+
72
+ - State lives in the library's single hidden input; slots read `char`, `isActive`, and `hasFakeCaret` from `OTPInputContext` by `index`.
73
+ - The active slot shows the focus treatment via `data-active=true` — same border/ring pair as Input's focus.
74
+ - Slots inside a group share hairline borders; only the group's outer corners are rounded (`rounded-l-sm`/`rounded-r-sm`).
75
+ - Disabled state dims the whole container to 50% via `has-disabled`.
76
+
77
+ ## Do / Don't
78
+
79
+ **Do**
80
+ - Match slot count to `maxLength` — indexes 0..maxLength-1, no gaps.
81
+ - Use `InputOTPSeparator` to chunk long codes (3+3 for six digits).
82
+ - Put `aria-invalid` on the OTP input when the code is rejected; error tokens flow to every slot.
83
+
84
+ **Don't**
85
+ - Don't restyle the caret; the 1px `text-prominent-default` blink is the affordance that the slot is live.
86
+ - Don't give slots the 50% control focus ring — OTP slots are text entry and use the 8% soft ring.
87
+ - Don't add per-slot shadows or fills; hairline on primary surface, solid only.
@@ -0,0 +1,71 @@
1
+ # Input
2
+
3
+ Single-line text control on the 32/40/48 size rail, with a dark-glaze variant for brand surfaces.
4
+
5
+ ## When to use
6
+
7
+ - Free text, numbers, emails, passwords, file pickers — any single-line `<input>`.
8
+
9
+ **When not:** For text with leading/trailing addons or inline buttons use [Input Group](./input-group.md). For fixed-length codes use [Input OTP](./input-otp.md). For pick-one dropdowns use [Native Select](./native-select.md).
10
+
11
+ ## Anatomy
12
+
13
+ - `Input` — the styled `<input>`. `data-slot="input"`, plus `data-variant` and `data-size` reflecting the resolved values.
14
+ - `inputVariants` — exported cva for building lookalike controls.
15
+
16
+ ## API
17
+
18
+ ### Input
19
+
20
+ | Prop | Type | Default | Notes |
21
+ | --- | --- | --- | --- |
22
+ | `variant` | `"default" \| "on-brand" \| "inverse"` | `"default"` | `on-brand` is the sunken treatment for brand-coloured surfaces: `--primitive-black-alpha-32` glaze fill + `--primitive-white-alpha-24` border over whatever brand colour sits underneath. `inverse` @deprecated — alias of `on-brand`, removed at v1.0. |
23
+ | `size` | `"sm" \| "md" \| "lg"` | `"md"` (or the enclosing Field's size) | sm = `h-8` (32px) px-3, md = `h-10` (40px) px-3, lg = `h-12` (48px) px-4. The native `size` attribute is omitted from the type. |
24
+
25
+ All other native `<input>` props pass through (`type`, `disabled`, `readOnly`, `aria-invalid`, …).
26
+
27
+ ## Variants & sizes
28
+
29
+ ```tsx
30
+ import { Input } from "@trading-game/design-intelligence-layer"
31
+
32
+ // Size rail: 32 / 40 / 48.
33
+ <Input size="sm" placeholder="Search" />
34
+ <Input placeholder="Amount" /> {/* md, 40px */}
35
+ <Input size="lg" placeholder="Email" />
36
+
37
+ // On a brand-coloured hero/panel:
38
+ <Input variant="on-brand" placeholder="Enter amount" />
39
+ ```
40
+
41
+ ## Tokens
42
+
43
+ **Colour (semantic):**
44
+ - default: `border-border-default-default`, `bg-background-primary-surface`, `text-text-prominent-default`, placeholder `text-text-subtle-default`
45
+ - selection: `bg-background-brand-default` + `text-text-on-brand-static`
46
+ - focus: `border-ring-focus-default` + `ring-ring-focus-soft` (the soft text-field ring)
47
+ - read-only: `bg-background-secondary-surface`, border transparent — still focusable, selectable, and copyable
48
+ - invalid (`aria-invalid`): `border-border-error-default` + `ring-ring-error-soft`
49
+ - on-brand (alpha primitives — the sanctioned glaze use): fill `--primitive-black-alpha-32`, border `--primitive-white-alpha-24`, placeholder `--primitive-white-alpha-50`, focus ring `--primitive-white-alpha-40`, ink `text-text-on-brand-static`
50
+
51
+ **Type (private):** `--input-*` ladder, all regular weight — sm 12/16, md 14/20, lg 16/24 (`type-input-sm|md|lg`).
52
+
53
+ ## Behaviour
54
+
55
+ - Inside a [Field](./field.md), `useFieldSize()` supplies the default size; an explicit `size` prop on the Input wins.
56
+ - `readOnly` removes the border and switches to the secondary surface — visibly "not editable" while staying focusable and copyable (readonly is reachable, unlike disabled).
57
+ - `disabled` = pointer-events none, `cursor-not-allowed`, 50% opacity.
58
+ - Focus ring is `focus-visible` only — 3px at 8% alpha, the soft variant reserved for text fields.
59
+ - `inverse` renders identically to `on-brand`; it exists only for migration and is deleted at v1.0.
60
+
61
+ ## Do / Don't
62
+
63
+ **Do**
64
+ - Stay on the rail: 32/40/48 only, default md.
65
+ - Use `variant="on-brand"` on brand-coloured surfaces — the black-alpha glaze works over any brand fill.
66
+ - Let the Field cascade size the input; only set `size` explicitly to override.
67
+
68
+ **Don't**
69
+ - Don't "fix" the `rounded-xs` (4px) corner — inputs are deliberately the squarest thing in the system.
70
+ - Don't swap the 8% soft focus ring for the 50% control ring; text fields use the soft one.
71
+ - Don't write new code with `variant="inverse"`; it is a deprecated alias.
@@ -0,0 +1,105 @@
1
+ # Item
2
+
3
+ Generic list row — media, title/description, and actions in one flex shell, with auto-divided groups.
4
+
5
+ ## When to use
6
+
7
+ - Settings rows, notification lists, asset lists, any media + text + action row.
8
+ - Stacks of rows: wrap in `ItemGroup` for automatic hairline dividers.
9
+
10
+ **When not:** For interactive menus use [Menubar](./menubar.md) or a dropdown. For empty containers use [Empty](./empty.md). For form rows use [Field](./field.md).
11
+
12
+ ## Anatomy
13
+
14
+ - `ItemGroup` — `role="list"` column; auto-divider mode on by default. `data-slot="item-group"`, `data-divider`.
15
+ - `ItemSeparator` — manual hairline (`Separator`). `data-slot="item-separator"`.
16
+ - `Item` — the row (`rounded-md`, transparent hairline border for state changes). `data-slot="item"`, `data-variant`, `data-size`.
17
+ - `ItemMedia` — leading icon/image slot; top-aligns itself when a description is present. `data-slot="item-media"`, `data-variant`.
18
+ - `ItemContent` — flex-1 column for title + description. `data-slot="item-content"`.
19
+ - `ItemTitle` — 14/medium/20 prominent ink. `data-slot="item-title"`.
20
+ - `ItemDescription` — 14/regular/20 subtle ink, clamped to 2 lines. `data-slot="item-description"`.
21
+ - `ItemActions` — trailing controls. `data-slot="item-actions"`.
22
+ - `ItemHeader` / `ItemFooter` — full-width rows above/below the main line. `data-slot="item-header"` / `"item-footer"`.
23
+
24
+ ## API
25
+
26
+ ### ItemGroup
27
+
28
+ | Prop | Type | Default | Notes |
29
+ | --- | --- | --- | --- |
30
+ | `divider` | `boolean` | `true` | Draws a 1px `border-default-default` line (inset `inset-x-4`) after each non-last **default-variant** Item, in a 4px gap. `outline`/`muted` items are skipped so borders don't double. Pass `false` for flush stacks or manual `ItemSeparator`. |
31
+
32
+ ### Item
33
+
34
+ | Prop | Type | Default | Notes |
35
+ | --- | --- | --- | --- |
36
+ | `variant` | `"default" \| "outline" \| "muted"` | `"default"` | default transparent; outline = hairline `border-border-default-default` + hover fill; muted = `bg-background-secondary-surface`. |
37
+ | `size` | `"default" \| "sm"` | `"default"` | default `gap-4 p-4`; sm `gap-2.5 px-4 py-3`. |
38
+ | `asChild` | `boolean` | `false` | Render as link/button via Radix Slot; anchors get `hover:bg-background-hover-default`. |
39
+
40
+ ### ItemMedia
41
+
42
+ | Prop | Type | Default | Notes |
43
+ | --- | --- | --- | --- |
44
+ | `variant` | `"default" \| "icon" \| "image"` | `"default"` | icon = 32px circle, `border-border-default-default` + `bg-background-secondary-surface`, 16px svg, `text-icon-prominent-default`; image = 40px circular crop. |
45
+
46
+ ## Variants & sizes
47
+
48
+ ```tsx
49
+ import {
50
+ Item,
51
+ ItemGroup,
52
+ ItemMedia,
53
+ ItemContent,
54
+ ItemTitle,
55
+ ItemDescription,
56
+ ItemActions,
57
+ Button,
58
+ } from "@trading-game/design-intelligence-layer"
59
+ import { BellIcon } from "lucide-react"
60
+
61
+ <ItemGroup>
62
+ <Item>
63
+ <ItemMedia variant="icon"><BellIcon /></ItemMedia>
64
+ <ItemContent>
65
+ <ItemTitle>Price alerts</ItemTitle>
66
+ <ItemDescription>Notify me when Volatility 75 moves 2% in an hour.</ItemDescription>
67
+ </ItemContent>
68
+ <ItemActions>
69
+ <Button variant="secondary" size="sm">Edit</Button>
70
+ </ItemActions>
71
+ </Item>
72
+ <Item variant="outline" size="sm">…</Item>
73
+ </ItemGroup>
74
+ ```
75
+
76
+ ## Tokens
77
+
78
+ **Colour (semantic):**
79
+ - title `text-text-prominent-default`; description `text-text-subtle-default` (links underline, hover `text-text-brand-default`)
80
+ - outline: `border-border-default-default`, hover `bg-background-hover-default`; muted: `bg-background-secondary-surface`
81
+ - group divider: `bg-border-default-default`
82
+ - focus: `border-ring-focus-default` + 3px `ring-ring-focus-strong` (control ring)
83
+ - icon media: `bg-background-secondary-surface` + `text-icon-prominent-default`
84
+
85
+ **Type (private):** `--item-title-*`: 14 / medium / 20; `--item-description-*`: 14 / regular / 20 (the Item shell itself defaults to the description type).
86
+
87
+ ## Behaviour
88
+
89
+ - Group dividers are drawn as `::after` elements in the 4px gap — the Item keeps no border of its own, so hover/focus borders don't fight the divider.
90
+ - The last item and any non-default-variant item never get an auto divider.
91
+ - `ItemMedia` shifts down 2px and self-starts when the row contains a description, keeping the icon optically aligned with the title.
92
+ - Descriptions clamp at 2 lines (`line-clamp-2`).
93
+ - `asChild` rows are fully interactive: instant-duration colour transition, hover fill on anchors, 3px/50% focus ring.
94
+
95
+ ## Do / Don't
96
+
97
+ **Do**
98
+ - Let `ItemGroup` draw dividers; only reach for `ItemSeparator` with `divider={false}`.
99
+ - Use `rounded-md` rows as-is — 8px is the row radius tier.
100
+ - Use `asChild` with a real `<a>`/`<button>` when the whole row navigates.
101
+
102
+ **Don't**
103
+ - Don't mix `variant="outline"` items into a dividered group expecting dividers — they are deliberately skipped.
104
+ - Don't push the title past medium 500; row titles never reach semibold, and bold does not exist in components.
105
+ - Don't add shadows to muted/outline rows; state is shown by fill and hairline only.
@@ -0,0 +1,56 @@
1
+ # Label
2
+
3
+ Form control label at 14/medium/20 that deliberately inherits its colour.
4
+
5
+ ## When to use
6
+
7
+ - Labelling any form control by `htmlFor`/nesting — checkboxes, radios, inputs, switches.
8
+
9
+ **When not:** Inside a [Field](./field.md), use `FieldLabel` — it adds the size ladder and choice-card behaviour on top of Label. For non-label row titles use `FieldTitle` or `ItemTitle`.
10
+
11
+ ## Anatomy
12
+
13
+ - `Label` — Radix `LabelPrimitive.Root` with system type and disabled handling. `data-slot="label"`. Single part.
14
+
15
+ ## API
16
+
17
+ ### Label
18
+
19
+ No custom props — all Radix `Label.Root` / native `<label>` props pass through (`htmlFor`, etc.).
20
+
21
+ ## Variants & sizes
22
+
23
+ No variants; one size, 14/medium/20.
24
+
25
+ ```tsx
26
+ import { Label, Checkbox } from "@trading-game/design-intelligence-layer"
27
+
28
+ <div className="flex items-center gap-2">
29
+ <Checkbox id="tnc" />
30
+ <Label htmlFor="tnc">I accept the terms and conditions</Label>
31
+ </div>
32
+ ```
33
+
34
+ ## Tokens
35
+
36
+ **Colour (semantic):** none set — colour is inherited on purpose, so a parent [Field](./field.md) with `data-invalid` (`text-text-error-default`) paints the label red through inheritance.
37
+
38
+ **Type (private):** `--label-*`: 14 / medium / 20 (`type-label`).
39
+
40
+ ## Behaviour
41
+
42
+ - Colour inheritance is the contract: Field's `data-[invalid=true]:text-text-error-default` reaches the label with zero wiring.
43
+ - Disabled affordances: `group-data-[disabled=true]` (pointer-events none + 50% opacity) and `peer-disabled` (`cursor-not-allowed` + 50% opacity) — pair Label with `peer` controls or a `group` wrapper.
44
+ - `select-none`: label text is not selectable, so double-clicks hit the control.
45
+ - Layout is `flex items-center gap-2`, ready to hold a leading control or icon.
46
+
47
+ ## Do / Don't
48
+
49
+ **Do**
50
+ - Wire `htmlFor` to the control id, or nest the control inside the label.
51
+ - Let disabled state come from the `peer`/`group` mechanics rather than manual opacity.
52
+
53
+ **Don't**
54
+ - Never force an ink on Label (`text-*` classes) — you would sever the Field error cascade.
55
+ - Don't override the type: 14/medium/20 is fixed, and `.type-label` is plain CSS that beats `font-*` utilities anyway.
56
+ - Don't use Label for non-form headings; it carries `<label>` semantics.
@@ -0,0 +1,66 @@
1
+ # Link
2
+
3
+ Semibold inline navigation — quiet at rest, underline and darker ink arrive on hover.
4
+
5
+ ## When to use
6
+
7
+ - Navigational text: "View all trades", "Learn more", footer links, links with a trailing arrow icon.
8
+
9
+ **When not:** For actions that change state use Button. For circular icon-only navigation use [Navigation Button](./navigation-button.md). For links inside menus use [Navigation Menu](./navigation-menu.md)'s `NavigationMenuLink`.
10
+
11
+ ## Anatomy
12
+
13
+ - `Link` — a styled `<a>` (or Slot via `asChild`); inline-flex with a 6px gap for icons, `font-display normal-case`. `data-slot="link"`, `data-size`, `data-tone`.
14
+ - `linkVariants` — exported cva for composing.
15
+
16
+ ## API
17
+
18
+ ### Link
19
+
20
+ | Prop | Type | Default | Notes |
21
+ | --- | --- | --- | --- |
22
+ | `size` | `"lg" \| "md" \| "sm"` | `"lg"` | lg 16/24, md 14/20, sm 12/16 — all semibold. Icons `size-4` (lg/md) or `size-3.5` (sm). |
23
+ | `tone` | `"brand" \| "on-brand"` | `"brand"` | brand = brand ink darkening one step on hover; on-brand = static white for brand-coloured surfaces, hover `--primitive-white-alpha-80`. |
24
+ | `asChild` | `boolean` | `false` | Compose with a router `<Link>` via Radix Slot. |
25
+
26
+ ## Variants & sizes
27
+
28
+ ```tsx
29
+ import { Link } from "@trading-game/design-intelligence-layer"
30
+ import { ArrowRightIcon } from "lucide-react"
31
+
32
+ <Link href="/markets">Explore markets <ArrowRightIcon /></Link> {/* lg 16/24 */}
33
+ <Link size="md" href="/reports">View report</Link> {/* 14/20 */}
34
+ <Link size="sm" href="/terms">Terms</Link> {/* 12/16 */}
35
+
36
+ // On a brand-coloured hero:
37
+ <Link tone="on-brand" href="/promo">See offer</Link>
38
+ ```
39
+
40
+ ## Tokens
41
+
42
+ **Colour (semantic):**
43
+ - brand: `text-text-brand-default`, hover `text-text-brand-hover` (ink darkens one step)
44
+ - on-brand: `text-text-on-brand-static`, hover `--primitive-white-alpha-80` (alpha primitive, sanctioned for on-brand surfaces)
45
+ - focus: 3px `ring-ring-focus-strong`
46
+
47
+ **Type (private):** `--link-*` ladder, all semibold (`--link-font-weight`) — lg 16/24, md 14/20, sm 12/16.
48
+
49
+ ## Behaviour
50
+
51
+ - Rest state has NO underline — hover adds `underline` (`decoration-2`, `underline-offset-4`) plus the one-step ink darken. Semibold weight is the rest-state affordance.
52
+ - Trailing icon nudge: the last `svg` child slides right 2px (`translate-x-0.5`) on hover.
53
+ - Press feedback is `active:opacity-60`. Anchors have no disabled state — remove the link instead of disabling it.
54
+ - Renders in `font-display` with `normal-case`.
55
+
56
+ ## Do / Don't
57
+
58
+ **Do**
59
+ - Keep the default lg (16/24) for standalone links; drop to md/sm only to match surrounding body/caption text.
60
+ - Put the arrow icon last so the 2px hover nudge fires.
61
+ - Use `tone="on-brand"` on brand fills instead of overriding inks by hand.
62
+
63
+ **Don't**
64
+ - Don't underline at rest — the underline is hover feedback in this system.
65
+ - Don't lighten the weight; links are semibold at every size (and 600 is the system max — no bold).
66
+ - Don't use Link for onClick-only actions with no href; that is a Button in link's clothing.
@@ -0,0 +1,102 @@
1
+ # Menubar
2
+
3
+ Horizontal desktop-style menu bar — File/Edit/View triggers opening dropdown panels.
4
+
5
+ ## When to use
6
+
7
+ - Application-chrome command surfaces on desktop layouts: editors, dashboards with many commands.
8
+
9
+ **When not:** For a single contextual menu on one trigger use Dropdown Menu. For site navigation with panels use [Navigation Menu](./navigation-menu.md).
10
+
11
+ ## Anatomy
12
+
13
+ - `Menubar` — the bar: `h-10`, `rounded-lg`, hairline border, `p-1 gap-1`. `data-slot="menubar"`.
14
+ - `MenubarMenu` — one menu (trigger + content pair). `data-slot="menubar-menu"`.
15
+ - `MenubarTrigger` — bar button, `rounded-md px-2 py-1`. `data-slot="menubar-trigger"`.
16
+ - `MenubarContent` / `MenubarSubContent` — dropdown panels, `rounded-lg`, `min-w-[12rem]` / `min-w-[8rem]`, `p-1`. `data-slot="menubar-content"` / `"menubar-sub-content"`.
17
+ - `MenubarItem` — command row, `rounded-md px-2 py-1.5`. `data-slot="menubar-item"`.
18
+ - `MenubarCheckboxItem` / `MenubarRadioItem` (+ `MenubarRadioGroup`) — checkable rows with a left indicator (check / 8px filled circle). `data-slot="menubar-checkbox-item"` / `"menubar-radio-item"`.
19
+ - `MenubarLabel` — section heading. `data-slot="menubar-label"`.
20
+ - `MenubarSeparator` — 1px rule, `-mx-1 my-1`. `data-slot="menubar-separator"`.
21
+ - `MenubarShortcut` — right-aligned key hint. `data-slot="menubar-shortcut"`.
22
+ - `MenubarSub` / `MenubarSubTrigger` — nested submenu; trigger appends a `ChevronRightIcon`. `data-slot="menubar-sub"` / `"menubar-sub-trigger"`.
23
+ - `MenubarGroup`, `MenubarPortal` — grouping/portal passthroughs.
24
+
25
+ ## API
26
+
27
+ ### MenubarItem
28
+
29
+ | Prop | Type | Default | Notes |
30
+ | --- | --- | --- | --- |
31
+ | `variant` | `"default" \| "destructive"` | `"default"` | destructive = `text-text-error-default` ink, focus fill `bg-background-error-default`, icons forced error ink. |
32
+ | `inset` | `boolean` | — | `pl-8` to align with checkable rows. |
33
+
34
+ ### MenubarContent
35
+
36
+ | Prop | Type | Default | Notes |
37
+ | --- | --- | --- | --- |
38
+ | `align` | Radix align | `"start"` | |
39
+ | `alignOffset` | `number` | `-4` | |
40
+ | `sideOffset` | `number` | `8` | |
41
+
42
+ `MenubarTrigger`, `MenubarCheckboxItem` (`checked`), `MenubarRadioItem` (`value`), `MenubarLabel` (`inset`), and `MenubarSubTrigger` (`inset`) otherwise pass Radix props through.
43
+
44
+ ## Variants & sizes
45
+
46
+ No size rail — menus are fixed density (trigger 14/medium/20, items 14/regular/20).
47
+
48
+ ```tsx
49
+ import {
50
+ Menubar,
51
+ MenubarMenu,
52
+ MenubarTrigger,
53
+ MenubarContent,
54
+ MenubarItem,
55
+ MenubarSeparator,
56
+ MenubarShortcut,
57
+ } from "@trading-game/design-intelligence-layer"
58
+
59
+ <Menubar>
60
+ <MenubarMenu>
61
+ <MenubarTrigger>File</MenubarTrigger>
62
+ <MenubarContent>
63
+ <MenubarItem>
64
+ New chart <MenubarShortcut>⌘N</MenubarShortcut>
65
+ </MenubarItem>
66
+ <MenubarSeparator />
67
+ <MenubarItem variant="destructive">Delete workspace</MenubarItem>
68
+ </MenubarContent>
69
+ </MenubarMenu>
70
+ </Menubar>
71
+ ```
72
+
73
+ ## Tokens
74
+
75
+ **Colour (semantic):**
76
+ - bar and panels: `bg-background-primary-surface` + `border-border-default-default`, ink `text-text-prominent-default`
77
+ - hover/focus rows and triggers: `bg-background-hover-default` + `text-text-prominent-default`
78
+ - open trigger: `bg-background-hover-default` + `text-text-brand-selected` (selection-ink family)
79
+ - destructive: `text-text-error-default`, focus `bg-background-error-default`
80
+ - headings `text-text-prominent-default`; shortcuts `text-text-subtle-default`; row icons `text-icon-subtle-default`
81
+ - separator `bg-border-default-default`
82
+
83
+ **Type (private):** shared `--menu-*` family — item 14/regular/20, heading 12/medium/16, trigger 14/medium/20, shortcut 12/regular/16.
84
+
85
+ ## Behaviour
86
+
87
+ - Radix Menubar semantics: arrow keys move across triggers, open menus follow pointer, submenus open from `MenubarSub`.
88
+ - The open trigger takes selected ink (`text-text-brand-selected`) on the hover fill — the bar shows which menu is down.
89
+ - Panels are portalled, animated (fade/zoom/side slide), and solid — no shadow.
90
+ - Row icons default to 16px `text-icon-subtle-default` unless the icon sets its own size/colour class; destructive rows force error ink on icons.
91
+
92
+ ## Do / Don't
93
+
94
+ **Do**
95
+ - Use `inset` on plain items sharing a panel with checkbox/radio rows so text columns align at `pl-8`.
96
+ - Keep panels `rounded-lg` (10px) with `rounded-md` (8px) rows — the menu radius tiers.
97
+ - Reserve `variant="destructive"` for irreversible commands.
98
+
99
+ **Don't**
100
+ - Don't restyle the open-trigger ink; `text-text-brand-selected` is the selection contract (dark theme has its own values — it is not a mirror of light).
101
+ - Don't put shortcuts in their own component — `MenubarShortcut` right-aligns and mutes them for you.
102
+ - Don't add elevation/shadows to `MenubarContent`; hairline on solid surface only.
@@ -0,0 +1,71 @@
1
+ # Native Select
2
+
3
+ Styled native `<select>` on the 32/40/48 rail — the platform picker with system paint.
4
+
5
+ ## When to use
6
+
7
+ - Pick-one dropdowns where the native OS sheet/menu is acceptable or preferred (mobile forms, simple settings).
8
+
9
+ **When not:** For styled option rows, search, or rich option content use the custom Select. For free text use [Input](./input.md).
10
+
11
+ ## Anatomy
12
+
13
+ - `NativeSelect` — wrapper `div` (`data-slot="native-select-wrapper"`, `w-fit`) containing the styled `<select>` (`data-slot="native-select"`, `data-size`) and an absolutely positioned `ChevronDownIcon` (`data-slot="native-select-icon"`, 16px, right 14px).
14
+ - `NativeSelectOption` — `<option>` passthrough. `data-slot="native-select-option"`.
15
+ - `NativeSelectOptGroup` — `<optgroup>` passthrough. `data-slot="native-select-optgroup"`.
16
+
17
+ ## API
18
+
19
+ ### NativeSelect
20
+
21
+ | Prop | Type | Default | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `size` | `"sm" \| "md" \| "lg" \| "default"` | `"md"` (or the enclosing Field's size) | sm `h-8` (32), md `h-10` (40), lg `h-12` (48, `px-4 pr-10`). `"default"` @deprecated — alias resolving to `md`, removed at v1.0. The native numeric `size` attribute is not available. |
24
+
25
+ All other native `<select>` props pass through.
26
+
27
+ ## Variants & sizes
28
+
29
+ ```tsx
30
+ import {
31
+ NativeSelect,
32
+ NativeSelectOption,
33
+ } from "@trading-game/design-intelligence-layer"
34
+
35
+ <NativeSelect size="sm" defaultValue="1m">
36
+ <NativeSelectOption value="1m">1 minute</NativeSelectOption>
37
+ <NativeSelectOption value="5m">5 minutes</NativeSelectOption>
38
+ </NativeSelect>
39
+
40
+ <NativeSelect>…</NativeSelect> {/* md, 40px */}
41
+ <NativeSelect size="lg">…</NativeSelect> {/* 48px */}
42
+ ```
43
+
44
+ ## Tokens
45
+
46
+ **Colour (semantic):**
47
+ - `border-border-default-default`, `bg-background-primary-surface`, `text-text-prominent-default`
48
+ - chevron `text-icon-subtle-default`
49
+ - selection: `bg-background-brand-default` + `text-text-on-brand-static`
50
+ - focus: `border-ring-focus-default` + 3px `ring-ring-focus-soft` (soft text-field ring)
51
+
52
+ **Type (private):** `--native-select-*` ladder, regular weight — sm 12/16, md 14/20, lg 16/24 (`type-native-select-sm|md|lg`).
53
+
54
+ ## Behaviour
55
+
56
+ - Joins the [Field](./field.md) cascade: with no `size` prop it takes the Field's size; an explicit prop wins. `size="default"` always resolves to `md` regardless of context.
57
+ - `appearance-none` + the injected chevron replace the platform arrow; right padding (`pr-9`, lg `pr-10`) reserves its space.
58
+ - Disabled dims the wrapper (`has-[select:disabled]:opacity-24`) and kills pointer events on the select.
59
+ - The wrapper is `w-fit` and `className` lands on the inner `<select>` — for full-width layouts you must widen the wrapper (e.g. a `w-full` parent with `[&>[data-slot=native-select-wrapper]]:w-full`), not just the select.
60
+
61
+ ## Do / Don't
62
+
63
+ **Do**
64
+ - Stay on the 32/40/48 rail and let Field size it in forms.
65
+ - Use `NativeSelectOptGroup` for long lists; the native control handles it best.
66
+ - Keep the chevron as rendered — 16px subtle-icon ink at right 14px.
67
+
68
+ **Don't**
69
+ - Don't write `size="default"` in new code; it is a deprecated alias for `md`.
70
+ - Don't swap the soft 8% focus ring for the 50% control ring; selects follow the text-field focus recipe here.
71
+ - Don't try to restyle `<option>` rows — that is native UI; use the custom Select when options need system paint.
@@ -0,0 +1,68 @@
1
+ # Navigation Button
2
+
3
+ Circular icon-only control for moving through UI — back arrows, carousel steppers, close chevrons.
4
+
5
+ ## When to use
6
+
7
+ - Icon-only navigation affordances: back/forward, next/previous, header chrome.
8
+ - Over imagery or brand fills, with the `frosted-on-brand` variant.
9
+
10
+ **When not:** For icon actions that submit or mutate use an icon-size Button. For paged number lists use [Pagination](./pagination.md).
11
+
12
+ ## Anatomy
13
+
14
+ - `NavigationButton` — a circular `<button>` (or Slot via `asChild`) holding one icon. `data-slot="navigation-button"`, `data-variant`, `data-size`.
15
+ - `navigationButtonVariants` — exported cva.
16
+
17
+ ## API
18
+
19
+ ### NavigationButton
20
+
21
+ | Prop | Type | Default | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `variant` | `"default" \| "frosted-on-brand"` | `"default"` | default = transparent circle, hover fill; glass = frosted circle for art/hero/brand surfaces. |
24
+ | `size` | `"xs" \| "sm" \| "md" \| "lg"` | `"lg"` | xs 24px (16px icon), sm 32px (20px icon), md 40px (20px icon), lg 48px (24px icon). |
25
+ | `asChild` | `boolean` | `false` | Render as `<a>` etc. via Radix Slot. |
26
+
27
+ ## Variants & sizes
28
+
29
+ ```tsx
30
+ import { NavigationButton } from "@trading-game/design-intelligence-layer"
31
+ import { ChevronLeftIcon } from "lucide-react"
32
+
33
+ <NavigationButton aria-label="Back"><ChevronLeftIcon /></NavigationButton> {/* lg, 48px */}
34
+ <NavigationButton size="sm" aria-label="Back"><ChevronLeftIcon /></NavigationButton>
35
+
36
+ // Over a hero image or brand fill:
37
+ <NavigationButton variant="frosted-on-brand" size="md" aria-label="Previous slide">
38
+ <ChevronLeftIcon />
39
+ </NavigationButton>
40
+ ```
41
+
42
+ ## Tokens
43
+
44
+ **Colour (semantic):**
45
+ - default: transparent, `text-icon-prominent-default`, hover `bg-background-hover-default`
46
+ - focus: 3px `ring-ring-focus-strong`
47
+ - glass (alpha primitives — the sanctioned glass use): fill + border `--primitive-white-alpha-24` with `backdrop-blur-md`, ink `text-text-on-brand-static`; hover thickens to `--primitive-white-alpha-32`, press thins to `--primitive-white-alpha-16`
48
+
49
+ **Type (private):** none — icon-only, no text.
50
+
51
+ ## Behaviour
52
+
53
+ - Always a circle (`rounded-full`), per the icon-button law.
54
+ - Press feedback: default variant uses `active:opacity-60`; glass replaces it (`active:opacity-100`) with the thinner alpha-16 frost so the blur never flickers.
55
+ - Disabled is `opacity-24` (a deliberate deep dim, not the usual 50) with pointer-events none.
56
+ - Transitions run `duration-fast ease-standard`.
57
+
58
+ ## Do / Don't
59
+
60
+ **Do**
61
+ - Always pass `aria-label`; there is no text child.
62
+ - Use `frosted-on-brand` only on top of imagery or brand colour — its white-alpha recipe assumes a non-neutral ground.
63
+ - Pick from the 24/32/40/48 circle sizes; note the default is `lg` (48px), sized for touch chrome.
64
+
65
+ **Don't**
66
+ - Don't square the corners or turn it into a pill; icon buttons are circles, always.
67
+ - Don't use glass on plain light surfaces — the alpha-primitive frost is reserved for glazes/glass over art or brand.
68
+ - Don't add labels next to the icon inside the button; pair with a `Link` or Button instead.