@trading-game/design-intelligence-layer 0.17.4 → 1.0.1
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/AGENTS.md +105 -231
- package/README.md +50 -746
- package/dist/index.cjs +2753 -2986
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +247 -261
- package/dist/index.d.ts +247 -261
- package/dist/index.js +2648 -2856
- package/dist/index.js.map +1 -1
- package/docs/components/accordion.md +85 -0
- package/docs/components/alert-dialog.md +98 -0
- package/docs/components/aspect-ratio.md +55 -0
- package/docs/components/avatar.md +88 -0
- package/docs/components/badge.md +75 -0
- package/docs/components/banner.md +84 -0
- package/docs/components/bottom-navigation.md +90 -0
- package/docs/components/breadcrumb.md +85 -0
- package/docs/components/button.md +94 -0
- package/docs/components/calendar.md +74 -0
- package/docs/components/card.md +79 -0
- package/docs/components/carousel.md +82 -0
- package/docs/components/checkbox.md +66 -0
- package/docs/components/chip.md +72 -0
- package/docs/components/command.md +86 -0
- package/docs/components/context-menu.md +90 -0
- package/docs/components/dialog.md +95 -0
- package/docs/components/drawer.md +97 -0
- package/docs/components/dropdown-menu.md +92 -0
- package/docs/components/empty.md +87 -0
- package/docs/components/field.md +117 -0
- package/docs/components/hover-card.md +77 -0
- package/docs/components/input-group.md +105 -0
- package/docs/components/input-otp.md +87 -0
- package/docs/components/input.md +71 -0
- package/docs/components/item.md +105 -0
- package/docs/components/label.md +56 -0
- package/docs/components/link.md +66 -0
- package/docs/components/menubar.md +102 -0
- package/docs/components/native-select.md +71 -0
- package/docs/components/navigation-button.md +68 -0
- package/docs/components/navigation-menu.md +99 -0
- package/docs/components/numpad.md +78 -0
- package/docs/components/pagination.md +84 -0
- package/docs/components/popover.md +89 -0
- package/docs/components/profile-photo.md +81 -0
- package/docs/components/progress.md +60 -0
- package/docs/components/radio-group.md +82 -0
- package/docs/components/resizable.md +79 -0
- package/docs/components/scroll-area.md +66 -0
- package/docs/components/section-message.md +99 -0
- package/docs/components/select.md +105 -0
- package/docs/components/separator.md +55 -0
- package/docs/components/sheet.md +91 -0
- package/docs/components/sidebar.md +125 -0
- package/docs/components/skeleton.md +51 -0
- package/docs/components/slider.md +61 -0
- package/docs/components/spinner.md +52 -0
- package/docs/components/stepper.md +68 -0
- package/docs/components/switch.md +57 -0
- package/docs/components/table.md +86 -0
- package/docs/components/tabs.md +95 -0
- package/docs/components/textarea.md +58 -0
- package/docs/components/toast.md +66 -0
- package/docs/components/toggle-group.md +77 -0
- package/docs/components/toggle.md +60 -0
- package/docs/components/tooltip.md +83 -0
- package/docs/foundations/colors.md +110 -0
- package/docs/foundations/motion.md +63 -0
- package/docs/foundations/shape-layout.md +56 -0
- package/docs/foundations/typography.md +83 -0
- package/docs/patterns/forms.md +70 -0
- package/docs/patterns/menus.md +50 -0
- package/docs/patterns/on-brand.md +43 -0
- package/guides/audits/design-system-audit-2026-07.md +135 -0
- package/guides/rules/design-system-consuming-project.mdc +56 -0
- package/package.json +5 -6
- package/src/styles.css +1634 -252
- 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.
|