@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,52 @@
|
|
|
1
|
+
# Spinner
|
|
2
|
+
|
|
3
|
+
Eight-blade fading loading indicator that inherits `currentColor`.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Indeterminate waits tied to an action or small region — button loading states, inline fetches.
|
|
8
|
+
|
|
9
|
+
**When not:** For content placeholders with a known shape use [Skeleton](./skeleton.md). For measured progress use Progress.
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Spinner` — a `size-4` `text-current` div with `role="status"` / `aria-label="Loading"`; renders 8 absolutely-positioned pill blades at 45° increments. No `data-slot` attribute.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Notes |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `className` | `string` | — | Size via `size-*`, colour via `text-*`; all other div props pass through. |
|
|
20
|
+
|
|
21
|
+
## Variants & sizes
|
|
22
|
+
|
|
23
|
+
No variants and no size prop — scale with `className`, colour with text utilities.
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Spinner } from "@trading-game/design-intelligence-layer"
|
|
27
|
+
|
|
28
|
+
<Spinner /> {/* 16px, inherits text colour */}
|
|
29
|
+
<Spinner className="size-6" /> {/* 24px */}
|
|
30
|
+
<Spinner className="size-8 text-text-subtle-default" />
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Tokens
|
|
34
|
+
|
|
35
|
+
**Colour (semantic):** none hard-coded — blades are `currentColor`, so the spinner takes whatever semantic text token the context sets (e.g. white inside a primary button).
|
|
36
|
+
**Type (private):** none.
|
|
37
|
+
|
|
38
|
+
## Behaviour
|
|
39
|
+
|
|
40
|
+
- 8 blades animate `opacity 1 → 0.15` over 0.8s (`linear`), each offset by 0.1s, producing the rotating-fade effect with no transform animation.
|
|
41
|
+
- The `spinner-fade` keyframes ship once in `src/styles.css` (no per-instance `<style>` tags).
|
|
42
|
+
- `role="status"` + `aria-label="Loading"` announce it to screen readers; no visible text.
|
|
43
|
+
|
|
44
|
+
## Do / Don't
|
|
45
|
+
|
|
46
|
+
**Do**
|
|
47
|
+
- Size with `size-*` utilities and let `currentColor` pick up the surrounding ink — this is how it stays correct inside buttons of every variant.
|
|
48
|
+
- Keep the default 16px in inline/button contexts; step up (`size-6`, `size-8`) only for region-level loading.
|
|
49
|
+
|
|
50
|
+
**Don't**
|
|
51
|
+
- Don't hard-code a colour on the blades; set a semantic `text-*` token on the spinner or its parent instead.
|
|
52
|
+
- Don't use a Spinner where a Skeleton would preserve layout — spinners give no shape hint.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Stepper
|
|
2
|
+
|
|
3
|
+
Numeric amount entry with pill +/− buttons and a centered input, built entirely on Input Group; carries a fixed USDT suffix.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Trade and stake amount entry where the user nudges by a fixed step — the primary amount control in order tickets.
|
|
8
|
+
|
|
9
|
+
**When not:** For free-form numeric fields use Input. For picking along a visual scale use [Slider](./slider.md). For on-screen mobile digit entry pair with Numpad.
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
Composed from Input Group parts; the root is an `InputGroup` with `data-slot="stepper"` and `data-disabled` when disabled.
|
|
14
|
+
|
|
15
|
+
- Decrement addon — `InputGroupButton` (Secondary, `icon-xs`/`icon-sm`) with a Minus icon, `aria-label="Decrease value"`.
|
|
16
|
+
- Input — `InputGroupInput`, `type="text" inputMode="numeric" role="spinbutton"`, text-centered, native spin buttons stripped.
|
|
17
|
+
- Suffix — `InputGroupText` hard-coded to `USDT`.
|
|
18
|
+
- Increment addon — `InputGroupButton` with a Plus icon, `aria-label="Increase value"`.
|
|
19
|
+
|
|
20
|
+
## API
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Notes |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `value` | `number` | — | Controlled mode (`value !== undefined`). |
|
|
25
|
+
| `defaultValue` | `number` | `0` | Uncontrolled initial value, clamped to `[min, max]`. |
|
|
26
|
+
| `onValueChange` | `(value: number) => void` | — | Fires with the committed (clamped + rounded) value. |
|
|
27
|
+
| `min` / `max` | `number` | `-Infinity` / `Infinity` | Buttons disable at the bounds; omitted bounds drop the `aria-valuemin/max`. |
|
|
28
|
+
| `step` | `number` | `1` | Also sets display precision: decimal places of `step` drive `toFixed`. |
|
|
29
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | h-8 / h-10 / h-12 — the 32/40/48 rail; lg upgrades buttons to `icon-sm` and icons to `size-4`. |
|
|
30
|
+
| `disabled` | `boolean` | — | Disables input and both buttons. |
|
|
31
|
+
| `placeholder` / `id` / `name` / `aria-label` | `string` | — | Pass through to the input. |
|
|
32
|
+
| `className` | `string` | — | On the group; default width `w-32`. |
|
|
33
|
+
|
|
34
|
+
## Variants & sizes
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { Stepper } from "@trading-game/design-intelligence-layer"
|
|
38
|
+
|
|
39
|
+
<Stepper size="sm" defaultValue={10} step={5} min={0} max={100} />
|
|
40
|
+
<Stepper defaultValue={1} step={0.25} min={0} /> {/* md; shows 2 decimals from step 0.25... */}
|
|
41
|
+
<Stepper size="lg" value={amount} onValueChange={setAmount} min={0} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Tokens
|
|
45
|
+
|
|
46
|
+
**Colour (semantic):** inherited entirely from Input Group and the Secondary Button — no colour classes of its own. The group carries the input hairline/surface/focus tokens; the pill buttons carry Secondary button tokens.
|
|
47
|
+
**Type (private):** none of its own — the Input Group's `--input-*` ladder applies via `[data-slot="input-group"][data-size]` (sm 12/16, md 14/20, lg 16/24, regular).
|
|
48
|
+
|
|
49
|
+
## Behaviour
|
|
50
|
+
|
|
51
|
+
- Controlled when `value` is set, otherwise internal state seeded from `defaultValue` clamped to bounds.
|
|
52
|
+
- Every commit (button click, blur, Enter) clamps to `[min, max]`, then rounds: `toFixed(decimalPlaces(step))` when the step is fractional, `Math.round` otherwise. `onValueChange` receives the committed number.
|
|
53
|
+
- While focused, the raw typed string is preserved; on blur/Enter an unparseable string reverts to the last committed value.
|
|
54
|
+
- ArrowUp / ArrowDown increment / decrement by `step` (default prevented).
|
|
55
|
+
- Decrement disables at `value <= min`, increment at `value >= max`.
|
|
56
|
+
- The `USDT` suffix is hard-coded — there is no currency prop.
|
|
57
|
+
|
|
58
|
+
## Do / Don't
|
|
59
|
+
|
|
60
|
+
**Do**
|
|
61
|
+
- Set real `min`/`max` so the buttons self-disable at the bounds and `aria-valuemin/max` are exposed.
|
|
62
|
+
- Choose `step` to match the asset's precision — display precision follows the step's decimal places exactly.
|
|
63
|
+
- Keep the control on the rail via `size`; widen with `className` (default is `w-32`).
|
|
64
|
+
|
|
65
|
+
**Don't**
|
|
66
|
+
- Don't compose your own +/− input for amounts — this is the canonical Input Group composition.
|
|
67
|
+
- Don't try to relabel the suffix via props; it is fixed to USDT in the source.
|
|
68
|
+
- Don't listen to keystrokes for the value — only committed values reach `onValueChange`.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Switch
|
|
2
|
+
|
|
3
|
+
Binary on/off toggle (Radix Switch) with a sliding white thumb.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Settings that take effect immediately — notifications on/off, dark mode, one-click trading.
|
|
8
|
+
|
|
9
|
+
**When not:** For options submitted with a form use Checkbox. For selecting one of several visible options use [Toggle Group](./toggle-group.md).
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Switch` — Radix root, `rounded-full` pill track with 3px inner padding; `data-slot="switch"`, `data-size`, `data-state`.
|
|
14
|
+
- Thumb — internal `rounded-full` static-white circle; `data-slot="switch-thumb"`.
|
|
15
|
+
|
|
16
|
+
## API
|
|
17
|
+
|
|
18
|
+
| Prop | Type | Default | Notes |
|
|
19
|
+
| --- | --- | --- | --- |
|
|
20
|
+
| `size` | `"sm" \| "default"` | `"default"` | default: 24px tall (`h-6 w-11`, 18px thumb); sm: 20px tall (`h-5 w-9`, 14px thumb). |
|
|
21
|
+
| `checked` / `defaultChecked` | `boolean` | — | Controlled / uncontrolled. |
|
|
22
|
+
| `onCheckedChange` | `(checked: boolean) => void` | — | Radix callback. |
|
|
23
|
+
| `disabled` | `boolean` | — | `cursor-not-allowed opacity-24` (the system disabled strength). |
|
|
24
|
+
|
|
25
|
+
## Variants & sizes
|
|
26
|
+
|
|
27
|
+
Two sizes, one visual style.
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Switch } from "@trading-game/design-intelligence-layer"
|
|
31
|
+
|
|
32
|
+
<Switch defaultChecked />
|
|
33
|
+
<Switch size="sm" checked={enabled} onCheckedChange={setEnabled} />
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Tokens
|
|
37
|
+
|
|
38
|
+
**Colour (semantic):** `bg-background-brand-default` (checked track), `bg-border-default-default` (unchecked track), `bg-background-static-white` (thumb — static in both themes), `ring-ring-focus-strong` at 3px + `border-ring-focus-default` (focus).
|
|
39
|
+
**Type (private):** none — the component renders no text; pair it with `Label`.
|
|
40
|
+
|
|
41
|
+
## Behaviour
|
|
42
|
+
|
|
43
|
+
- The thumb translates `calc(100% + 1px)` when checked; movement is a pure transform transition, `ring-0` and no shadow on the thumb.
|
|
44
|
+
- The track border is transparent by default and only colours on focus-visible.
|
|
45
|
+
- Fully controlled or uncontrolled via the Radix props.
|
|
46
|
+
|
|
47
|
+
## Do / Don't
|
|
48
|
+
|
|
49
|
+
**Do**
|
|
50
|
+
- Use the default 24px size on the standard rail; reserve `sm` (20px) for dense rows and table cells.
|
|
51
|
+
- Pair every Switch with a visible `Label` or an `aria-label` — it has no text of its own.
|
|
52
|
+
- Apply the change immediately on toggle; a switch that needs a Save button should be a Checkbox.
|
|
53
|
+
|
|
54
|
+
**Don't**
|
|
55
|
+
- Don't tint the thumb — it is `background-static-white` in both themes by design.
|
|
56
|
+
- Don't add shadows to track or thumb; the checked brand fill is the entire state signal.
|
|
57
|
+
- Don't invent intermediate sizes; sm and default are the only two.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Table
|
|
2
|
+
|
|
3
|
+
Data table with hairline row borders, hover glaze rows, and a solid-surface footer.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Tabular data the user scans and compares — positions, order history, market lists.
|
|
8
|
+
|
|
9
|
+
**When not:** For mixed-media row layouts use Item or Card lists. For key-value pairs inside a card, a simple grid is lighter.
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Table` — `<table>` wrapped in an `overflow-x-auto` container; carries `type-table`; `data-slot="table"` (wrapper: `data-slot="table-container"`).
|
|
14
|
+
- `TableHeader` — `<thead>`; its rows get the bottom hairline; `data-slot="table-header"`.
|
|
15
|
+
- `TableBody` — `<tbody>`; strips the border from the last row; `data-slot="table-body"`.
|
|
16
|
+
- `TableFooter` — `<tfoot>`; `type-table-head` weight on a solid secondary surface with a top hairline; `data-slot="table-footer"`.
|
|
17
|
+
- `TableRow` — hairline bottom border, hover glaze, selected fill via `data-state="selected"`; `data-slot="table-row"`.
|
|
18
|
+
- `TableHead` — `<th>`, `h-10 px-2`, left-aligned prominent ink; `data-slot="table-head"`.
|
|
19
|
+
- `TableCell` — `<td>`, `p-2`, middle-aligned, `whitespace-nowrap`; `data-slot="table-cell"`.
|
|
20
|
+
- `TableCaption` — subtle ink, `mt-4`; `data-slot="table-caption"`.
|
|
21
|
+
|
|
22
|
+
## API
|
|
23
|
+
|
|
24
|
+
All parts take their native element props plus `className` — no cva variants, no size props. Row selection styling is driven by `data-state="selected"` on `TableRow`.
|
|
25
|
+
|
|
26
|
+
## Variants & sizes
|
|
27
|
+
|
|
28
|
+
One density.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import {
|
|
32
|
+
Table, TableHeader, TableBody, TableFooter,
|
|
33
|
+
TableRow, TableHead, TableCell, TableCaption,
|
|
34
|
+
} from "@trading-game/design-intelligence-layer"
|
|
35
|
+
|
|
36
|
+
<Table>
|
|
37
|
+
<TableCaption>Open positions</TableCaption>
|
|
38
|
+
<TableHeader>
|
|
39
|
+
<TableRow>
|
|
40
|
+
<TableHead>Market</TableHead>
|
|
41
|
+
<TableHead>Size</TableHead>
|
|
42
|
+
</TableRow>
|
|
43
|
+
</TableHeader>
|
|
44
|
+
<TableBody>
|
|
45
|
+
<TableRow data-state="selected">
|
|
46
|
+
<TableCell>BTC/USDT</TableCell>
|
|
47
|
+
<TableCell>0.50</TableCell>
|
|
48
|
+
</TableRow>
|
|
49
|
+
</TableBody>
|
|
50
|
+
<TableFooter>
|
|
51
|
+
<TableRow>
|
|
52
|
+
<TableCell>Total</TableCell>
|
|
53
|
+
<TableCell>0.50</TableCell>
|
|
54
|
+
</TableRow>
|
|
55
|
+
</TableFooter>
|
|
56
|
+
</Table>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Tokens
|
|
60
|
+
|
|
61
|
+
**Colour (semantic):** `border-border-default-default` (row hairlines, footer top border), `bg-background-hover-default` (row hover glaze), `bg-background-brand-selected` (selected rows — the selection family, correct in both themes), `bg-background-secondary-surface` (footer surface), `text-text-prominent-default` (heads), `text-text-subtle-default` (caption).
|
|
62
|
+
**Type (private):**
|
|
63
|
+
|
|
64
|
+
| Token | Value |
|
|
65
|
+
| --- | --- |
|
|
66
|
+
| `--table-*` (cells, via `.type-table`) | 14 / regular (400) / 20 |
|
|
67
|
+
| `--table-head-font-weight` (heads and footer, via `.type-table-head`) | medium (500), same 14/20 metrics |
|
|
68
|
+
|
|
69
|
+
## Behaviour
|
|
70
|
+
|
|
71
|
+
- The root always renders inside a `relative w-full overflow-x-auto` container, so wide tables scroll horizontally instead of breaking the page.
|
|
72
|
+
- The last body row drops its border (`[&_tr:last-child]:border-0`); the footer re-introduces separation with its own top hairline and solid secondary fill.
|
|
73
|
+
- Hover glaze and selected fill apply per `TableRow`; selection state is your responsibility via `data-state="selected"`.
|
|
74
|
+
- Cells with a `[role=checkbox]` get `pr-0` and a 2px vertical nudge for optical alignment.
|
|
75
|
+
|
|
76
|
+
## Do / Don't
|
|
77
|
+
|
|
78
|
+
**Do**
|
|
79
|
+
- Keep heads at medium (500) via the built-in `type-table-head` — that is the header/body hierarchy.
|
|
80
|
+
- Use `data-state="selected"` for selected rows; the solid secondary surface is the selection treatment here (not the brand-selected family).
|
|
81
|
+
- Right-align numeric columns with `className="text-right"` on head and cell together.
|
|
82
|
+
|
|
83
|
+
**Don't**
|
|
84
|
+
- Don't bold cells or heads — medium is the table maximum; semibold 600 is the system-wide ceiling.
|
|
85
|
+
- Don't add zebra striping or shadows; hairlines plus hover glaze carry the row rhythm.
|
|
86
|
+
- Don't wrap `Table` in another horizontal scroll container — it ships its own.
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Tabs
|
|
2
|
+
|
|
3
|
+
Peer-view switcher (Radix Tabs) with a filled-pill default variant and a brand-underline line variant.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Switching between sibling views of equal weight — Buy/Sell, chart intervals, account sections.
|
|
8
|
+
|
|
9
|
+
**When not:** For navigation between routes use Navigation Menu or [Sidebar](./sidebar.md). For toggling values that stay on screen use [Toggle Group](./toggle-group.md).
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Tabs` — Radix root, `group/tabs`, column layout when horizontal; `data-slot="tabs"`, `data-orientation`.
|
|
14
|
+
- `TabsList` — the track; carries `data-variant` and `data-size` which drive trigger type via CSS; `data-slot="tabs-list"`.
|
|
15
|
+
- `TabsTrigger` — one tab; `data-slot="tabs-trigger"`, `data-icon-position`, `data-state`.
|
|
16
|
+
- `TabsContent` — the panel; `data-slot="tabs-content"`.
|
|
17
|
+
|
|
18
|
+
## API
|
|
19
|
+
|
|
20
|
+
### Tabs (root)
|
|
21
|
+
|
|
22
|
+
| Prop | Type | Default | Notes |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| `value` / `defaultValue` | `string` | — | Controlled / uncontrolled active tab. |
|
|
25
|
+
| `onValueChange` | `(value: string) => void` | — | Radix callback. |
|
|
26
|
+
| `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Vertical stacks the list and moves the line-variant underline to the right edge. |
|
|
27
|
+
|
|
28
|
+
### TabsList
|
|
29
|
+
|
|
30
|
+
| Prop | Type | Default | Notes |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| `variant` | `"default" \| "line"` | `"default"` | default: secondary-surface track with an active primary-surface pill; line: transparent track with a brand underline. |
|
|
33
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | sm `h-8 rounded-md p-[2px]`, md `h-10 rounded-lg p-1`, lg `h-12 rounded-xl p-1.5` — the 32/40/48 rail. |
|
|
34
|
+
|
|
35
|
+
### TabsTrigger
|
|
36
|
+
|
|
37
|
+
| Prop | Type | Default | Notes |
|
|
38
|
+
| --- | --- | --- | --- |
|
|
39
|
+
| `value` | `string` | — | Required tab identity. |
|
|
40
|
+
| `iconPosition` | `"inline" \| "top"` | `"inline"` | `top` stacks icon over label (`flex-col`, auto height). |
|
|
41
|
+
|
|
42
|
+
## Variants & sizes
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@trading-game/design-intelligence-layer"
|
|
46
|
+
|
|
47
|
+
<Tabs defaultValue="buy">
|
|
48
|
+
<TabsList size="md">
|
|
49
|
+
<TabsTrigger value="buy">Buy</TabsTrigger>
|
|
50
|
+
<TabsTrigger value="sell">Sell</TabsTrigger>
|
|
51
|
+
</TabsList>
|
|
52
|
+
<TabsContent value="buy">…</TabsContent>
|
|
53
|
+
<TabsContent value="sell">…</TabsContent>
|
|
54
|
+
</Tabs>
|
|
55
|
+
|
|
56
|
+
<Tabs defaultValue="1h">
|
|
57
|
+
<TabsList variant="line" size="sm">
|
|
58
|
+
<TabsTrigger value="1h">1H</TabsTrigger>
|
|
59
|
+
<TabsTrigger value="1d">1D</TabsTrigger>
|
|
60
|
+
</TabsList>
|
|
61
|
+
</Tabs>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Tokens
|
|
65
|
+
|
|
66
|
+
**Colour (semantic):** `bg-background-secondary-surface` (default track), `bg-background-primary-surface` (active pill), `text-text-subtle-default` (resting), `text-text-brand-selected` (active ink, both variants), `text-text-prominent-default` / `text-text-brand-hover` (hover ink — default / line variant), `bg-background-hover-default` (default-variant hover), `border-border-brand-selected` (line-variant underline via `after:bg-border-brand-selected`, 2px, offset 5px below), focus `border-ring-focus-default` + `ring-ring-focus-strong` at 3px.
|
|
67
|
+
**Type (private):**
|
|
68
|
+
|
|
69
|
+
| Token | Value |
|
|
70
|
+
| --- | --- |
|
|
71
|
+
| `--tabs-trigger-*-sm` | 12 / regular (400) / 16 |
|
|
72
|
+
| `--tabs-trigger-*-md` | 14 / regular (400) / 20 |
|
|
73
|
+
| `--tabs-trigger-*-lg` | 16 / regular (400) / 24 |
|
|
74
|
+
| `--tabs-trigger-active-font-weight` | semibold (600) |
|
|
75
|
+
|
|
76
|
+
The trigger type ladder is applied in CSS off the **list's** `data-size` (`[data-slot="tabs-list"][data-size=…] [data-slot="tabs-trigger"]`), and the active semibold via `[data-state="active"]` — because `.type-*` classes are plain CSS that Tailwind variants can't gate.
|
|
77
|
+
|
|
78
|
+
## Behaviour
|
|
79
|
+
|
|
80
|
+
- Active state: default variant lifts the trigger onto a primary-surface pill (`rounded-md`/`sm`/`lg` matching list size, no shadow); line variant keeps the background transparent and fades in the 2px brand underline (`after:opacity-100`).
|
|
81
|
+
- Active ink is `text-brand-selected` and the weight jumps to semibold 600 through the CSS data-state rule — no `font-*` utility involved.
|
|
82
|
+
- Sizing cascades from the list: triggers read the list's `data-size` for type, gap, radius, and padding; `iconPosition="top"` switches to a stacked column and lets the list grow (`has-[[data-icon-position=top]]:h-auto`).
|
|
83
|
+
- Vertical orientation makes triggers full-width/left-justified and moves the line underline to a 2px right-edge bar.
|
|
84
|
+
|
|
85
|
+
## Do / Don't
|
|
86
|
+
|
|
87
|
+
**Do**
|
|
88
|
+
- Set `size` on `TabsList` only — triggers inherit everything from the list's `data-size`.
|
|
89
|
+
- Use `variant="line"` where the track would fight a busy surface; the underline carries selection alone.
|
|
90
|
+
- Keep tab labels to one or two words; triggers are `whitespace-nowrap`.
|
|
91
|
+
|
|
92
|
+
**Don't**
|
|
93
|
+
- Don't apply `font-semibold` to the active trigger — the `data-state="active"` CSS rule already does, and it beats `font-*` utilities.
|
|
94
|
+
- Don't add shadows to the active pill; both variants explicitly force `shadow-none`.
|
|
95
|
+
- Don't mix `iconPosition` values within one list — the whole list stretches to the tallest trigger.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Textarea
|
|
2
|
+
|
|
3
|
+
Multi-line text field mirroring the md Input treatment, with content-driven auto-sizing.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Free-form text longer than one line — notes, feedback, support messages.
|
|
8
|
+
|
|
9
|
+
**When not:** For single-line values use Input. For numeric amounts use [Stepper](./stepper.md).
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Textarea` — a single `<textarea>` with hairline border, solid primary surface, and `type-textarea`; `data-slot="textarea"`.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Notes |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `className` | `string` | — | All native `<textarea>` props pass through (`value`, `onChange`, `disabled`, `aria-invalid`, `rows`, …). |
|
|
20
|
+
|
|
21
|
+
No variants, no size prop — one md-equivalent density.
|
|
22
|
+
|
|
23
|
+
## Variants & sizes
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
import { Textarea } from "@trading-game/design-intelligence-layer"
|
|
27
|
+
|
|
28
|
+
<Textarea placeholder="Add a note…" />
|
|
29
|
+
<Textarea aria-invalid value={note} onChange={(e) => setNote(e.target.value)} />
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Tokens
|
|
33
|
+
|
|
34
|
+
**Colour (semantic):** `border-border-default-default` (hairline), `bg-background-primary-surface` (solid surface), `text-text-prominent-default` (value), `text-text-subtle-default` (placeholder), `bg-background-brand-default` + `text-text-on-brand-static` (text selection), `border-ring-focus-default` + `ring-ring-focus-soft` at 3px (soft text-field focus ring), `border-border-error-default` + `ring-error-soft` ring (invalid).
|
|
35
|
+
**Type (private):**
|
|
36
|
+
|
|
37
|
+
| Token | Value |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `--textarea-*` (via `.type-textarea`) | 14 / regular (400) / 20 |
|
|
40
|
+
|
|
41
|
+
## Behaviour
|
|
42
|
+
|
|
43
|
+
- `field-sizing-content` grows the field with its content, floored at `min-h-16` (64px) — no JS auto-resize needed.
|
|
44
|
+
- Focus uses the soft text-field ring (`ring-focus-soft`), matching Input, not the 50% control ring.
|
|
45
|
+
- Error state is attribute-driven: set `aria-invalid` and the border/ring switch to the error tokens.
|
|
46
|
+
- Disabled renders `cursor-not-allowed opacity-24`.
|
|
47
|
+
|
|
48
|
+
## Do / Don't
|
|
49
|
+
|
|
50
|
+
**Do**
|
|
51
|
+
- Drive errors through `aria-invalid` (typically via Field) so styling and a11y stay in sync.
|
|
52
|
+
- Let `field-sizing-content` handle height; constrain with `max-h-*` + your own overflow if needed.
|
|
53
|
+
- Pair with `Label`; the placeholder is not a label.
|
|
54
|
+
|
|
55
|
+
**Don't**
|
|
56
|
+
- Don't recreate the focus ring at 50% opacity — text fields take the soft `ring-focus-soft` ring.
|
|
57
|
+
- Don't override the surface with tints or shadows; inputs are hairline + solid surface.
|
|
58
|
+
- Don't bump the type — 14/regular/20 matches the md Input exactly.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Toast
|
|
2
|
+
|
|
3
|
+
Transient notification system — a themed wrapper around `sonner` on the inverse surface.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Short-lived confirmations and alerts that need no reply — order placed, copied to clipboard, connection lost.
|
|
8
|
+
|
|
9
|
+
**When not:** For persistent contextual status use [Section Message](./section-message.md). For blocking decisions use Alert Dialog.
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Toaster` — the mount point (place once at the app root); wraps sonner's `<Sonner>` with theme, position, icons, and token styling. No `data-slot` attribute (markup is sonner's).
|
|
14
|
+
- `toast` — re-exported sonner imperative API (`toast(...)`, `toast.success(...)`, `toast.error(...)`, …).
|
|
15
|
+
|
|
16
|
+
## API
|
|
17
|
+
|
|
18
|
+
### Toaster
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Default | Notes |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| ...props | sonner `ToasterProps` | — | All pass through; the wrapper presets the values below (overridable). |
|
|
23
|
+
|
|
24
|
+
Preset defaults: `theme` from `next-themes` (`useTheme`, fallback `"system"`); `position` `"top-center"` on mobile / `"bottom-right"` on desktop (`useIsMobile`); `visibleToasts` 1 mobile / 3 desktop; `duration` 4000ms; status icons CircleCheck / Info / TriangleAlert / OctagonX / spinning Loader2, all `size-4`.
|
|
25
|
+
|
|
26
|
+
## Variants & sizes
|
|
27
|
+
|
|
28
|
+
No cva variants — severity comes from the `toast.*` method used.
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
import { Toaster, toast } from "@trading-game/design-intelligence-layer"
|
|
32
|
+
|
|
33
|
+
// once, at the app root
|
|
34
|
+
<Toaster />
|
|
35
|
+
|
|
36
|
+
// anywhere
|
|
37
|
+
toast.success("Order placed")
|
|
38
|
+
toast.error("Deposit failed", {
|
|
39
|
+
action: { label: "Retry", onClick: retry },
|
|
40
|
+
})
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Tokens
|
|
44
|
+
|
|
45
|
+
**Colour (semantic, applied via CSS variables / class overrides):** `--background-inverse-surface` (toast surface, borderless), `--text-prominent-inverse` (title), `--text-subtle-inverse` (description — a real mixed ink, AA on the inverse surface), `--background-static-white` + `--text-brand-container-static` (action button — static white fill with brand-container ink in both themes), `--radius` (corner radius).
|
|
46
|
+
**Type (private):** none of its own — title is `font-display` semibold, description `font-body` regular, set through sonner `classNames`.
|
|
47
|
+
|
|
48
|
+
## Behaviour
|
|
49
|
+
|
|
50
|
+
- Renders on the inverse surface: dark card on the light theme, light card on dark — with no border and no shadow class of its own.
|
|
51
|
+
- Position and stack depth adapt to viewport: mobile shows a single toast top-center; desktop stacks up to three bottom-right.
|
|
52
|
+
- Theme sync comes from `next-themes`, so the app must be inside a `ThemeProvider` for correct light/dark toasts.
|
|
53
|
+
- Every toast auto-dismisses after 4 seconds unless overridden per call.
|
|
54
|
+
- All sonner options (promise toasts, custom duration, `richColors` off by default) remain available through the `toast` API and `Toaster` props.
|
|
55
|
+
|
|
56
|
+
## Do / Don't
|
|
57
|
+
|
|
58
|
+
**Do**
|
|
59
|
+
- Mount exactly one `Toaster` at the root; call `toast.*` from anywhere.
|
|
60
|
+
- Keep messages to one line plus an optional short description; 4 seconds is the reading budget.
|
|
61
|
+
- Use the built-in action slot for the follow-up ("Retry", "Undo") — it renders as the static-white/brand-ink button.
|
|
62
|
+
|
|
63
|
+
**Don't**
|
|
64
|
+
- Don't restyle toasts onto ordinary surfaces — the inverse surface is what separates them from page content without a shadow.
|
|
65
|
+
- Don't use toasts for errors requiring user action to resolve; those belong in a [Section Message](./section-message.md) next to the failed control.
|
|
66
|
+
- Don't stack meaning into colour alone — the icon set (check/info/triangle/octagon) carries severity.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Toggle Group
|
|
2
|
+
|
|
3
|
+
A set of related toggles (Radix ToggleGroup) sharing size/variant, rendered joined by default.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- Exclusive or multi-select among a few peer options that stay visible — chart interval (1m/5m/1h), order side, view density.
|
|
8
|
+
|
|
9
|
+
**When not:** For switching content panels use [Tabs](./tabs.md). For a single independent control use [Toggle](./toggle.md). For 5+ options use [Select](./select.md).
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `ToggleGroup` — Radix root; publishes `variant`/`size`/`spacing` through context and sets `--gap`; `data-slot="toggle-group"` with `data-variant`, `data-size`, `data-spacing`.
|
|
14
|
+
- `ToggleGroupItem` — one segment, styled by the shared `toggleVariants` from Toggle; `data-slot="toggle-group-item"`, `data-state="on|off"`.
|
|
15
|
+
|
|
16
|
+
## API
|
|
17
|
+
|
|
18
|
+
### ToggleGroup
|
|
19
|
+
|
|
20
|
+
| Prop | Type | Default | Notes |
|
|
21
|
+
| --- | --- | --- | --- |
|
|
22
|
+
| `type` | `"single" \| "multiple"` | — | Radix prop; required. |
|
|
23
|
+
| `variant` | `"default" \| "outline"` | — | Inherited by every item via context. |
|
|
24
|
+
| `size` | `"default" \| "sm" \| "lg"` | — | Same rail as Toggle (36/32/40px). |
|
|
25
|
+
| `spacing` | `number` | `0` | Tailwind spacing units between items; `0` = joined segments. |
|
|
26
|
+
| `value` / `defaultValue`, `onValueChange` | Radix props | — | `string` for single, `string[]` for multiple. |
|
|
27
|
+
|
|
28
|
+
### ToggleGroupItem
|
|
29
|
+
|
|
30
|
+
| Prop | Type | Default | Notes |
|
|
31
|
+
| --- | --- | --- | --- |
|
|
32
|
+
| `value` | `string` | — | Required item identity. |
|
|
33
|
+
| `variant` / `size` | as Toggle | — | Context wins: group-level values override per-item ones. |
|
|
34
|
+
|
|
35
|
+
## Variants & sizes
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { ToggleGroup, ToggleGroupItem } from "@trading-game/design-intelligence-layer"
|
|
39
|
+
|
|
40
|
+
// joined segments (default)
|
|
41
|
+
<ToggleGroup type="single" variant="outline" defaultValue="1h">
|
|
42
|
+
<ToggleGroupItem value="1m">1M</ToggleGroupItem>
|
|
43
|
+
<ToggleGroupItem value="5m">5M</ToggleGroupItem>
|
|
44
|
+
<ToggleGroupItem value="1h">1H</ToggleGroupItem>
|
|
45
|
+
</ToggleGroup>
|
|
46
|
+
|
|
47
|
+
// spaced multiple-select
|
|
48
|
+
<ToggleGroup type="multiple" spacing={2} size="sm">
|
|
49
|
+
<ToggleGroupItem value="ema">EMA</ToggleGroupItem>
|
|
50
|
+
<ToggleGroupItem value="vol">VOL</ToggleGroupItem>
|
|
51
|
+
</ToggleGroup>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Tokens
|
|
55
|
+
|
|
56
|
+
**Colour (semantic):** everything from Toggle (`bg-background-hover-default` on-fill/hover, `text-text-brand-selected` on-ink, `border-border-prominent-default` outline), plus `border-border-brand-selected` on an on-segment's border in joined outline groups.
|
|
57
|
+
**Type (private):** shared `--toggle-*` family — 14 / medium (500) / 20.
|
|
58
|
+
|
|
59
|
+
## Behaviour
|
|
60
|
+
|
|
61
|
+
- `spacing={0}` (default) joins segments: items lose their own radius (`rounded-none`), the first/last regain `rounded-l-md`/`rounded-r-md`, and outline items share borders by dropping `border-l` on all but the first.
|
|
62
|
+
- In a joined outline group, an on segment restores its left border and colours its full border `border-border-brand-selected`, so the selected segment reads as outlined in brand.
|
|
63
|
+
- Group-level `variant`/`size` override per-item props through context (`context.variant || variant`).
|
|
64
|
+
- Items are `w-auto min-w-0 shrink-0 px-3` and raise `z-10` on focus so the ring is never clipped by neighbours.
|
|
65
|
+
- Any positive `spacing` separates the items and each keeps its own `rounded-md` shape.
|
|
66
|
+
|
|
67
|
+
## Do / Don't
|
|
68
|
+
|
|
69
|
+
**Do**
|
|
70
|
+
- Keep the default `spacing={0}` for segmented-control semantics; use spacing only when items act as independent toggles.
|
|
71
|
+
- Set `variant`/`size` on the group once; per-item overrides are ignored when the group sets them.
|
|
72
|
+
- Use `type="single"` with a `defaultValue` for interval/side pickers so one option is always on.
|
|
73
|
+
|
|
74
|
+
**Don't**
|
|
75
|
+
- Don't give on-segments a solid brand fill — selection is glaze + `text-brand-selected` ink, with `border-brand-selected` in joined outline groups.
|
|
76
|
+
- Don't mix sizes inside one group; the shared border geometry assumes uniform height.
|
|
77
|
+
- Don't use a joined group for navigation — that is [Tabs](./tabs.md).
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Toggle
|
|
2
|
+
|
|
3
|
+
Two-state pressable button (Radix Toggle) whose on-state uses the hover glaze plus brand-selected ink.
|
|
4
|
+
|
|
5
|
+
## When to use
|
|
6
|
+
|
|
7
|
+
- A single on/off tool control — bold in an editor, favourite a market, show/hide an overlay.
|
|
8
|
+
|
|
9
|
+
**When not:** For exclusive or multiple selection across several options use [Toggle Group](./toggle-group.md). For settings that apply immediately use [Switch](./switch.md).
|
|
10
|
+
|
|
11
|
+
## Anatomy
|
|
12
|
+
|
|
13
|
+
- `Toggle` — Radix root styled by `toggleVariants` (cva, exported and reused by Toggle Group); `data-slot="toggle"`, `data-state="on|off"`.
|
|
14
|
+
|
|
15
|
+
## API
|
|
16
|
+
|
|
17
|
+
| Prop | Type | Default | Notes |
|
|
18
|
+
| --- | --- | --- | --- |
|
|
19
|
+
| `variant` | `"default" \| "outline"` | `"default"` | default is transparent at rest; outline adds `border-border-prominent-default`. |
|
|
20
|
+
| `size` | `"default" \| "sm" \| "lg"` | `"default"` | default `h-10 min-w-10 px-2.5` (40px), sm `h-8 min-w-8 px-1.5` (32px), lg `h-12 min-w-12 px-3` (48px) — on the 32/40/48 rail. |
|
|
21
|
+
| `pressed` / `defaultPressed` | `boolean` | — | Controlled / uncontrolled. |
|
|
22
|
+
| `onPressedChange` | `(pressed: boolean) => void` | — | Radix callback. |
|
|
23
|
+
|
|
24
|
+
## Variants & sizes
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
import { Toggle } from "@trading-game/design-intelligence-layer"
|
|
28
|
+
import { Star } from "lucide-react"
|
|
29
|
+
|
|
30
|
+
<Toggle aria-label="Favourite"><Star /></Toggle>
|
|
31
|
+
<Toggle variant="outline" size="sm">1H</Toggle>
|
|
32
|
+
<Toggle size="lg" pressed={on} onPressedChange={setOn}>Overlay</Toggle>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Tokens
|
|
36
|
+
|
|
37
|
+
**Colour (semantic):** `bg-background-hover-default` (hover fill, and the on-state fill — the glaze does double duty), `text-text-prominent-default` (hover ink, outline resting ink), `text-text-brand-selected` (on-state ink), `border-border-prominent-default` (outline variant border; on-state sets `border-transparent`), focus `border-ring-focus-default` + `ring-ring-focus-strong` at 3px, `border-border-error-default` + `ring-error-soft` ring (aria-invalid).
|
|
38
|
+
**Type (private):**
|
|
39
|
+
|
|
40
|
+
| Token | Value |
|
|
41
|
+
| --- | --- |
|
|
42
|
+
| `--toggle-*` (via `.type-toggle`) | 14 / medium (500) / 20 |
|
|
43
|
+
|
|
44
|
+
## Behaviour
|
|
45
|
+
|
|
46
|
+
- On-state = `bg-background-hover-default` fill + `text-text-brand-selected` ink + transparent border. There is no separate pressed surface token — the hover glaze is reused deliberately.
|
|
47
|
+
- Radius is `rounded-md` (8px, the row radius); icons default to `size-4`.
|
|
48
|
+
- Disabled: `pointer-events-none opacity-24`.
|
|
49
|
+
|
|
50
|
+
## Do / Don't
|
|
51
|
+
|
|
52
|
+
**Do**
|
|
53
|
+
- Give icon-only toggles an `aria-label`.
|
|
54
|
+
- Use `outline` when the toggle sits on a busy surface and needs a resting boundary.
|
|
55
|
+
- Let the exported `toggleVariants` style any custom toggle-like control instead of re-deriving classes.
|
|
56
|
+
|
|
57
|
+
**Don't**
|
|
58
|
+
- Don't give the on-state a solid brand fill — the system on-state is glaze + brand-selected ink only.
|
|
59
|
+
- Don't exceed medium 500 for the label; the type token is fixed at 14/medium/20.
|
|
60
|
+
- Don't use a Toggle where the action navigates or submits — that is a Button.
|