@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.
Files changed (77) hide show
  1. package/AGENTS.md +105 -231
  2. package/README.md +50 -746
  3. package/dist/index.cjs +2753 -2986
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.cts +247 -261
  6. package/dist/index.d.ts +247 -261
  7. package/dist/index.js +2648 -2856
  8. package/dist/index.js.map +1 -1
  9. package/docs/components/accordion.md +85 -0
  10. package/docs/components/alert-dialog.md +98 -0
  11. package/docs/components/aspect-ratio.md +55 -0
  12. package/docs/components/avatar.md +88 -0
  13. package/docs/components/badge.md +75 -0
  14. package/docs/components/banner.md +84 -0
  15. package/docs/components/bottom-navigation.md +90 -0
  16. package/docs/components/breadcrumb.md +85 -0
  17. package/docs/components/button.md +94 -0
  18. package/docs/components/calendar.md +74 -0
  19. package/docs/components/card.md +79 -0
  20. package/docs/components/carousel.md +82 -0
  21. package/docs/components/checkbox.md +66 -0
  22. package/docs/components/chip.md +72 -0
  23. package/docs/components/command.md +86 -0
  24. package/docs/components/context-menu.md +90 -0
  25. package/docs/components/dialog.md +95 -0
  26. package/docs/components/drawer.md +97 -0
  27. package/docs/components/dropdown-menu.md +92 -0
  28. package/docs/components/empty.md +87 -0
  29. package/docs/components/field.md +117 -0
  30. package/docs/components/hover-card.md +77 -0
  31. package/docs/components/input-group.md +105 -0
  32. package/docs/components/input-otp.md +87 -0
  33. package/docs/components/input.md +71 -0
  34. package/docs/components/item.md +105 -0
  35. package/docs/components/label.md +56 -0
  36. package/docs/components/link.md +66 -0
  37. package/docs/components/menubar.md +102 -0
  38. package/docs/components/native-select.md +71 -0
  39. package/docs/components/navigation-button.md +68 -0
  40. package/docs/components/navigation-menu.md +99 -0
  41. package/docs/components/numpad.md +78 -0
  42. package/docs/components/pagination.md +84 -0
  43. package/docs/components/popover.md +89 -0
  44. package/docs/components/profile-photo.md +81 -0
  45. package/docs/components/progress.md +60 -0
  46. package/docs/components/radio-group.md +82 -0
  47. package/docs/components/resizable.md +79 -0
  48. package/docs/components/scroll-area.md +66 -0
  49. package/docs/components/section-message.md +99 -0
  50. package/docs/components/select.md +105 -0
  51. package/docs/components/separator.md +55 -0
  52. package/docs/components/sheet.md +91 -0
  53. package/docs/components/sidebar.md +125 -0
  54. package/docs/components/skeleton.md +51 -0
  55. package/docs/components/slider.md +61 -0
  56. package/docs/components/spinner.md +52 -0
  57. package/docs/components/stepper.md +68 -0
  58. package/docs/components/switch.md +57 -0
  59. package/docs/components/table.md +86 -0
  60. package/docs/components/tabs.md +95 -0
  61. package/docs/components/textarea.md +58 -0
  62. package/docs/components/toast.md +66 -0
  63. package/docs/components/toggle-group.md +77 -0
  64. package/docs/components/toggle.md +60 -0
  65. package/docs/components/tooltip.md +83 -0
  66. package/docs/foundations/colors.md +110 -0
  67. package/docs/foundations/motion.md +63 -0
  68. package/docs/foundations/shape-layout.md +56 -0
  69. package/docs/foundations/typography.md +83 -0
  70. package/docs/patterns/forms.md +70 -0
  71. package/docs/patterns/menus.md +50 -0
  72. package/docs/patterns/on-brand.md +43 -0
  73. package/guides/audits/design-system-audit-2026-07.md +135 -0
  74. package/guides/rules/design-system-consuming-project.mdc +56 -0
  75. package/package.json +5 -6
  76. package/src/styles.css +1634 -252
  77. package/guides/design-system-guide/trading-game-ds-guide.md +0 -933
@@ -0,0 +1,99 @@
1
+ # Navigation Menu
2
+
3
+ Site navigation with dropdown panels — triggers in a row, rich link panels beneath.
4
+
5
+ ## When to use
6
+
7
+ - Top-level site/app navigation where sections expand into panels of links.
8
+
9
+ **When not:** For application command menus use [Menubar](./menubar.md). For a single circular back/forward control use [Navigation Button](./navigation-button.md). For page number navigation use [Pagination](./pagination.md).
10
+
11
+ ## Anatomy
12
+
13
+ - `NavigationMenu` — Radix root; renders the shared `NavigationMenuViewport` unless `viewport={false}`. `data-slot="navigation-menu"`, `data-viewport`.
14
+ - `NavigationMenuList` — horizontal `<ul>`, `gap-1`. `data-slot="navigation-menu-list"`.
15
+ - `NavigationMenuItem` — one entry. `data-slot="navigation-menu-item"`.
16
+ - `NavigationMenuTrigger` — `h-10 rounded-md px-4` button with an auto-appended `ChevronDownIcon` that rotates 180° while open. `data-slot="navigation-menu-trigger"`.
17
+ - `NavigationMenuContent` — the panel content; when `viewport={false}` it becomes its own `rounded-lg` bordered dropdown. `data-slot="navigation-menu-content"`.
18
+ - `NavigationMenuViewport` — shared panel frame below the bar: `rounded-lg`, hairline border, solid surface, animated height/width. `data-slot="navigation-menu-viewport"`.
19
+ - `NavigationMenuLink` — link row, `rounded-md p-2`. `data-slot="navigation-menu-link"`.
20
+ - `NavigationMenuIndicator` — small rotated-square arrow under the active trigger. `data-slot="navigation-menu-indicator"`.
21
+ - `navigationMenuTriggerStyle` — exported cva to give plain links the trigger look.
22
+
23
+ ## API
24
+
25
+ ### NavigationMenu
26
+
27
+ | Prop | Type | Default | Notes |
28
+ | --- | --- | --- | --- |
29
+ | `viewport` | `boolean` | `true` | `true` = panels render in the single shared viewport; `false` = each content is its own dropdown under its item. |
30
+
31
+ ### NavigationMenuLink
32
+
33
+ Radix `Link` props pass through; set `active` (renders `data-active=true`) to mark the current page, and use `asChild` for router links.
34
+
35
+ Other parts take Radix/native props plus `className` only.
36
+
37
+ ## Variants & sizes
38
+
39
+ No size rail — fixed density from the shared menu type family.
40
+
41
+ ```tsx
42
+ import {
43
+ NavigationMenu,
44
+ NavigationMenuList,
45
+ NavigationMenuItem,
46
+ NavigationMenuTrigger,
47
+ NavigationMenuContent,
48
+ NavigationMenuLink,
49
+ navigationMenuTriggerStyle,
50
+ } from "@trading-game/design-intelligence-layer"
51
+
52
+ <NavigationMenu>
53
+ <NavigationMenuList>
54
+ <NavigationMenuItem>
55
+ <NavigationMenuTrigger>Markets</NavigationMenuTrigger>
56
+ <NavigationMenuContent>
57
+ <NavigationMenuLink active href="/markets/forex">Forex</NavigationMenuLink>
58
+ <NavigationMenuLink href="/markets/synthetics">Synthetics</NavigationMenuLink>
59
+ </NavigationMenuContent>
60
+ </NavigationMenuItem>
61
+ <NavigationMenuItem>
62
+ <NavigationMenuLink className={navigationMenuTriggerStyle()} href="/pricing">
63
+ Pricing
64
+ </NavigationMenuLink>
65
+ </NavigationMenuItem>
66
+ </NavigationMenuList>
67
+ </NavigationMenu>
68
+ ```
69
+
70
+ ## Tokens
71
+
72
+ **Colour (semantic):**
73
+ - trigger/viewport/panels: `bg-background-primary-surface`, `border-border-default-default`, ink `text-text-prominent-default`
74
+ - hover/focus/open: `bg-background-hover-default` + `text-text-prominent-default`
75
+ - active link (`data-active=true`): `bg-background-brand-selected` + `text-text-brand-selected` — held on hover and focus
76
+ - link icons `text-icon-subtle-default` (16px default)
77
+ - focus: 3px `ring-ring-focus-strong`; indicator arrow `bg-border-default-default`
78
+
79
+ **Type (private):** shared `--menu-*` family — trigger `type-menu-trigger` 14/medium/20, links `type-menu-item` 14/regular/20.
80
+
81
+ ## Behaviour
82
+
83
+ - With the default shared viewport, panels cross-fade and the viewport animates to each panel's size; content slides in from the direction of travel (`data-motion`).
84
+ - `viewport={false}` gives per-item dropdowns: `top-full mt-1.5`, own `rounded-lg` border and surface.
85
+ - The trigger chevron rotates 180° while open (`duration-slow`).
86
+ - The current page keeps the brand-selected fill and ink even when hovered or focused — selection outranks hover.
87
+ - On small screens content is full-width static (`md:absolute md:w-auto` gates the dropdown positioning).
88
+
89
+ ## Do / Don't
90
+
91
+ **Do**
92
+ - Mark the current route with `active` on `NavigationMenuLink` so it takes the brand-selected pair.
93
+ - Use `navigationMenuTriggerStyle()` for panel-less top-level links so heights and hover states match triggers.
94
+ - Keep panels `rounded-lg` (10px) with `rounded-md` (8px) link rows.
95
+
96
+ **Don't**
97
+ - Don't repaint the active state — `bg-background-brand-selected`/`text-text-brand-selected` are the selection contract, and dark mode has its own values (solid #121A55 fill + blue-200 ink), not a mirror of light.
98
+ - Don't add shadows to the viewport or dropdowns; hairline on solid surface.
99
+ - Don't nest a `Link` component inside `NavigationMenuLink`; use `asChild`/`href` on the menu link itself.
@@ -0,0 +1,78 @@
1
+ # Numpad
2
+
3
+ The mobile amount-entry tray — a bare 3×4 digit grid with no key backgrounds; only glazes mark a touch.
4
+
5
+ ## When to use
6
+
7
+ - Mobile amount entry: stake, deposit, withdrawal amounts, paired with an amount display the consumer owns.
8
+
9
+ **When not:** For general text/number typing with a system keyboard use [Input](./input.md) (`inputMode="decimal"`). For code entry use [Input OTP](./input-otp.md).
10
+
11
+ ## Anatomy
12
+
13
+ - `Numpad` — the whole grid: `role="group"`, `aria-label="Number pad"`, `grid-cols-3 gap-1`. `data-slot="numpad"`.
14
+ - Keys — internal `<button>`s, `h-12 rounded-lg`, one per digit plus "." and backspace (a 22px `Delete` icon). `data-slot="numpad-key"`, `data-key`.
15
+ - With `decimal={false}` the "." cell is an empty `aria-hidden` span — the slot stays, the grid never reflows.
16
+
17
+ ## API
18
+
19
+ ### Numpad
20
+
21
+ | Prop | Type | Default | Notes |
22
+ | --- | --- | --- | --- |
23
+ | `onKeyPress` | `(key: NumpadKey) => void` | — | Fires for every tap. `NumpadKey = "0"–"9" \| "." \| "backspace"`. |
24
+ | `onClear` | `() => void` | — | Fires when backspace is long-pressed (~600ms). The release tap after a fired long-press is swallowed — no `onKeyPress("backspace")` follows. |
25
+ | `decimal` | `boolean` | `true` | `false` hides the "." key; its grid slot stays empty. |
26
+ | `disabled` | `boolean` | `false` | Dims the tray to 50% and blocks pointer events on grid and keys. |
27
+
28
+ `onKeyDown`/`onKeyPress` DOM handlers are omitted from the div props; everything else passes through.
29
+
30
+ ## Variants & sizes
31
+
32
+ No variants; keys are fixed at 48px rows (`h-12`) in a full-width 3-column grid.
33
+
34
+ ```tsx
35
+ import { Numpad, type NumpadKey } from "@trading-game/design-intelligence-layer"
36
+
37
+ const [amount, setAmount] = React.useState("")
38
+
39
+ <Numpad
40
+ onKeyPress={(key: NumpadKey) => {
41
+ if (key === "backspace") setAmount((a) => a.slice(0, -1))
42
+ else setAmount((a) => a + key)
43
+ }}
44
+ onClear={() => setAmount("")}
45
+ />
46
+
47
+ // Integer-only entry:
48
+ <Numpad decimal={false} onKeyPress={handleKey} onClear={handleClear} />
49
+ ```
50
+
51
+ ## Tokens
52
+
53
+ **Colour (semantic):**
54
+ - keys: transparent at rest, `text-text-prominent-default`; backspace icon `text-icon-subtle-default`
55
+ - hover glaze `bg-background-hover-default`, press glaze `bg-background-pressed-default` — the only fills the keys ever get
56
+ - focus: 3px `ring-ring-focus-strong`
57
+
58
+ **Type (private):** `--numpad-*`: 20 / medium / 28 (`type-numpad`).
59
+
60
+ ## Behaviour
61
+
62
+ - Fully controlled: the component owns no amount — it only emits keys; the consumer owns the string.
63
+ - Long-press clear: pointer-down on backspace starts a ~600ms timer; if it fires, `onClear` runs and the subsequent click is swallowed. Pointer-up or leaving the key cancels the timer. Context menu on backspace is suppressed so the long-press isn't hijacked on touch.
64
+ - The hold timer is cleaned up on unmount.
65
+ - Key order is 1–9 then [".", "0", backspace]; `decimal={false}` replaces "." with an empty `aria-hidden` span.
66
+ - Backspace's accessible name is "Backspace — hold to clear".
67
+
68
+ ## Do / Don't
69
+
70
+ **Do**
71
+ - Own the amount string and its validation (decimal places, max) in the consumer — the tray is dumb by design.
72
+ - Wire `onClear`; users expect hold-to-clear on trading numpads.
73
+ - Keep keys at 48px height — the touch-target floor.
74
+
75
+ **Don't**
76
+ - Don't give keys resting backgrounds, borders, or shadows; per the design file, only the hover/press glazes mark a touch.
77
+ - Don't emit your own backspace on long-press release — the component already swallows it; double handling double-deletes.
78
+ - Don't collapse the empty "." slot when `decimal={false}`; the 0 key must stay centred under 8.
@@ -0,0 +1,84 @@
1
+ # Pagination
2
+
3
+ Page navigation as a row of 40px circles — the active page lit with the brand-selected tokens.
4
+
5
+ ## When to use
6
+
7
+ - Paged tables and lists: trade history, statements, leaderboards.
8
+
9
+ **When not:** For prev/next-only stepping without page numbers use two [Navigation Buttons](./navigation-button.md). For loading more inline, use a "Load more" Button.
10
+
11
+ ## Anatomy
12
+
13
+ - `Pagination` — `<nav role="navigation" aria-label="pagination">`, centred. `data-slot="pagination"`.
14
+ - `PaginationContent` — `<ul>` row, `gap-1`. `data-slot="pagination-content"`.
15
+ - `PaginationItem` — `<li>` wrapper. `data-slot="pagination-item"`.
16
+ - `PaginationLink` — a page number: `size-10` circle `<a>`. `data-slot="pagination-link"`, `data-active`.
17
+ - `PaginationPrevious` / `PaginationNext` — chevron links rendered as tertiary icon Buttons (`variant="tertiary" size="icon-md"`, 20px chevrons). `data-slot="pagination-link"`.
18
+ - `PaginationEllipsis` — non-interactive `size-10` cell with `MoreHorizontalIcon` + sr-only "More pages". `data-slot="pagination-ellipsis"`.
19
+
20
+ ## API
21
+
22
+ ### PaginationLink
23
+
24
+ | Prop | Type | Default | Notes |
25
+ | --- | --- | --- | --- |
26
+ | `isActive` | `boolean` | — | Sets `aria-current="page"` and the brand-selected fill/border/ink. |
27
+
28
+ `PaginationPrevious`/`PaginationNext` accept `<a>` props (`href`, etc.); the aria-labels "Go to previous page"/"Go to next page" are built in.
29
+
30
+ ## Variants & sizes
31
+
32
+ One size: 40px circular cells.
33
+
34
+ ```tsx
35
+ import {
36
+ Pagination,
37
+ PaginationContent,
38
+ PaginationItem,
39
+ PaginationLink,
40
+ PaginationPrevious,
41
+ PaginationNext,
42
+ PaginationEllipsis,
43
+ } from "@trading-game/design-intelligence-layer"
44
+
45
+ <Pagination>
46
+ <PaginationContent>
47
+ <PaginationItem><PaginationPrevious href="?page=1" /></PaginationItem>
48
+ <PaginationItem><PaginationLink href="?page=1">1</PaginationLink></PaginationItem>
49
+ <PaginationItem><PaginationLink href="?page=2" isActive>2</PaginationLink></PaginationItem>
50
+ <PaginationItem><PaginationEllipsis /></PaginationItem>
51
+ <PaginationItem><PaginationLink href="?page=9">9</PaginationLink></PaginationItem>
52
+ <PaginationItem><PaginationNext href="?page=3" /></PaginationItem>
53
+ </PaginationContent>
54
+ </Pagination>
55
+ ```
56
+
57
+ ## Tokens
58
+
59
+ **Colour (semantic):**
60
+ - rest page: transparent fill and border, `text-text-prominent-default`, hover `bg-background-hover-default`
61
+ - active page: `bg-background-brand-selected` + `border-border-brand-selected` + `text-text-brand-selected` (light: 10% brand tint + blue-600 ink; dark: solid #121A55 + blue-200 ink + blue-300 border)
62
+ - focus: 3px `ring-ring-focus-strong`
63
+ - prev/next inherit the tertiary Button recipe
64
+
65
+ **Type (private):** `--pagination-*`: 14 / semibold / 20 (`type-pagination`, `font-display`).
66
+
67
+ ## Behaviour
68
+
69
+ - Pure links — no internal state; the consumer renders the right `isActive` page and hrefs.
70
+ - `isActive` drives both `aria-current="page"` and `data-active` plus the selected paint.
71
+ - Every cell carries a border (transparent at rest) so the active border-color change never shifts layout.
72
+ - Prev/next are real Buttons via `asChild`, so they inherit Button's hover/press/disabled behaviour.
73
+
74
+ ## Do / Don't
75
+
76
+ **Do**
77
+ - Keep cells 40px circles (`size-10 rounded-full`) — buttons/chips round fully in this system.
78
+ - Use `PaginationEllipsis` for gaps; it stays non-interactive and announces "More pages" to screen readers.
79
+ - Render page numbers at the token type: 14/semibold/20 — semibold is the ceiling.
80
+
81
+ **Don't**
82
+ - Don't restyle the active cell with raw brand fills; the brand-selected trio is the selection contract, and dark theme is not a mirror.
83
+ - Don't turn page links into `<button>`s when they change the URL — they are navigation.
84
+ - Don't drop the transparent rest border; you'll get a 1px jump when the active ring appears.
@@ -0,0 +1,89 @@
1
+ # Popover
2
+
3
+ Click-opened floating card for small interactive surfaces — filters, quick settings, confirm-lite content.
4
+
5
+ ## When to use
6
+
7
+ - Small interactive panels anchored to a trigger: column pickers, mini forms, share sheets.
8
+
9
+ **When not:** For hover-only previews use [Hover Card](./hover-card.md). For command lists use a dropdown menu. For flows needing full attention use a Dialog.
10
+
11
+ ## Anatomy
12
+
13
+ - `Popover` — Radix root. `data-slot="popover"`.
14
+ - `PopoverTrigger` — the opening control. `data-slot="popover-trigger"`.
15
+ - `PopoverContent` — portalled card: 288px (`w-72`), `rounded-2xl`, `p-4`, hairline border, no shadow. `data-slot="popover-content"`.
16
+ - `PopoverAnchor` — optional custom anchor. `data-slot="popover-anchor"`.
17
+ - `PopoverHeader` — title + description stack, `gap-1`. `data-slot="popover-header"`.
18
+ - `PopoverTitle` — 14/medium/20 heading. `data-slot="popover-title"`.
19
+ - `PopoverDescription` — 14/regular/20 subtle copy. `data-slot="popover-description"`.
20
+
21
+ ## API
22
+
23
+ ### Popover (root)
24
+
25
+ Radix `Popover.Root` props: `open`, `defaultOpen`, `onOpenChange`, `modal`.
26
+
27
+ ### PopoverContent
28
+
29
+ | Prop | Type | Default | Notes |
30
+ | --- | --- | --- | --- |
31
+ | `align` | `"start" \| "center" \| "end"` | `"center"` | |
32
+ | `sideOffset` | `number` | `4` | Gap to the anchor in px. |
33
+
34
+ Other Radix `Popover.Content` props (`side`, `collisionPadding`, …) pass through. Header/Title/Description take only element props plus `className`.
35
+
36
+ ## Variants & sizes
37
+
38
+ No variants; one `w-72` card — widen via `className` when the content demands it.
39
+
40
+ ```tsx
41
+ import {
42
+ Popover,
43
+ PopoverTrigger,
44
+ PopoverContent,
45
+ PopoverHeader,
46
+ PopoverTitle,
47
+ PopoverDescription,
48
+ Button,
49
+ } from "@trading-game/design-intelligence-layer"
50
+
51
+ <Popover>
52
+ <PopoverTrigger asChild>
53
+ <Button variant="secondary">Chart settings</Button>
54
+ </PopoverTrigger>
55
+ <PopoverContent align="end">
56
+ <PopoverHeader>
57
+ <PopoverTitle>Chart settings</PopoverTitle>
58
+ <PopoverDescription>Applies to this chart only.</PopoverDescription>
59
+ </PopoverHeader>
60
+ {/* controls */}
61
+ </PopoverContent>
62
+ </Popover>
63
+ ```
64
+
65
+ ## Tokens
66
+
67
+ **Colour (semantic):**
68
+ - card: `bg-background-primary-surface` (solid), `border-border-default-default`, ink `text-text-prominent-default`
69
+ - title `text-text-prominent-default`; description `text-text-subtle-default`
70
+
71
+ **Type (private):** `--popover-title-*`: 14 / medium / 20; `--popover-description-*`: 14 / regular / 20. (The base `--popover`/`--popover-foreground` aliases map to primary surface / prominent ink.)
72
+
73
+ ## Behaviour
74
+
75
+ - Radix popover semantics: opens on trigger click, closes on outside click and Escape, focus moves into the content.
76
+ - Content is portalled and animated (fade + 95% zoom + side-aware slide) from the trigger's transform origin.
77
+ - The card shell is the system overlay-card recipe: `rounded-2xl` (18px), hairline border, solid surface, **no shadow**.
78
+
79
+ ## Do / Don't
80
+
81
+ **Do**
82
+ - Use `PopoverTitle`/`PopoverDescription` for headings — 14/medium and 14/regular subtle are the fixed pair.
83
+ - Keep the trigger a real focusable control (`asChild` with a Button).
84
+ - Use `align`/`sideOffset` rather than margin hacks to position the card.
85
+
86
+ **Don't**
87
+ - Don't add `shadow-*` classes — surfaces are solid and shadowless by law; the hairline is the depth cue.
88
+ - Don't shrink the 18px radius to menu radius; popovers are cards, not menus.
89
+ - Don't stack a popover inside a hover card; pick the interaction model that fits.
@@ -0,0 +1,81 @@
1
+ # Profile Photo
2
+
3
+ Page-level profile photo with a verification progress ring, photo-change button, and status badge slot.
4
+
5
+ ## When to use
6
+
7
+ - Account/profile pages where the photo carries verification progress and a change affordance.
8
+
9
+ **When not:** For identity chips in lists, nav, and comments use Avatar (24–48px) — ProfilePhoto is a fixed 112px page element, not a list glyph.
10
+
11
+ ## Anatomy
12
+
13
+ - `ProfilePhoto` — the 112px composite. `data-slot="profile-photo"`, `data-progress`, `data-complete`.
14
+ - Ring — SVG track + progress circle, 5px stroke flush to the outer edge, 3px gap to the photo. Progress arc: `data-slot="profile-photo-ring"`.
15
+ - Photo — 96px circle (`data-slot="profile-photo-image"`); placeholder is a secondary-surface tile with a 40px `User` glyph.
16
+ - Camera button — 24px primary icon Button (`data-slot="profile-photo-camera"`), hit area extended to ~44px via `after:-inset-2.5`.
17
+ - Status slot — centred over the bottom edge (`data-slot="profile-photo-status"`); pass a `Badge`.
18
+
19
+ ## API
20
+
21
+ ### ProfilePhoto
22
+
23
+ | Prop | Type | Default | Notes |
24
+ | --- | --- | --- | --- |
25
+ | `src` | `string` | — | Photo URL; omit for the placeholder glyph. |
26
+ | `alt` | `string` | `""` | Ignored when there is no `src`. |
27
+ | `progress` | `number` | `0` | Verification progress 0–100, clamped; drives the ring arc. |
28
+ | `onPhotoChange` | `() => void` | — | Shows the camera button; omit to hide it entirely (read-only contexts). |
29
+ | `photoChangeLabel` | `string` | `"Change photo"` | aria-label for the camera button. |
30
+ | `status` | `ReactNode` | — | Rendered overlapping the bottom edge; positioned, not styled — pass a `Badge`. |
31
+
32
+ `children` is excluded; other `div` props pass through.
33
+
34
+ ## Variants & sizes
35
+
36
+ No variants; fixed geometry — 112px box, 96px photo, 5px ring, 3px gap.
37
+
38
+ ```tsx
39
+ import { ProfilePhoto, Badge } from "@trading-game/design-intelligence-layer"
40
+
41
+ <ProfilePhoto
42
+ src="/players/fahad.jpg"
43
+ alt="Fahad"
44
+ progress={60}
45
+ onPhotoChange={openUploader}
46
+ status={<Badge variant="warning">Verifying</Badge>}
47
+ />
48
+
49
+ // Read-only, unverified placeholder:
50
+ <ProfilePhoto progress={0} />
51
+ ```
52
+
53
+ ## Tokens
54
+
55
+ **Colour (semantic):**
56
+ - ring track: `var(--border-default-default)` (SVG stroke)
57
+ - progress arc: `var(--background-brand-default)`; at 100%: `var(--text-success-default)`
58
+ - placeholder tile: `bg-background-secondary-surface` + `text-icon-subtle-default` glyph
59
+ - camera chip: primary Button recipe with a 2px `border-prominent` ring against the photo
60
+
61
+ **Type (private):** none — no text of its own.
62
+
63
+ ## Behaviour
64
+
65
+ - `progress` is clamped to 0–100; `data-complete` appears at ≥100 and swaps the arc stroke to the win colour.
66
+ - The arc starts and ends at 6 o'clock (SVG rotated 90°) so its seam hides behind the status badge, and animates between values (`transition-[stroke-dashoffset,stroke] duration-open ease-enter`, disabled under `motion-reduce`).
67
+ - The stroke path radius is inset by half the stroke (r = 53.5) so the 5px ring stays inside the 112px box.
68
+ - The camera button renders only when `onPhotoChange` is provided; at 24px it stays subordinate to the photo while the `::after` hit area meets the ~44px touch minimum.
69
+ - The status node is positioned (`-bottom-1`, centred) but never styled — content is the consumer's Badge.
70
+
71
+ ## Do / Don't
72
+
73
+ **Do**
74
+ - Pass a system `Badge` into `status`; the component only owns its position.
75
+ - Omit `onPhotoChange` for viewers who cannot change the photo — hiding the button is the API, not CSS.
76
+ - Provide `alt` whenever `src` is set.
77
+
78
+ **Don't**
79
+ - Don't resize the composite; 112/96/5/3 is fixed geometry, and Avatar covers the small sizes.
80
+ - Don't recolour the ring track or arc — border-default track, brand arc, win at 100% is the verification vocabulary.
81
+ - Don't put arbitrary content in `status`; a long node will collide with the ring seam it is meant to cover.
@@ -0,0 +1,60 @@
1
+ # Progress
2
+
3
+ Determinate progress bar — a brand wash track with a solid brand indicator.
4
+
5
+ ## When to use
6
+
7
+ - Showing completion of a known-length process: upload progress, profile completeness, step progress.
8
+
9
+ **When not:** For unknown-length waits use Spinner or Skeleton. For circular verification progress on a profile use [Profile Photo](./profile-photo.md).
10
+
11
+ ## Anatomy
12
+
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"`.
15
+
16
+ ## API
17
+
18
+ ### Progress
19
+
20
+ | Prop | Type | Default | Notes |
21
+ | --- | --- | --- | --- |
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`).
29
+
30
+ ```tsx
31
+ import { Progress } from "@trading-game/design-intelligence-layer"
32
+
33
+ <Progress value={66} />
34
+ <Progress value={verifiedSteps * 25} className="max-w-xs" />
35
+ ```
36
+
37
+ ## Tokens
38
+
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
42
+
43
+ **Type (private):** none.
44
+
45
+ ## Behaviour
46
+
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.
50
+
51
+ ## Do / Don't
52
+
53
+ **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.
56
+
57
+ **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.
@@ -0,0 +1,82 @@
1
+ # Radio Group
2
+
3
+ Pick-one control set — 16px hairline circles that take a brand border and dot when checked.
4
+
5
+ ## When to use
6
+
7
+ - Mutually exclusive choices, 2–6 options, all visible at once (trade type, duration unit).
8
+ - As the control inside [Field](./field.md) choice cards.
9
+
10
+ **When not:** For many options use [Native Select](./native-select.md) or Select. For independent on/off choices use Checkbox; for a single toggle use Switch.
11
+
12
+ ## Anatomy
13
+
14
+ - `RadioGroup` — Radix root, `grid gap-3`; owns value and roving focus. `data-slot="radio-group"`.
15
+ - `RadioGroupItem` — one radio: 16px (`size-4`) circle, hairline border on primary surface. `data-slot="radio-group-item"`.
16
+ - Indicator — centred 8px (`size-2`) brand dot, rendered only when checked. `data-slot="radio-group-indicator"`.
17
+
18
+ ## API
19
+
20
+ ### RadioGroup
21
+
22
+ Radix `RadioGroup.Root` props: `value`, `defaultValue`, `onValueChange`, `disabled`, `orientation`, `name`, `required`, `loop`.
23
+
24
+ ### RadioGroupItem
25
+
26
+ | Prop | Type | Default | Notes |
27
+ | --- | --- | --- | --- |
28
+ | `value` | `string` | required | The option's value. |
29
+ | …rest | Radix `Item` props | — | `disabled`, `id`, `aria-invalid`, etc. |
30
+
31
+ ## Variants & sizes
32
+
33
+ No variants; one 16px size.
34
+
35
+ ```tsx
36
+ import {
37
+ RadioGroup,
38
+ RadioGroupItem,
39
+ Label,
40
+ } from "@trading-game/design-intelligence-layer"
41
+
42
+ <RadioGroup defaultValue="rise" onValueChange={setSide}>
43
+ <div className="flex items-center gap-2">
44
+ <RadioGroupItem value="rise" id="rise" />
45
+ <Label htmlFor="rise">Rise</Label>
46
+ </div>
47
+ <div className="flex items-center gap-2">
48
+ <RadioGroupItem value="fall" id="fall" />
49
+ <Label htmlFor="fall">Fall</Label>
50
+ </div>
51
+ </RadioGroup>
52
+ ```
53
+
54
+ ## Tokens
55
+
56
+ **Colour (semantic):**
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)
59
+ - focus: `border-ring-focus-default` + 3px `ring-ring-focus-strong` (control ring)
60
+ - error (`aria-invalid`): `border-border-error-default` + `ring-ring-error-soft`
61
+
62
+ **Type (private):** none — pair with [Label](./label.md) (14/medium/20).
63
+
64
+ ## Behaviour
65
+
66
+ - Single-select with roving tabindex: Tab reaches the group once, arrows move and select within it (Radix).
67
+ - Controlled (`value` + `onValueChange`) or uncontrolled (`defaultValue`).
68
+ - Checked state swaps only border and dot — the background never fills; there is no shadow in any state.
69
+ - Disabled items get `cursor-not-allowed` + 50% opacity.
70
+ - Inside a Field choice card, the item's `data-state=checked` is what flips the surrounding `FieldLabel` to the brand-selected card treatment.
71
+
72
+ ## Do / Don't
73
+
74
+ **Do**
75
+ - Give every item an `id` and a `Label htmlFor` — the label is the touch target that makes 16px circles usable.
76
+ - Put `aria-invalid` on the items and `data-invalid` on the enclosing Field so ring and label ink agree.
77
+ - Use `gap-3` stacking as rendered; wrap each item + label row in a flex row.
78
+
79
+ **Don't**
80
+ - Don't fill the checked circle's background; brand border + brand dot on primary surface is the checked recipe.
81
+ - Don't add shadows or inner glows to states — hairline, ring, and dot carry everything.
82
+ - Don't manage checked state per-item; the group owns the single value.
@@ -0,0 +1,79 @@
1
+ # Resizable
2
+
3
+ Draggable panel groups (wrapping `react-resizable-panels`) for splitting a region into user-adjustable panes.
4
+
5
+ ## When to use
6
+
7
+ - Side-by-side work areas the user should be able to re-proportion — chart vs. order panel, list vs. detail.
8
+ - Nested horizontal/vertical splits in dense desktop layouts.
9
+
10
+ **When not:** For a fixed navigation column use [Sidebar](./sidebar.md). For a static divider with no drag affordance use [Separator](./separator.md).
11
+
12
+ ## Anatomy
13
+
14
+ - `ResizablePanelGroup` — flex container, flips to `flex-col` when `aria-orientation="vertical"`; `data-slot="resizable-panel-group"`.
15
+ - `ResizablePanel` — pass-through panel from `react-resizable-panels`; `data-slot="resizable-panel"`.
16
+ - `ResizableHandle` — 1px drag separator (`w-px` / `h-px` horizontal) with an invisible 4px hit area (`after:w-1`); optional grip pill; `data-slot="resizable-handle"`.
17
+
18
+ ## API
19
+
20
+ ### ResizablePanelGroup
21
+
22
+ | Prop | Type | Default | Notes |
23
+ | --- | --- | --- | --- |
24
+ | `direction` | `"horizontal" \| "vertical"` | — | Required by `react-resizable-panels`. |
25
+ | ...props | `GroupProps` | — | All library props pass through. |
26
+
27
+ ### ResizablePanel
28
+
29
+ | Prop | Type | Default | Notes |
30
+ | --- | --- | --- | --- |
31
+ | `defaultSize` / `minSize` / `maxSize` | `number` | — | Percentages, from the library. |
32
+ | ...props | `PanelProps` | — | Pass-through. |
33
+
34
+ ### ResizableHandle
35
+
36
+ | Prop | Type | Default | Notes |
37
+ | --- | --- | --- | --- |
38
+ | `withHandle` | `boolean` | `false` | Renders the grip pill (`h-4 w-3 rounded-xs`, `GripVerticalIcon` at `size-2.5`, rotated 90° when horizontal). |
39
+
40
+ ## Variants & sizes
41
+
42
+ No cva variants and no size prop — one hairline handle, with or without the grip.
43
+
44
+ ```tsx
45
+ import {
46
+ ResizablePanelGroup,
47
+ ResizablePanel,
48
+ ResizableHandle,
49
+ } from "@trading-game/design-intelligence-layer"
50
+
51
+ <ResizablePanelGroup direction="horizontal">
52
+ <ResizablePanel defaultSize={70}>Chart</ResizablePanel>
53
+ <ResizableHandle withHandle />
54
+ <ResizablePanel defaultSize={30}>Positions</ResizablePanel>
55
+ </ResizablePanelGroup>
56
+ ```
57
+
58
+ ## Tokens
59
+
60
+ **Colour (semantic):** `bg-border-default-default` (handle line and grip fill), `border-border-default-default` (grip border), `text-icon-subtle-default` (grip icon), `ring-ring-focus-default` (keyboard focus, `ring-1` + `ring-offset-1`).
61
+ **Type (private):** none — the component renders no text.
62
+
63
+ ## Behaviour
64
+
65
+ - Orientation is driven by the library's `aria-orientation` attribute, not a prop on the handle: horizontal groups get a `h-px w-full` handle and the grip rotates 90°.
66
+ - The visible line is 1px; the `after:` pseudo-element widens the pointer target to 4px on the drag axis.
67
+ - Keyboard resize focus uses `ring-1 ring-ring-focus-default ring-offset-1` — a thinner ring than the standard 3px control ring, because the handle itself is only 1px.
68
+
69
+ ## Do / Don't
70
+
71
+ **Do**
72
+ - Keep the handle at the default 1px hairline — it matches every other divider in the system.
73
+ - Use `withHandle` when the split is a primary interaction; the bare hairline alone is easy to miss.
74
+ - Set `minSize` on panels holding controls so they cannot collapse below usability.
75
+
76
+ **Don't**
77
+ - Don't thicken or recolour the handle to make it "visible" — the 4px hit area already handles discoverability.
78
+ - Don't add shadows to panels — surfaces are solid and flat everywhere in the system.
79
+ - Don't use Resizable for mobile layouts; drag-resize is a desktop pattern.