@trading-game/design-intelligence-layer 1.0.1 → 1.0.2

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.
@@ -52,7 +52,7 @@ import { Home, LineChart, User } from "lucide-react"
52
52
 
53
53
  ## Tokens
54
54
 
55
- **Colour (chrome family — see the Colors page's Chrome group):** `background-chrome-default` (heavy near-solid frost: white 78% light / navy 72% dark, with `blur(20px) saturate(1.4)`), `border-chrome-default`, `shadow-chrome-default` (the one sanctioned shadow — floating chrome over scrolling content), `text-chrome-subtle` (idle ink, resolves through `text-subtle-default`), `text-chrome-selected` (blue-600 light / white dark), `background-chrome-selected` (the bubble's layered top-lit gloss over `background-brand-selected` — paint via `background:`), `chrome-highlight-default` (the bubble's 1px inset specular edge — material rendering, not elevation).
55
+ **Colour (chrome family — see the Colors page's Chrome group):** `background-chrome-default` (heavy near-solid frost: white 78% light / navy 72% dark, with `blur(20px) saturate(1.4)`), `border-chrome-default`, `shadow-chrome-default` (the one sanctioned shadow — floating chrome over scrolling content), `text-chrome-subtle` (idle ink, resolves through `text-subtle-default`), `text-chrome-selected` (blue-600 light / blue-100 dark), `background-chrome-selected` (the bubble's layered top-lit gloss — over `background-brand-selected` in light, over a richer blue-400 45% mix in dark so the blue glows on the dark bar — paint via `background:`), `chrome-highlight-default` (the bubble's 1px inset specular edge — material rendering, not elevation).
56
56
 
57
57
  **Motion:** travel timing reads the tokens at runtime — `--motion-travel-lead` (200ms leading edge), `--motion-travel-trail` (380ms trailing edge) on `--primitive-ease-spring`; the arriving icon pops on `--motion-pop` × `--primitive-ease-overshoot` (a CSS rule in styles.css — stacked arbitrary variants can't reach descendant svgs).
58
58
 
@@ -57,7 +57,7 @@ import {
57
57
 
58
58
  ## Tokens
59
59
 
60
- **Colour (semantic):** `text-text-subtle-default` (trail), `text-text-prominent-default` (link hover), `text-text-brand-default` (current page).
60
+ **Colour (semantic):** `text-text-subtle-default` (trail), `text-text-prominent-default` (link hover), `text-text-brand-selected` (current page).
61
61
  **Type (private):**
62
62
 
63
63
  | Token | Value |
@@ -13,7 +13,7 @@ Date and date-range picker built on react-day-picker, restyled with circular day
13
13
  - `Calendar` — configured `DayPicker`; root gets `data-slot="calendar"`, panel is `w-fit`, `p-3`, cell size `--cell-size` = 32px. Background goes transparent inside `card-content` and `popover-content`.
14
14
  - Nav — an absolute strip over the caption using `pointer-events-none`, with the arrow Buttons re-enabling their own hits so clicks pass through to the caption dropdowns beneath.
15
15
  - `CalendarDropdown` (internal `components.Dropdown`) — themed Select replacing the native month/year `<select>`; popper-positioned panel, `max-h-64`.
16
- - `CalendarDayButton` — exported day cell: a tertiary icon Button, circular, with today/selected/range modifiers via data attributes.
16
+ - `CalendarDayButton` — exported day cell: a tertiary icon Button, circular, with today/selected/range modifiers via data attributes (`data-today`, `data-selected-single`, `data-range-start`, `data-range-middle`, `data-range-end`).
17
17
 
18
18
  ## API
19
19
 
@@ -43,7 +43,7 @@ import { Calendar } from "@trading-game/design-intelligence-layer"
43
43
 
44
44
  ## Tokens
45
45
 
46
- **Colour (semantic):** `background-primary-surface` (panel), `text-text-prominent-default` (caption, days), `text-text-subtle-default` (weekdays, week numbers), `text-text-disabled-default` (outside/disabled days), `background-hover-default` (day hover glaze), `--background-brand-container` + `text-text-brand-container-static` (selected day and range band — the static icy recipe, painted via `[background:var(…)]`), `text-text-brand-default` + `background-brand-default` (today's number and dot), `icon-subtle-default` (dropdown chevron), `ring-focus-strong` (focus).
46
+ **Colour (semantic):** `background-primary-surface` (panel), `text-text-prominent-default` (caption, days), `text-text-subtle-default` (weekdays, week numbers), `text-text-disabled-default` (outside/disabled days), `background-hover-default` (day hover glaze), `background-brand-default` + `text-text-on-brand-static` (a picked date — selected single day and both range endpoints), `background-brand-selected` + `text-text-brand-selected` (the range band between the endpoints), `text-text-brand-selected` + `border-border-brand-selected` (today's ring and numeral), `icon-subtle-default` (dropdown chevron), `ring-focus-strong` (focus).
47
47
  **Type (private):**
48
48
 
49
49
  | Token | Value |
@@ -54,9 +54,9 @@ import { Calendar } from "@trading-game/design-intelligence-layer"
54
54
 
55
55
  ## Behaviour
56
56
 
57
- - Day cells are circular (tertiary icon Buttons). Selection paints the static icy `--background-brand-container` via arbitrary `background` — a `bg-*` utility cannot override it — with `text-brand-container-static` ink and semibold numerals.
58
- - Ranges: circular start/end (rounded-l/r-full, semibold), square middles (`rounded-none`), all on the same icy band; first/last cells in a row re-round via row selectors.
59
- - Today (unselected) keeps the regular cell but takes brand ink, semibold, and a 4px brand dot centred below the numeral.
57
+ - Day cells are circular (tertiary icon Buttons). A **picked date** — the selected single day, or either range endpoint — is a solid `background-brand-default` disc with `text-on-brand-static` ink and semibold numerals. Single and endpoint share one recipe deliberately: they mean the same thing, so they must not diverge.
58
+ - Ranges: solid discs at both endpoints, square middles (`rounded-none`) on the quieter `background-brand-selected` band with `text-brand-selected` ink; first/last cells in a row re-round via row selectors. The band rides `brand-selected`, not the static `brand-container`, because that token is one value in both themes and reads as a pale strip on the dark canvas.
59
+ - Today (unselected) keeps the regular cell but takes brand ink, semibold, and a 1.5px `border-brand-selected` ring. The ring — not a heavier weight — is what makes today legible, since components cap at semibold 600 and 600-vs-400 doesn't read at this size.
60
60
  - `CalendarDropdown` replaces the native selects with the themed Select: `position="popper"` content so the panel scrolls in place, and fixed trigger widths — `w-20` for months, `w-24` for years — so the `w-fit` panel never resizes between "Jan" and "Sep" or 999 and 2026.
61
61
  - The nav strip overlays the caption with `pointer-events-none`; only the arrow buttons (`pointer-events-auto`) catch clicks, so the dropdowns underneath stay clickable.
62
62
  - Focused days are programmatically focused (`modifiers.focused` → `ref.focus()`), and RTL flips the nav chevrons.
@@ -69,6 +69,6 @@ import { Calendar } from "@trading-game/design-intelligence-layer"
69
69
  - Host in a Popover or Card — the calendar background auto-clears there.
70
70
 
71
71
  **Don't**
72
- - Don't restyle selected days with `bg-*` utilities — the icy container is painted via `background` and will win.
73
- - Don't theme the selection per colour scheme by hand; `--background-brand-container` is the static recipe on purpose.
72
+ - Don't give the selected single day and the range endpoints different fills — one picked-date recipe covers both.
73
+ - Don't put the range band back on `--background-brand-container`; that token is static by design and can't carry a dark theme.
74
74
  - Don't square the day cells; circles (and square range middles) are the system shape.
@@ -5,7 +5,7 @@ The shared surface shell — card surface, hairline border, 18px radius, flat
5
5
  ## When to use
6
6
 
7
7
  - Grouping related content on a page: positions, game tiles, stats, settings sections.
8
- - `variant="frosted-on-brand"` for cards sitting over hero or game artwork.
8
+ - `variant="frosted"` for a tinted card on a plain page surface. `variant="frosted-on-brand"` for cards sitting over hero or game artwork.
9
9
 
10
10
  **When not:** For modal content use [Dialog](./dialog.md). For a painted promotional panel use [Banner](./banner.md).
11
11
 
@@ -25,7 +25,7 @@ The shared surface shell — card surface, hairline border, 18px radius, flat
25
25
 
26
26
  | Prop | Type | Default | Notes |
27
27
  | --- | --- | --- | --- |
28
- | `variant` | `"default" \| "frosted-on-brand"` | `"default"` | Glass = white-alpha-80 + blur-xl + white-alpha-64 hairline, static black ink, identical in both themes. |
28
+ | `variant` | `"default" \| "frosted" \| "frosted-on-brand"` | `"default"` | frosted = solid brand-selected tint + brand-selected ink, for plain page surfaces. frosted-on-brand = white-alpha-80 + blur-xl + white-alpha-64 hairline, static black ink, identical in both themes — for art/brand surfaces only. |
29
29
  | `interactive` | `boolean` | `false` | Desktop-only hover lift: `sm:hover:-translate-y-0.75`, disabled under `motion-reduce`. |
30
30
 
31
31
  All slots take `div` props plus `className`.
@@ -13,7 +13,7 @@ General-purpose modal on the shared card shell — dismissable, with an optional
13
13
  - `Dialog` / `DialogTrigger` / `DialogPortal` / `DialogClose` — Radix plumbing with `data-slot` names (`dialog`, `dialog-trigger`, `dialog-portal`, `dialog-close`).
14
14
  - `DialogOverlay` — scrim in `background-overlay-default`; `data-slot="dialog-overlay"`.
15
15
  - `DialogContent` — centred card: `rounded-2xl` (18px), hairline border, `p-6`, `gap-6`, `sm:max-w-lg`, no shadow; `data-slot="dialog-content"`. Includes the close X unless disabled.
16
- - Close X — 32px circle, top-right, subtle icon at 70% opacity, hover = full opacity + `background-hover-default` glaze.
16
+ - Close X — a [NavigationButton](./navigation-button.md) (`size="sm"`), top-right, wired to `DialogPrimitive.Close` via `asChild`.
17
17
  - `DialogHeader` — column, centred text, left at `sm`; `data-slot="dialog-header"`.
18
18
  - `DialogTitle` / `DialogDescription` — `type-dialog-title` / `-description`.
19
19
  - `DialogFooter` — reverse-stacked buttons, row right-aligned at `sm`; can append its own Close button; `data-slot="dialog-footer"`.
@@ -66,7 +66,7 @@ import {
66
66
 
67
67
  ## Tokens
68
68
 
69
- **Colour (semantic):** `background-overlay-default` (scrim), `background-primary-surface` (card), `border-border-default-default` (hairline), `text-prominent-default` (title), `text-subtle-default` (description), `icon-subtle-default` + `background-hover-default` (close X), `ring-focus-strong` (focus).
69
+ **Colour (semantic):** `background-overlay-default` (scrim), `background-primary-surface` (card), `border-border-default-default` (hairline), `text-prominent-default` (title), `text-subtle-default` (description), `ring-focus-strong` (focus). The close X inherits [NavigationButton](./navigation-button.md)'s own tokens.
70
70
  **Type (private):**
71
71
 
72
72
  | Token | Value |
@@ -77,7 +77,7 @@ import {
77
77
  ## Behaviour
78
78
 
79
79
  - Radix handles focus trap, Escape, and overlay dismissal; open/close animates fade + 95% zoom over `duration-base`.
80
- - The close X is circular (`rounded-full`, 32px) with the hover glaze — the icon-button rule applied to a built-in control; it stops rendering with `showCloseButton={false}`.
80
+ - The close X is a composed `NavigationButton` rather than a hand-rolled circle, so it tracks that component's resting tint, hover step, and focus ring automatically; it stops rendering with `showCloseButton={false}`.
81
81
  - `DialogFooter showCloseButton` gives you a standard Cancel/Close without wiring `DialogClose` manually.
82
82
  - The shell is flat: no shadow, `rounded-2xl` 18px, matching [Card](./card.md).
83
83
  - `CommandDialog` composes this component with `p-0` content.
@@ -62,7 +62,7 @@ import { InboxIcon } from "lucide-react"
62
62
  - `border-border-default-default` (dashed container border)
63
63
  - `bg-background-secondary-surface` + `text-icon-prominent-default` (icon tile)
64
64
  - `text-text-prominent-default` (title)
65
- - `text-text-subtle-default` (description), links hover to `text-text-brand-default`
65
+ - `text-text-subtle-default` (description), links hover to `text-text-brand-selected`
66
66
 
67
67
  **Type (private):**
68
68
  - `--empty-title-*`: 18 / semibold / 24
@@ -12,7 +12,7 @@ Segmented one-time-code entry — one visual slot per character over a single hi
12
12
 
13
13
  - `InputOTP` — wrapper around the `input-otp` library's `OTPInput`; owns `maxLength`, value, and the hidden real input. `data-slot="input-otp"`.
14
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`.
15
+ - `InputOTPSlot` — one character cell, 48px (`size-12`, the lg rail); renders the char and a fake blinking caret. `data-slot="input-otp-slot"`, `data-active`.
16
16
  - `InputOTPSeparator` — `role="separator"` dash (`MinusIcon`) between groups. `data-slot="input-otp-separator"`.
17
17
 
18
18
  ## API
@@ -32,7 +32,7 @@ Segmented one-time-code entry — one visual slot per character over a single hi
32
32
 
33
33
  ## Variants & sizes
34
34
 
35
- No variants; one 40px slot size.
35
+ No variants; one 48px slot size.
36
36
 
37
37
  ```tsx
38
38
  import {
@@ -65,7 +65,7 @@ import {
65
65
  - caret: `bg-text-prominent-default` (`animate-caret-blink`, 1s)
66
66
  - invalid (`aria-invalid`): `border-border-error-default` + `ring-ring-error-soft`, active or not
67
67
 
68
- **Type (private):** `--input-otp-*`: 14 / regular / 20 (`type-input-otp`).
68
+ **Type (private):** `--input-otp-*`: 20 / semibold / 28 (`type-input-otp`).
69
69
 
70
70
  ## Behaviour
71
71
 
@@ -76,7 +76,7 @@ import { BellIcon } from "lucide-react"
76
76
  ## Tokens
77
77
 
78
78
  **Colour (semantic):**
79
- - title `text-text-prominent-default`; description `text-text-subtle-default` (links underline, hover `text-text-brand-default`)
79
+ - title `text-text-prominent-default`; description `text-text-subtle-default` (links underline, hover `text-text-brand-selected`)
80
80
  - outline: `border-border-default-default`, hover `bg-background-hover-default`; muted: `bg-background-secondary-surface`
81
81
  - group divider: `bg-border-default-default`
82
82
  - focus: `border-ring-focus-default` + 3px `ring-ring-focus-strong` (control ring)
@@ -40,7 +40,7 @@ import { ArrowRightIcon } from "lucide-react"
40
40
  ## Tokens
41
41
 
42
42
  **Colour (semantic):**
43
- - brand: `text-text-brand-default`, hover `text-text-brand-hover` (ink darkens one step)
43
+ - brand: `text-text-brand-selected`, hover `text-text-brand-hover` (ink darkens one step)
44
44
  - on-brand: `text-text-on-brand-static`, hover `--primitive-white-alpha-80` (alpha primitive, sanctioned for on-brand surfaces)
45
45
  - focus: 3px `ring-ring-focus-strong`
46
46
 
@@ -20,7 +20,7 @@ Circular icon-only control for moving through UI — back arrows, carousel stepp
20
20
 
21
21
  | Prop | Type | Default | Notes |
22
22
  | --- | --- | --- | --- |
23
- | `variant` | `"default" \| "frosted-on-brand"` | `"default"` | default = transparent circle, hover fill; glass = frosted circle for art/hero/brand surfaces. |
23
+ | `variant` | `"default" \| "frosted-on-brand"` | `"default"` | default = tinted circle at rest, deeper tint on hover; glass = frosted circle for art/hero/brand surfaces. |
24
24
  | `size` | `"xs" \| "sm" \| "md" \| "lg"` | `"lg"` | xs 24px (16px icon), sm 32px (20px icon), md 40px (20px icon), lg 48px (24px icon). |
25
25
  | `asChild` | `boolean` | `false` | Render as `<a>` etc. via Radix Slot. |
26
26
 
@@ -42,7 +42,7 @@ import { ChevronLeftIcon } from "lucide-react"
42
42
  ## Tokens
43
43
 
44
44
  **Colour (semantic):**
45
- - default: transparent, `text-icon-prominent-default`, hover `bg-background-hover-default`
45
+ - default: `bg-background-hover-default` at rest, `text-icon-prominent-default`, hover steps to `bg-background-pressed-default` — the circle is always visibly tinted, not just on hover.
46
46
  - focus: 3px `ring-ring-focus-strong`
47
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
48
 
@@ -1,6 +1,18 @@
1
1
  # Progress
2
2
 
3
- Determinate progress bar — a brand wash track with a solid brand indicator.
3
+ Determinate progress in the design file's four shapes: the linear bar (with an optional setup-meter label row), pill-segment dots, and the circular ring — whose `xl` size is exactly the avatar verification ring's geometry.
4
+
5
+ ## Variants
6
+
7
+ | Variant | Notes |
8
+ | --- | --- |
9
+ | `bar` (default) | Sizes sm 4 / md 8 / lg 12; add `label` + `valueLabel` for the setup-meter row |
10
+ | `dots` | `segments` pill segments (24×4 at md; widths 16/24/32 by size), filled in value order |
11
+ | `ring` | Circular arc, rounded caps; sizes sm 24 / md 32 / lg 48 / xl 112 (`xl` = ProfilePhoto's verification-ring geometry); `children` render centred (countdown number, `--progress-value-*` type) |
12
+
13
+ Any variant can also **self-run**: pass `timer` (ms) + `direction` and the value animates smoothly — `down` is a countdown drain, `up` a countup fill.
14
+
15
+ **Tokens:** track `background-secondary-surface` (ring track: `border-default-default` hairline), fill `background-brand-default`, meter labels `--progress-label-*` (12/semibold), ring value `--progress-value-*` (14/semibold, tabular). Fill motion rides `duration-slow ease-linear` per the progress motion role.
4
16
 
5
17
  ## When to use
6
18
 
@@ -10,51 +22,56 @@ Determinate progress bar — a brand wash track with a solid brand indicator.
10
22
 
11
23
  ## Anatomy
12
24
 
13
- - `Progress` — Radix `Progress.Root` track: `h-2` (8px), `rounded-2xs`, overflow hidden. `data-slot="progress"`.
14
- - Indicator — full-size bar translated left by the remaining percentage. `data-slot="progress-indicator"`.
25
+ - `Progress` — the track (`bar`: Radix `Progress.Root`, rounded-full, overflow hidden; `dots`: segment row; `ring`: SVG arc pair). `data-slot="progress"`, `data-variant`.
26
+ - Fill — solid brand: the translated indicator (`data-slot="progress-indicator"`), the filled dots (`data-slot="progress-dot"`), or the dashoffset arc (`data-slot="progress-ring-fill"`).
15
27
 
16
28
  ## API
17
29
 
18
- ### Progress
19
-
20
30
  | Prop | Type | Default | Notes |
21
31
  | --- | --- | --- | --- |
22
- | `value` | `number \| null` | — | 0–100. `null`/`undefined` renders as 0 (indicator fully translated out). |
23
-
24
- Other Radix `Progress.Root` props (`max`, `getValueLabel`, …) pass through; note the fill math reads `value` directly against 100.
25
-
26
- ## Variants & sizes
27
-
28
- No variants; one 8px bar. Width is fluid (`w-full`).
32
+ | `value` | `number \| null` | — | 0–100; clamped. Ignored while `timer` runs. |
33
+ | `variant` | `"bar" \| "dots" \| "ring"` | `"bar"` | |
34
+ | `size` | `"sm" \| "md" \| "lg"` (+ `"xl"` ring) | `"md"` | |
35
+ | `label` / `valueLabel` | `ReactNode` | — | bar only — the setup-meter row. |
36
+ | `segments` | `number` | `5` | dots only. |
37
+ | `timer` | `number` (ms) | — | Self-running mode: the value animates smoothly over `timer` ms. Remount (change `key`) to restart. |
38
+ | `direction` | `"down" \| "up"` | `"down"` | Timer direction — `down` drains 100 → 0 (countdown), `up` fills 0 → 100 (countup). |
39
+ | `onTimerEnd` | `() => void` | — | Fires once when the timer finishes. |
40
+ | `children` | `ReactNode \| (pct) => ReactNode` | — | ring only — centred content. Pass a function to render from the live value (the countdown number). |
29
41
 
30
42
  ```tsx
31
43
  import { Progress } from "@trading-game/design-intelligence-layer"
32
44
 
33
45
  <Progress value={66} />
34
- <Progress value={verifiedSteps * 25} className="max-w-xs" />
46
+ <Progress value={40} label="Account setup" valueLabel="2 of 5" />
47
+ <Progress variant="dots" segments={5} value={40} />
48
+ <Progress variant="ring" size="lg" value={62} />
49
+
50
+ // 5s countdown ring with a live seconds number — remount with `key` to restart
51
+ <Progress variant="ring" size="lg" timer={5000} direction="down" onTimerEnd={settle}>
52
+ {(pct) => Math.ceil((pct / 100) * 5)}
53
+ </Progress>
35
54
  ```
36
55
 
37
56
  ## Tokens
38
57
 
39
- **Colour (semantic):**
40
- - track: `bg-background-secondary-surface` — the shared track spec (h-2, rounded-full), same as Slider
41
- - indicator: `bg-background-brand-default`, solid
58
+ **Colour (semantic):** track `background-secondary-surface` (ring track: `border-default-default` hairline), fill `background-brand-default`, ring value ink `text-prominent-default`, meter label inks `text-prominent-default` / `text-subtle-default`.
42
59
 
43
- **Type (private):** none.
60
+ **Type (private):** `--progress-label-*` (12 / semibold / 16), `--progress-value-*` (14 / semibold / 20, tabular).
44
61
 
45
62
  ## Behaviour
46
63
 
47
- - The indicator is always full-width and is moved with `translateX(-(100 - value)%)`, so value changes animate smoothly via `transition-all`.
48
- - `value || 0` means null/undefined/0 all show an empty bar — there is no indeterminate animation in this component.
49
- - Radix provides the `progressbar` role and aria value attributes.
64
+ - One geometry, three drivers: pass `value` for real progress, or `timer` + `direction` for time (countdown drain / countup fill) — how it's driven is the consumer's choice.
65
+ - `value` changes ease on `duration-slow ease-linear` (the progress motion role); in timer mode the animation is frame-driven and the CSS transition is off.
66
+ - At 0 the ring paints no arc at all — no stray round-cap dot at 12 o'clock.
67
+ - The `progressbar` role and aria value attributes are on every variant.
50
68
 
51
69
  ## Do / Don't
52
70
 
53
71
  **Do**
54
- - Keep the track as the 20% brand wash — track and fill are the same hue by design, separated by alpha.
55
- - Constrain width with layout (`max-w-*`) rather than restyling the bar.
72
+ - Constrain width with layout (`max-w-*`) rather than restyling the track.
73
+ - Use the `children` render function for countdown numbers so the figure stays in sync with the arc.
56
74
 
57
75
  **Don't**
58
- - Don't repaint the pair with grey tracks or gradient fills; brand wash + solid brand is the recipe.
59
- - Don't fake indeterminate state by animating `value`; use Spinner for unknown durations.
60
- - Don't change the 8px height or `rounded-2xs` radius per screen; it is a fixed primitive.
76
+ - Don't repaint tracks or fills; secondary-surface track + solid brand fill is the recipe.
77
+ - Don't use a timer for unknown-length waits — that's [Spinner](./spinner.md) or [Skeleton](./skeleton.md).
@@ -55,7 +55,7 @@ import {
55
55
 
56
56
  **Colour (semantic):**
57
57
  - rest: `border-border-default-default` hairline on `bg-background-primary-surface`
58
- - checked: `border-background-brand-default` ring + `fill-background-brand-default` dot (surface stays primary — no filled disc)
58
+ - checked: `border-border-brand-selected` ring + `fill-background-brand-default` dot (surface stays primary — no filled disc)
59
59
  - focus: `border-ring-focus-default` + 3px `ring-ring-focus-strong` (control ring)
60
60
  - error (`aria-invalid`): `border-border-error-default` + `ring-ring-error-soft`
61
61
 
@@ -7,7 +7,7 @@ Single-choice dropdown (Radix Select) with a form-input trigger and a menu-style
7
7
  - Choosing one value from 5–15 options inside a form — account currency, leverage, market.
8
8
  - When the current value must read like an input, sitting on the 32/40/48 field rail.
9
9
 
10
- **When not:** For 2–4 always-visible options use [Toggle Group](./toggle-group.md) or Tabs. For command-style search over long lists use Command. For native mobile pickers use Native Select.
10
+ **When not:** For 2–4 always-visible options use [Toggle Group](./toggle.md) or Tabs. For command-style search over long lists use Command. For native mobile pickers use Native Select.
11
11
 
12
12
  ## Anatomy
13
13
 
@@ -63,7 +63,7 @@ import {
63
63
 
64
64
  ## Tokens
65
65
 
66
- **Colour (semantic):** `bg-background-overlay-default` (scrim), `bg-background-primary-surface` (panel), `border-border-default-default` (hairline on the attached edge: `border-l` for right, `border-r` for left, `border-b` for top, `border-t` for bottom), `text-text-prominent-default` (title), `text-text-subtle-default` (description), `text-icon-subtle-default` + `bg-background-hover-default` hover glaze (close button), `ring-ring-focus-strong` at 3px (close focus).
66
+ **Colour (semantic):** `bg-background-overlay-default` (scrim), `bg-background-primary-surface` (panel), `border-border-default-default` (hairline on the attached edge: `border-l` for right, `border-r` for left, `border-b` for top, `border-t` for bottom), `text-text-prominent-default` (title), `text-text-subtle-default` (description). The close button inherits [NavigationButton](./navigation-button.md)'s own tokens, including its focus ring.
67
67
  **Type (private):**
68
68
 
69
69
  | Token | Value |
@@ -75,7 +75,7 @@ import {
75
75
 
76
76
  - Enter/exit are directional slides (`slide-in-from-*` / `slide-out-to-*`) on the overlay motion role: `--motion-overlay-open` (320ms, `ease-enter`) in and `--motion-overlay-close` (250ms, `ease-exit`) out; the overlay fades.
77
77
  - Left/right sheets are 75% viewport width capped at `sm:max-w-sm`; top/bottom size to content.
78
- - The built-in close is a `size-8` circle (`rounded-full`) at 70% opacity that gains the `background-hover-default` glaze on hover — the icon-buttons-are-circles rule.
78
+ - The built-in close is a composed `NavigationButton` (`size="sm"`, 32px) rather than a hand-rolled circle, so it tracks that component's resting tint, hover step, and focus ring automatically.
79
79
  - The panel has a hairline only on its attached edge and no rounded corners on any side — it meets the viewport edges flush.
80
80
 
81
81
  ## Do / Don't
@@ -0,0 +1,68 @@
1
+ # Slide to Confirm
2
+
3
+ Swipe-to-commit control for irreversible money actions — withdrawals, transfers. A pill track with a draggable brand handle: drag past ~90% to confirm; release earlier and the handle springs back on the liquid spring.
4
+
5
+ ## When to use
6
+
7
+ - Confirming an action with real consequences where an accidental tap is costly (withdraw, transfer out).
8
+
9
+ **When not:** ordinary submissions use [Button](./button.md); blocking decisions use [Alert Dialog](./alert-dialog.md).
10
+
11
+ ## Anatomy
12
+
13
+ - Track — `h-14 rounded-full` (56px), secondary surface, **borderless** — the fill and the handle carry the shape; becomes the solid brand fill when confirmed. `data-slot="slide-to-confirm"`, `data-confirmed`.
14
+ - Handle — `size-11.5` (46px) brand circle with an arrow, 5px inset all round, lifted by the `--shadow-brand-glow` glow. At rest it runs the `slide-confirm-nudge` idle animation. `data-slot="slide-to-confirm-handle"`.
15
+ - Brand sweep — the solid `background-brand-default` fill riding just ahead of the handle. `data-slot="slide-to-confirm-progress"`.
16
+ - Label reveal — the label is painted twice: subtle ink on the track, white ink revealed from behind the handle. The grey→white switch line sits under the handle's centre, so text ahead of the button always reads grey and text behind it white — the change itself is never visible. `data-slot="slide-to-confirm-reveal"`.
17
+ - Confirmed state — brand fill, `on-brand-static` label, check pops in on `--motion-pop` × `ease-overshoot`.
18
+
19
+ ## API
20
+
21
+ | Prop | Type | Default | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `onConfirm` | `() => void` | — | Fires once when the handle crosses the threshold (or Enter/Space). |
24
+ | `label` | `ReactNode` | `"Slide to confirm"` | Track label; fades as the handle travels. |
25
+ | `confirmedLabel` | `ReactNode` | `"Confirmed"` | |
26
+ | `confirmed` | `boolean` | — | Controlled confirmed state — pass `false` to reset from outside. |
27
+ | `disabled` | `boolean` | `false` | `opacity-24`, pointer events off. |
28
+
29
+ ```tsx
30
+ import { SlideToConfirm } from "@trading-game/design-intelligence-layer"
31
+
32
+ <SlideToConfirm
33
+ label="Slide to withdraw 250 USDT"
34
+ confirmedLabel="Withdrawal requested"
35
+ onConfirm={submitWithdrawal}
36
+ />
37
+ ```
38
+
39
+ ## Tokens
40
+
41
+ **Colour (semantic):** `background-secondary-surface` + `border-default-default` (track), `background-brand-default` + `text-on-brand-static` (handle and the confirmed fill — the brand flip in dark is the design), `text-subtle-default` (track label) and `text-on-brand-static` (the revealed label behind the handle), `ring-focus-strong` (focus).
42
+
43
+ **Motion:** snap-back rides `--primitive-ease-spring` at `duration-slow`; the confirmed check pops on `--motion-pop` × `--primitive-ease-overshoot`.
44
+
45
+ **Type (private):**
46
+
47
+ | Token | Value |
48
+ | --- | --- |
49
+ | `--slide-to-confirm-label-*` | 14 / medium / 20; confirmed steps up to semibold via the data-attribute rule |
50
+
51
+ ## Behaviour
52
+
53
+ - Pointer drag with capture; below the 90% threshold the handle springs back to rest.
54
+ - Keyboard: the control is focusable — Enter or Space confirms directly (the drag is a pointer affordance, not an accessibility gate).
55
+ - `onConfirm` fires exactly once; reset by controlling `confirmed` and passing `false`.
56
+ - 56px tall (`h-14`), full-width by default — constrain with `className`.
57
+ - The handle nudges 10px and back on a 1.8s loop **only at rest** — it stops the moment you drag, and `motion-reduce` disables it entirely, per the foundations rule that infinite loops must honour reduced motion.
58
+ - The glow (`--shadow-brand-glow`) is the second sanctioned shadow in the system, after floating chrome. It exists because this handle must read as liftable off its track; don't reuse it for ordinary buttons.
59
+
60
+ ## Do / Don't
61
+
62
+ **Do**
63
+ - Say what confirming does in the label ("Slide to withdraw 250 USDT").
64
+ - Reset via the `confirmed` prop after your async action settles or fails.
65
+
66
+ **Don't**
67
+ - Don't use it for reversible actions — the friction is the point.
68
+ - Don't stack two on one screen.
@@ -1,68 +1,60 @@
1
1
  # Stepper
2
2
 
3
- Numeric amount entry with pill +/− buttons and a centered input, built entirely on Input Group; carries a fixed USDT suffix.
3
+ The mobile amount tray — a centred amount with frosted − / + circles either side. The product's stake control. Not to be confused with [Steps](./steps.md), the progress rail.
4
4
 
5
5
  ## When to use
6
6
 
7
- - Trade and stake amount entry where the user nudges by a fixed step — the primary amount control in order tickets.
7
+ - Adjusting a numeric amount in coarse steps: stake, multiplier, quantity.
8
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.
9
+ **When not:** free-form numeric entry belongs to [Input](./input.md) (or [Numpad](./numpad.md) on mobile); multi-stage progress is [Steps](./steps.md).
10
10
 
11
11
  ## Anatomy
12
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"`.
13
+ - Container — full-width row, `data-slot="stepper"`, `data-disabled`.
14
+ - − / + — composed `<Button variant="frosted" size="icon-lg">` (48px brand-selected tint circles); each disables at its bound.
15
+ - Centre — optional uppercase `label` over the editable amount (`role="spinbutton"`, tabular semibold figure).
19
16
 
20
17
  ## API
21
18
 
22
19
  | Prop | Type | Default | Notes |
23
20
  | --- | --- | --- | --- |
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
21
+ | `label` | `ReactNode` | — | Uppercase label above the amount, e.g. `"Stake (USDT)"`. |
22
+ | `value` / `defaultValue` | `number` | `0` | Controlled / uncontrolled. |
23
+ | `onValueChange` | `(n: number) => void` | — | |
24
+ | `min` / `max` | `number` | ±∞ | Buttons disable at the bounds; typed values clamp on commit. |
25
+ | `step` | `number` | `1` | Decimal steps set the display precision (0.5 → "2.50"). |
26
+ | `disabled` | `boolean` | `false` | Pointer events off; the `+`/`−` render Button's own disabled state and the label and amount switch to `text-disabled-default`. No wash on the wrapper — stacking one over the Buttons' own dim multiplied to ~6% and made them invisible. |
27
+ | `placeholder`, `id`, `name`, `aria-label` | | — | Pass-through to the amount input. |
35
28
 
36
29
  ```tsx
37
30
  import { Stepper } from "@trading-game/design-intelligence-layer"
38
31
 
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} />
32
+ <Stepper label="Stake (USDT)" defaultValue={2} step={0.5} min={0.5} max={100} />
42
33
  ```
43
34
 
44
35
  ## Tokens
45
36
 
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).
37
+ **Colour (semantic):** the circles are Button `frosted` (`background-brand-selected` + `text-brand-selected`, state-layer glazes); label `text-subtle-default`; amount `text-prominent-default`. Disabled swaps both text roles to `text-disabled-default` and leaves the circles to Button. No colour of its own — everything is composed.
38
+
39
+ **Type (private):**
40
+
41
+ | Token | Value |
42
+ | --- | --- |
43
+ | `--stepper-stake-label-*` | 12 / medium / 16, tracked uppercase |
44
+ | `--stepper-stake-amount-*` | 24 / semibold / 32, tabular |
48
45
 
49
46
  ## Behaviour
50
47
 
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.
48
+ - The amount is directly editable: type, then blur or Enter commits (clamped to min/max, rounded to the step's precision); ArrowUp/ArrowDown step the value.
49
+ - `onValueChange` fires with the committed number.
50
+ - Full-width by default — constrain with `className` (`max-w-90` in the demo).
57
51
 
58
52
  ## Do / Don't
59
53
 
60
54
  **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`).
55
+ - Put the unit in the `label` ("Stake (USDT)").
56
+ - Pair with [Numpad](./numpad.md) on mobile amount screens.
64
57
 
65
58
  **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`.
59
+ - Don't use it for long-range values — that's [Slider](./slider.md).
60
+ - Don't hide the label on money amounts; the unit matters.
@@ -0,0 +1,77 @@
1
+ # Steps
2
+
3
+ Progress rail — numbered indicators, onboarding timelines, and live status timelines. Fully generic composition: the consumer supplies the steps (any count), each with optional icon, title, description, and tag. Not to be confused with [Stepper](./stepper.md), the numeric +/− input.
4
+
5
+ ## When to use
6
+
7
+ - Multi-stage flows (onboarding, KYC, checkout) — horizontal.
8
+ - Live progress of a process (withdrawal status, transfer pipeline) — vertical.
9
+
10
+ **When not:** for a value the user adjusts use [Stepper](./stepper.md); for page position use [Pagination](./pagination.md) or [Breadcrumb](./breadcrumb.md).
11
+
12
+ ## Anatomy
13
+
14
+ - `Steps` — the `<ol>` rail; sets shape, orientation and size for its children and auto-numbers them. `data-slot="steps"`, `data-shape`, `data-orientation`, `data-size`.
15
+ - `Step` — one `<li>`: indicator (number, icon, or check when done) + optional title / description / tag, joined by hairline connectors. `data-slot="step"`, `data-state`; `aria-current="step"` when active.
16
+
17
+ ## API
18
+
19
+ ### Steps
20
+
21
+ | Prop | Type | Default | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `shape` | `"circle" \| "square"` | `"circle"` | circle = dots; square = `rounded-md` tiles (the onboarding look). |
24
+ | `orientation` | `"horizontal" \| "vertical"` | `"horizontal"` | Vertical puts the rail left, content right. |
25
+ | `size` | `"sm" \| "md"` | `"md"` | md = 32px indicators, 14/20 type; sm = 24px indicators, 12/16 type, tighter spacing. Works with both shapes and orientations. |
26
+
27
+ ### Step
28
+
29
+ | Prop | Type | Default | Notes |
30
+ | --- | --- | --- | --- |
31
+ | `state` | `"done" \| "active" \| "upcoming"` | `"upcoming"` | done = success fill + check; active = brand fill; upcoming = muted surface. |
32
+ | `icon` | `ReactNode` | — | Replaces the automatic step number. |
33
+ | `title` | `ReactNode` | — | |
34
+ | `description` | `ReactNode` | — | |
35
+ | `tag` | `ReactNode` | — | Small uppercase marker beside the title (e.g. "Start here"), selected-family ink. |
36
+
37
+ ```tsx
38
+ import { Step, Steps } from "@trading-game/design-intelligence-layer"
39
+
40
+ <Steps orientation="vertical" shape="square">
41
+ <Step state="done" icon={<ArrowDownLeft />} title="Deposit" description="Send any crypto — it lands as USDT." />
42
+ <Step state="active" icon={<ArrowLeftRight />} title="Transfer" tag="You are here" />
43
+ <Step title="Trade" />
44
+ </Steps>
45
+ ```
46
+
47
+ ## Tokens
48
+
49
+ **Colour (semantic):** `background-success-solid` + `text-on-status-static` (done), `background-brand-default` + `text-on-brand-static` (active — the fill flips to the dark brand by design), `background-secondary-surface` + `text-subtle-default` (upcoming), `border-neutral-default` (connectors — the stronger hairline so the rail stays visible in both themes), `text-prominent-default` / `text-subtle-default` (title/description), `text-brand-selected` (tag ink).
50
+
51
+ **Type (private):**
52
+
53
+ | Token | Value |
54
+ | --- | --- |
55
+ | `--steps-indicator-*` | 14 / semibold / 20, tabular |
56
+ | `--steps-title-*` | 14 / semibold / 20 |
57
+ | `--steps-description-*` | 14 / regular / 20 |
58
+ | `--steps-tag-*` | 10 / semibold / 16, tracked uppercase |
59
+
60
+ `size="sm"` re-points the indicator/title/description tokens at the 12/16 primitives via the `[data-size="sm"]` scope — weights unchanged, tag stays 10/16.
61
+
62
+ ## Behaviour
63
+
64
+ - Numbering is automatic from child order; add or remove steps freely (5–8 render comfortably in a content column).
65
+ - `done` always shows the check — an `icon` is overridden in that state.
66
+ - Indicators are 32px (`size-8`) at md, 24px (`size-6`) at sm.
67
+ - Vertical steps keep a minimum row height (`min-h-14` md / `min-h-11` sm) so title-only steps always show a stretch of rail; steps with descriptions outgrow it naturally.
68
+
69
+ ## Do / Don't
70
+
71
+ **Do**
72
+ - Mark exactly one step `active`.
73
+ - Use `square` + icons for onboarding, `circle` for numbered/status rails.
74
+
75
+ **Don't**
76
+ - Don't use it as navigation — steps aren't links.
77
+ - Don't put long copy in `description`; one line per step.
@@ -6,7 +6,7 @@ Binary on/off toggle (Radix Switch) with a sliding white thumb.
6
6
 
7
7
  - Settings that take effect immediately — notifications on/off, dark mode, one-click trading.
8
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).
9
+ **When not:** For options submitted with a form use Checkbox. For selecting one of several visible options use [Toggle Group](./toggle.md).
10
10
 
11
11
  ## Anatomy
12
12
 
@@ -6,7 +6,7 @@ Peer-view switcher (Radix Tabs) with a filled-pill default variant and a brand-u
6
6
 
7
7
  - Switching between sibling views of equal weight — Buy/Sell, chart intervals, account sections.
8
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).
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.md).
10
10
 
11
11
  ## Anatomy
12
12