@trading-game/design-intelligence-layer 1.0.3 → 1.0.4
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 +17 -12
- package/README.md +5 -2
- package/dist/index.cjs +801 -460
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +72 -12
- package/dist/index.d.ts +72 -12
- package/dist/index.js +884 -547
- package/dist/index.js.map +1 -1
- package/docs/components/accordion.md +1 -1
- package/docs/components/button.md +6 -1
- package/docs/components/carousel.md +13 -1
- package/docs/components/command.md +1 -1
- package/docs/components/context-menu.md +1 -1
- package/docs/components/dropdown-menu.md +1 -1
- package/docs/components/native-select.md +1 -1
- package/docs/components/navigation-button.md +2 -2
- package/docs/components/pagination.md +40 -1
- package/docs/components/slide-to-confirm.md +1 -1
- package/docs/components/slider.md +1 -1
- package/docs/components/switch.md +1 -1
- package/docs/components/textarea.md +1 -1
- package/docs/components/toast.md +90 -0
- package/docs/components/toggle.md +1 -1
- package/docs/foundations/colors.md +14 -6
- package/docs/foundations/shape-layout.md +1 -1
- package/docs/foundations/typography.md +2 -2
- package/docs/patterns/menus.md +1 -1
- package/guides/rules/design-system-consuming-project.mdc +45 -3
- package/package.json +1 -1
- package/src/styles.css +143 -7
|
@@ -69,7 +69,7 @@ import {
|
|
|
69
69
|
|
|
70
70
|
- Open state is controlled via Radix `value`/`onValueChange`; `type="single"` closes siblings automatically.
|
|
71
71
|
- The trigger is `rounded-md`, underlines on hover, and takes the standard control focus ring (`focus-visible:ring-[3px] ring-ring-focus-strong`).
|
|
72
|
-
- Disabled triggers get `pointer-events-none
|
|
72
|
+
- Disabled triggers get `pointer-events-none` + `text-disabled-default`. No opacity — the trigger has no fill of its own, so the ink carries the state.
|
|
73
73
|
- Expand/collapse uses the `accordion-down`/`accordion-up` keyframes; the chevron transition is 200 ms.
|
|
74
74
|
|
|
75
75
|
## Do / Don't
|
|
@@ -73,7 +73,12 @@ Padding-x = height/2 − 4; gap fixed at 8px and padding does not change when ic
|
|
|
73
73
|
## Behaviour
|
|
74
74
|
|
|
75
75
|
- All shapes are `rounded-full`; icon sizes are perfect circles.
|
|
76
|
-
- Focus: `focus-visible:ring-[3px] ring-ring-focus-strong` + focus border.
|
|
76
|
+
- Focus: `focus-visible:ring-[3px] ring-ring-focus-strong` + focus border.
|
|
77
|
+
- Disabled is **per variant**, never an element `opacity` — that dims the fill and its label together, which is why a disabled `primary` used to lose its label entirely. Each variant keeps its own construction:
|
|
78
|
+
- filled page variants (`primary`, `secondary`, `frosted`) take the disabled glaze (`background-disabled-default` on `background-image`, so the fill stays underneath) plus `text-disabled-default`; `secondary` also drops to `border-disabled-default`
|
|
79
|
+
- fill-less variants (`tertiary`, `tertiary-on-brand`, `secondary-on-brand`) take the ink only — a glaze would paint a surface the variant deliberately doesn't have
|
|
80
|
+
- on-brand variants stay on the static white ladder: `primary-on-brand`'s white pill becomes `white-alpha-24`, `frosted-on-brand` drops from `alpha-24` to `alpha-8`, and the ink is `text-on-brand-disabled-default`
|
|
81
|
+
- `loading` still uses `opacity-24` — a different state, deliberately.
|
|
77
82
|
- Shimmer is a 4s looping gradient sweep — white over `primary`, brand-blue over `primary-on-brand` (white would vanish on white). It is suppressed while `loading` or `disabled` so the button reads as inert.
|
|
78
83
|
- `loading` keeps the button's width: children render invisible, the spinner centres on top, sized per button size.
|
|
79
84
|
- `asChild` passes children straight into Slot — the shimmer/loading layers physically cannot render there.
|
|
@@ -14,6 +14,7 @@ Embla-powered slide track with previous/next controls, horizontal or vertical.
|
|
|
14
14
|
- `CarouselContent` — the overflow-hidden viewport and flex track (`-ml-4` / `-mt-4` gutter offset); `data-slot="carousel-content"`.
|
|
15
15
|
- `CarouselItem` — one slide, `basis-full` with `pl-4` / `pt-4` gutter, `role="group" aria-roledescription="slide"`; `data-slot="carousel-item"`.
|
|
16
16
|
- `CarouselPrevious` / `CarouselNext` — circular icon Buttons positioned outside the track (−48px on the travel axis); `data-slot="carousel-previous"` / `"carousel-next"`.
|
|
17
|
+
- `CarouselDots` — the position indicator. Composes [Pagination](./pagination.md)'s `PaginationDots` / `PaginationBar`, wired to the carousel's own index — Carousel does **not** draw its own dots, so there is one indicator implementation in the system (same reason Dialog composes NavigationButton).
|
|
17
18
|
- `CarouselApi` — exported Embla API type for `setApi`.
|
|
18
19
|
|
|
19
20
|
## API
|
|
@@ -27,6 +28,15 @@ Embla-powered slide track with previous/next controls, horizontal or vertical.
|
|
|
27
28
|
| `plugins` | Embla plugins | — | e.g. autoplay. |
|
|
28
29
|
| `setApi` | `(api: CarouselApi) => void` | — | Escape hatch to the Embla instance. |
|
|
29
30
|
|
|
31
|
+
### CarouselDots
|
|
32
|
+
|
|
33
|
+
| Prop | Type | Default | Notes |
|
|
34
|
+
| --- | --- | --- | --- |
|
|
35
|
+
| `variant` | `"dots" \| "bar"` | `"dots"` | Which Pagination indicator to render. |
|
|
36
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Forwarded to the indicator. |
|
|
37
|
+
|
|
38
|
+
`page`, `total` and `onPageChange` are supplied by the carousel and cannot be overridden. Tapping a dot calls `scrollTo`, so the indicator is interactive by default.
|
|
39
|
+
|
|
30
40
|
### CarouselPrevious / CarouselNext
|
|
31
41
|
|
|
32
42
|
| Prop | Type | Default | Notes |
|
|
@@ -67,7 +77,8 @@ import {
|
|
|
67
77
|
- ArrowLeft/ArrowRight are captured on the wrapper and scroll the track.
|
|
68
78
|
- Vertical orientation rotates the arrows 90° and places them above/below the track.
|
|
69
79
|
- Slide gutter is a fixed 16px built from the `-ml-4`/`pl-4` pair — adjust both together if you must change it.
|
|
70
|
-
- `
|
|
80
|
+
- The context exposes `selectedIndex`, `slideCount` and `scrollTo`, refreshed on Embla's `select`/`reInit` — that's what `CarouselDots` consumes, and what any custom indicator should use.
|
|
81
|
+
- `setApi` still exposes the raw Embla instance for autoplay or anything the context doesn't cover.
|
|
71
82
|
|
|
72
83
|
## Do / Don't
|
|
73
84
|
|
|
@@ -80,3 +91,4 @@ import {
|
|
|
80
91
|
- Don't hide the arrows without providing another affordance (drag alone is not discoverable on desktop).
|
|
81
92
|
- Don't restyle the arrows as squares; icon buttons are circles system-wide.
|
|
82
93
|
- Don't nest a carousel inside another carousel on the same axis.
|
|
94
|
+
- Don't hand-roll carousel dots; render `CarouselDots` so the indicator stays in one place and on the shared tokens.
|
|
@@ -70,7 +70,7 @@ import {
|
|
|
70
70
|
- cmdk filters as you type and moves the highlight (`data-[selected=true]` = the hover glaze); Enter fires `onSelect`.
|
|
71
71
|
- `CommandDialog` hides its title/description for screen readers only, strips content padding, and upsizes input and rows to 48px with 20px icons.
|
|
72
72
|
- Rows are `rounded-md` (8px) per the menu-row radius rule; the shell is `rounded-2xl` standalone (the dialog supplies its own 18px shell).
|
|
73
|
-
- Disabled items: `data-[disabled=true]:pointer-events-none
|
|
73
|
+
- Disabled items: `data-[disabled=true]:pointer-events-none` + `data-[disabled=true]:text-text-disabled-default`.
|
|
74
74
|
- Note the shell radius difference from anchored menus: Command is a modal/embedded surface (18px), while dropdown/context panels are `rounded-lg` (10px).
|
|
75
75
|
|
|
76
76
|
## Do / Don't
|
|
@@ -74,7 +74,7 @@ import {
|
|
|
74
74
|
- Open state, positioning, typeahead, and keyboard navigation are Radix-managed; content mounts in a portal with fade/zoom/slide animations per side.
|
|
75
75
|
- Focused rows take the hover glaze (`focus:bg-background-hover-default`) — not a brand-selected fill; an open sub-trigger holds the same glaze.
|
|
76
76
|
- Destructive rows keep error ink even on their icons (`*:[svg]:text-text-error-default!`).
|
|
77
|
-
- Disabled rows: `data-[disabled]:pointer-events-none
|
|
77
|
+
- Disabled rows: `data-[disabled]:pointer-events-none` + `data-[disabled]:text-text-disabled-default`. Rows have no resting fill, so the ink is the signal.
|
|
78
78
|
- Radius contract: panel `rounded-lg` 10px, rows `rounded-md` 8px.
|
|
79
79
|
|
|
80
80
|
## Do / Don't
|
|
@@ -76,7 +76,7 @@ import {
|
|
|
76
76
|
- Radix manages open state, positioning, collision, typeahead, and keyboard navigation; the panel portals with fade/zoom/slide-per-side animation.
|
|
77
77
|
- Row focus uses the hover glaze (`focus:bg-background-hover-default`), never a brand-selected fill; an open sub-trigger holds the glaze.
|
|
78
78
|
- Destructive rows force error ink on their icons (`*:[svg]:text-text-error-default!`).
|
|
79
|
-
- Disabled rows: `pointer-events-none
|
|
79
|
+
- Disabled rows: `pointer-events-none` + `data-[disabled]:text-text-disabled-default`.
|
|
80
80
|
- Radius contract: panel `rounded-lg` 10px, rows `rounded-md` 8px.
|
|
81
81
|
|
|
82
82
|
## Do / Don't
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
|
|
56
56
|
- Joins the [Field](./field.md) cascade: with no `size` prop it takes the Field's size; an explicit prop wins. `size="default"` always resolves to `md` regardless of context.
|
|
57
57
|
- `appearance-none` + the injected chevron replace the platform arrow; right padding (`pr-9`, lg `pr-10`) reserves its space.
|
|
58
|
-
- Disabled
|
|
58
|
+
- Disabled sets `text-disabled-default` on the wrapper via `has-[select:disabled]:`, lays the disabled glaze (`background-disabled-default` on `background-image`, so the fill stays underneath) on the select, and kills pointer events. The wrapper is no longer dimmed as a group.
|
|
59
59
|
- The wrapper is `w-fit` and `className` lands on the inner `<select>` — for full-width layouts you must widen the wrapper (e.g. a `w-full` parent with `[&>[data-slot=native-select-wrapper]]:w-full`), not just the select.
|
|
60
60
|
|
|
61
61
|
## Do / Don't
|
|
@@ -44,7 +44,7 @@ import { ChevronLeftIcon } from "lucide-react"
|
|
|
44
44
|
**Colour (semantic):**
|
|
45
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
|
+
- frosted-on-brand (alpha primitives — the sanctioned glass use): fill `--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`. **No outline** — it matched Button's frost recipe, and a border at the same alpha as the fill only double-draws the silhouette
|
|
48
48
|
|
|
49
49
|
**Type (private):** none — icon-only, no text.
|
|
50
50
|
|
|
@@ -52,7 +52,7 @@ import { ChevronLeftIcon } from "lucide-react"
|
|
|
52
52
|
|
|
53
53
|
- Always a circle (`rounded-full`), per the icon-button law.
|
|
54
54
|
- Press feedback: default variant uses `active:opacity-60`; glass replaces it (`active:opacity-100`) with the thinner alpha-16 frost so the blur never flickers.
|
|
55
|
-
- Disabled is `
|
|
55
|
+
- Disabled is the disabled glaze (`background-disabled-default` on `background-image`, so the fill stays underneath) plus `text-disabled-default`, with pointer-events none — the tinted circle keeps its fill and drains, rather than the whole element being dimmed.
|
|
56
56
|
- Transitions run `duration-fast ease-standard`.
|
|
57
57
|
|
|
58
58
|
## Do / Don't
|
|
@@ -16,6 +16,8 @@ Page navigation as a row of 40px circles — the active page lit with the brand-
|
|
|
16
16
|
- `PaginationLink` — a page number: `size-10` circle `<a>`. `data-slot="pagination-link"`, `data-active`.
|
|
17
17
|
- `PaginationPrevious` / `PaginationNext` — chevron links rendered as tertiary icon Buttons (`variant="tertiary" size="icon-md"`, 20px chevrons). `data-slot="pagination-link"`.
|
|
18
18
|
- `PaginationEllipsis` — non-interactive `size-10` cell with `MoreHorizontalIcon` + sr-only "More pages". `data-slot="pagination-ellipsis"`.
|
|
19
|
+
- `PaginationDots` — compact position indicator: one `<button>` per page, active one widened into a pill. `data-slot="pagination-dots"` / `"pagination-dot"`, `data-active`.
|
|
20
|
+
- `PaginationBar` — the segmented form of the same thing: equal-width bars, only the current one filled. `data-slot="pagination-bar"` / `"pagination-segment"`, `data-active`.
|
|
19
21
|
|
|
20
22
|
## API
|
|
21
23
|
|
|
@@ -27,9 +29,40 @@ Page navigation as a row of 40px circles — the active page lit with the brand-
|
|
|
27
29
|
|
|
28
30
|
`PaginationPrevious`/`PaginationNext` accept `<a>` props (`href`, etc.); the aria-labels "Go to previous page"/"Go to next page" are built in.
|
|
29
31
|
|
|
32
|
+
### PaginationDots / PaginationBar
|
|
33
|
+
|
|
34
|
+
| Prop | Type | Default | Notes |
|
|
35
|
+
| --- | --- | --- | --- |
|
|
36
|
+
| `page` | `number` | — | **Zero-based** current page. Controlled — these hold no state. |
|
|
37
|
+
| `total` | `number` | — | Number of pages. |
|
|
38
|
+
| `onPageChange` | `(page: number) => void` | — | Fired on click. Omit for a display-only indicator. |
|
|
39
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Matches Progress's dot width ladder. |
|
|
40
|
+
| `label` | `string` | `"Pagination"` | `aria-label` on the `<nav>`. |
|
|
41
|
+
|
|
42
|
+
They render `<button aria-current="page">`, **not** the numbered variant's `<a href>` — they jump within a view rather than navigating to a URL.
|
|
43
|
+
|
|
30
44
|
## Variants & sizes
|
|
31
45
|
|
|
32
|
-
|
|
46
|
+
Three forms of one idea — *which of N am I on*:
|
|
47
|
+
|
|
48
|
+
| Form | Shape | Use for |
|
|
49
|
+
| --- | --- | --- |
|
|
50
|
+
| numbered | 40px circular cells | paged tables and lists, where the page number matters |
|
|
51
|
+
| dots | 6–10px dots, active widened to a pill | carousels, onboarding, swipeable stacks — small bounded sets |
|
|
52
|
+
| bar | equal-width 4px segments | the same, where a linear read suits the layout better |
|
|
53
|
+
|
|
54
|
+
Numbered has one size (40px cells). Dots and bar take `sm` / `md` / `lg`.
|
|
55
|
+
|
|
56
|
+
**Not the same as Progress `dots`.** Progress fills segments *cumulatively* (2 of 5 done); these mark a *single* position (you are on 2 of 5). Identical geometry, opposite meaning — the metrics are deliberately shared so the two read as one system, but don't substitute one for the other.
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
import { PaginationDots, PaginationBar } from "@trading-game/design-intelligence-layer"
|
|
60
|
+
|
|
61
|
+
const [page, setPage] = useState(0)
|
|
62
|
+
|
|
63
|
+
<PaginationDots page={page} total={5} onPageChange={setPage} />
|
|
64
|
+
<PaginationBar page={page} total={5} size="lg" onPageChange={setPage} />
|
|
65
|
+
```
|
|
33
66
|
|
|
34
67
|
```tsx
|
|
35
68
|
import {
|
|
@@ -64,12 +97,16 @@ import {
|
|
|
64
97
|
|
|
65
98
|
**Type (private):** `--pagination-*`: 14 / semibold / 20 (`type-pagination`, `font-display`).
|
|
66
99
|
|
|
100
|
+
**Dots / bar colour:** active `bg-background-brand-default`; inert `bg-background-secondary-surface`, hover `bg-background-brand-selected`; focus 3px `ring-ring-focus-strong`. No component-layer colour — the geometry is Tailwind utilities matching Progress's `DOT_WIDTHS`.
|
|
101
|
+
|
|
67
102
|
## Behaviour
|
|
68
103
|
|
|
69
104
|
- Pure links — no internal state; the consumer renders the right `isActive` page and hrefs.
|
|
70
105
|
- `isActive` drives both `aria-current="page"` and `data-active` plus the selected paint.
|
|
71
106
|
- Every cell carries a border (transparent at rest) so the active border-color change never shifts layout.
|
|
72
107
|
- Prev/next are real Buttons via `asChild`, so they inherit Button's hover/press/disabled behaviour.
|
|
108
|
+
- The numbered variant is uncontrolled navigation (links); **dots and bar are controlled** — pass `page` and handle `onPageChange`, or they will never move.
|
|
109
|
+
- Carousel drives them through [`CarouselDots`](./carousel.md), which supplies its own index. The indicator lives here so there is one implementation; Carousel composes it rather than drawing its own.
|
|
73
110
|
|
|
74
111
|
## Do / Don't
|
|
75
112
|
|
|
@@ -82,3 +119,5 @@ import {
|
|
|
82
119
|
- 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
120
|
- Don't turn page links into `<button>`s when they change the URL — they are navigation.
|
|
84
121
|
- Don't drop the transparent rest border; you'll get a 1px jump when the active ring appears.
|
|
122
|
+
- Don't reach for Progress `dots` as a position indicator, or `PaginationDots` as a progress meter — same shape, different meaning.
|
|
123
|
+
- Don't rely on colour alone in the dots variant; the active dot widens to a pill on purpose.
|
|
@@ -24,7 +24,7 @@ Swipe-to-commit control for irreversible money actions — withdrawals, transfer
|
|
|
24
24
|
| `label` | `ReactNode` | `"Slide to confirm"` | Track label; fades as the handle travels. |
|
|
25
25
|
| `confirmedLabel` | `ReactNode` | `"Confirmed"` | |
|
|
26
26
|
| `confirmed` | `boolean` | — | Controlled confirmed state — pass `false` to reset from outside. |
|
|
27
|
-
| `disabled` | `boolean` | `false` | `
|
|
27
|
+
| `disabled` | `boolean` | `false` | The disabled glaze + `text-disabled-default`, pointer events off. |
|
|
28
28
|
|
|
29
29
|
```tsx
|
|
30
30
|
import { SlideToConfirm } from "@trading-game/design-intelligence-layer"
|
|
@@ -47,7 +47,7 @@ import { Slider } from "@trading-game/design-intelligence-layer"
|
|
|
47
47
|
- Thumb count is derived from the value array; an uncontrolled slider with no `defaultValue` renders two thumbs at `[min, max]`.
|
|
48
48
|
- The thumb is a `rounded-full size-4` brand circle with `border-0`; hover and keyboard focus both show a 4px `ring-ring-focus-strong` halo.
|
|
49
49
|
- Vertical orientation flips the layout (`flex-col`, `w-1.5` track) and enforces `min-h-44`.
|
|
50
|
-
- Disabled state:
|
|
50
|
+
- Disabled state: the disabled glaze (`background-disabled-default` on `background-image`, so the fill stays underneath) over the track/range/thumb assembly plus `text-disabled-default`, pointer events off on thumbs. Previously one `opacity-24` layer over the whole assembly, which compounded.
|
|
51
51
|
|
|
52
52
|
## Do / Don't
|
|
53
53
|
|
|
@@ -20,7 +20,7 @@ Binary on/off toggle (Radix Switch) with a sliding white thumb.
|
|
|
20
20
|
| `size` | `"sm" \| "default"` | `"default"` | default: 24px tall (`h-6 w-11`, 18px thumb); sm: 20px tall (`h-5 w-9`, 14px thumb). |
|
|
21
21
|
| `checked` / `defaultChecked` | `boolean` | — | Controlled / uncontrolled. |
|
|
22
22
|
| `onCheckedChange` | `(checked: boolean) => void` | — | Radix callback. |
|
|
23
|
-
| `disabled` | `boolean` | — | `cursor-not-allowed
|
|
23
|
+
| `disabled` | `boolean` | — | `cursor-not-allowed` + `text-disabled-default`. The track fill is left alone so a disabled-checked switch still reads as on. |
|
|
24
24
|
|
|
25
25
|
## Variants & sizes
|
|
26
26
|
|
|
@@ -43,7 +43,7 @@ import { Textarea } from "@trading-game/design-intelligence-layer"
|
|
|
43
43
|
- `field-sizing-content` grows the field with its content, floored at `min-h-16` (64px) — no JS auto-resize needed.
|
|
44
44
|
- Focus uses the soft text-field ring (`ring-focus-soft`), matching Input, not the 50% control ring.
|
|
45
45
|
- Error state is attribute-driven: set `aria-invalid` and the border/ring switch to the error tokens.
|
|
46
|
-
- Disabled renders `cursor-not-allowed
|
|
46
|
+
- Disabled renders `cursor-not-allowed`, the disabled glaze (`background-disabled-default` on `background-image`, so the fill stays underneath), and `text-disabled-default`.
|
|
47
47
|
|
|
48
48
|
## Do / Don't
|
|
49
49
|
|
package/docs/components/toast.md
CHANGED
|
@@ -64,3 +64,93 @@ toast.error("Deposit failed", {
|
|
|
64
64
|
- Don't restyle toasts onto ordinary surfaces — the inverse surface is what separates them from page content without a shadow.
|
|
65
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
66
|
- Don't stack meaning into colour alone — the icon set (check/info/triangle/octagon) carries severity.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
# Result Snackbar
|
|
71
|
+
|
|
72
|
+
The settlement surface — a floating-chrome glass card reporting the money on a contract the player was **not** watching when it ended. Exported from this component alongside `Toaster`/`toast`, which it deliberately does not resemble.
|
|
73
|
+
|
|
74
|
+
## Why it isn't a Toast variant
|
|
75
|
+
|
|
76
|
+
The two are different jobs, so they are different surfaces on purpose — do not "unify" them:
|
|
77
|
+
|
|
78
|
+
| | `Toaster` / `toast` | `ResultSnackbar` |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| Job | transient system messaging | reports a settled figure |
|
|
81
|
+
| Surface | inverse, borderless, `var(--radius)` | floating-chrome glass, `--radius-2xl` |
|
|
82
|
+
| Dwell | 4s, sonner-stacked | consumer-set (2.5s default), one at a time |
|
|
83
|
+
| Content | title + description + action | illustration + signed figure + terms + dwell timer |
|
|
84
|
+
| Placement | fixed by the Toaster | consumer-anchored via `className` |
|
|
85
|
+
|
|
86
|
+
Rise/Fall and Digits both need this and had built it twice; the queue logic being duplicated is what made it a design-system concern.
|
|
87
|
+
|
|
88
|
+
## When to use
|
|
89
|
+
|
|
90
|
+
**Only for a settled game result — a win or a loss — that the player was not watching.** That is the entire remit. `outcome` has two values because there are only two cases.
|
|
91
|
+
|
|
92
|
+
- A background settle, with other contracts still running.
|
|
93
|
+
- A contract that ran on an instrument the player has since navigated away from.
|
|
94
|
+
|
|
95
|
+
**When not — use `toast` instead:**
|
|
96
|
+
|
|
97
|
+
- **Refunds.** Nothing was won or lost, so there is no figure to report. A refund is a non-event.
|
|
98
|
+
- **Trade rejections, live payout warnings, connection problems** — every other notification.
|
|
99
|
+
- Anything needing a response. This surface is pointer-transparent by design: it reports, it is not a control.
|
|
100
|
+
|
|
101
|
+
**And not for the contract the player *was* watching** as it ended — that gets the full-screen result moment. This one deliberately does not take the board.
|
|
102
|
+
|
|
103
|
+
It is never the surface of record either: every result also lands in positions and history, which is where a player goes to study terms.
|
|
104
|
+
|
|
105
|
+
## Anatomy
|
|
106
|
+
|
|
107
|
+
- Card — `w-max`, `rounded-2xl`, floating-chrome glass (`--background-chrome-default` + `--border-chrome-default`, `blur(20px) saturate(1.4)`, `--shadow-chrome-default` with a `--chrome-highlight-default` specular top edge), `py-2 pr-4 pl-2`, `gap-3`. Pointer-transparent — it reports, it is not a control. `data-slot="result-snackbar"`, `data-outcome`, `data-leaving`.
|
|
108
|
+
- Illustration slot — fixed `size-10` box so every game's artwork occupies identical space and the text never shifts. `data-slot="result-snackbar-icon"`.
|
|
109
|
+
- Amount — the primary line: signed figure in outcome ink, tabular, with the currency inline one step down. `type-result-snackbar-amount` / `-currency`.
|
|
110
|
+
- Terms — one muted line, direction and duration joined by a middot. `type-result-snackbar-terms`.
|
|
111
|
+
- Drain — 4px outcome-coloured band on the bottom edge, shrinking for the life of the card, so dismissal reads as a timer expiring rather than the message being lost. `data-slot="result-snackbar-drain"`.
|
|
112
|
+
|
|
113
|
+
## API
|
|
114
|
+
|
|
115
|
+
| Prop | Type | Default | Notes |
|
|
116
|
+
| --- | --- | --- | --- |
|
|
117
|
+
| `outcome` | `"win" \| "loss"` | required | Drives figure ink, drain colour, and the icon's twist direction. |
|
|
118
|
+
| `amount` | `string` | required | Pre-formatted **and pre-signed** — `"+8.40"` / `"−5.00"`. Rounding and the true Unicode minus are product decisions. |
|
|
119
|
+
| `currency` | `string` | required | Rendered inline after the figure, one step down. |
|
|
120
|
+
| `contractLabel` | `string` | required | What was traded — "Rise", "Matches 5". |
|
|
121
|
+
| `duration` | `string` | required | How long it ran — "15 seconds", "5 ticks". |
|
|
122
|
+
| `icon` | `ReactNode` | — | Illustration for the 40px box. Mark decorative artwork `aria-hidden` at the call site. |
|
|
123
|
+
| `amountValue` | `number` | — | Count-up target (absolute value). Omit for a static figure. The last frame always snaps to `amount` verbatim, so the animation can never leave a rounding artefact. |
|
|
124
|
+
| `placement` | `"top" \| "bottom"` | `"top"` | Which edge it travels from and returns to. |
|
|
125
|
+
| `durationMs` | `number` | `2500` | Time on screen. Short on purpose: results arrive in bursts, and a longer dwell backs the queue up until the card is describing rounds the player has left. |
|
|
126
|
+
| `onDismiss` | `() => void` | — | Fires once the exit finishes — never earlier, never twice. Advance a queue here. |
|
|
127
|
+
| `announcement` | `string` | composed | Sentence for assistive technology. Override for outcomes the default wording does not suit. |
|
|
128
|
+
|
|
129
|
+
## Tokens
|
|
130
|
+
|
|
131
|
+
**Colour (semantic):** surface `--background-chrome-default`, edge `--border-chrome-default`, lift `--shadow-chrome-default` + `--chrome-highlight-default`; figure `text-text-success-default` / `text-text-error-default`; drain `background-success-solid` / `background-error-solid`; terms `text-text-subtle-default`.
|
|
132
|
+
|
|
133
|
+
**Type (private):** `--result-snackbar-amount-*` 18 / **bold 700** / 24 · `--result-snackbar-currency-*` 14 / semibold / 20 · `--result-snackbar-terms-*` 12 / semibold / 16.
|
|
134
|
+
|
|
135
|
+
> The amount is the system's **one sanctioned bold-700 component exception** — the settled figure is the whole reason the surface exists and must carry at a glance against a moving chart. Documented on the [Typography](../foundations/typography.md) page. Do not treat it as precedent.
|
|
136
|
+
|
|
137
|
+
## Behaviour
|
|
138
|
+
|
|
139
|
+
- Placement is the consumer's: the meaningful anchor differs per game (Rise/Fall docks it under the header's positions button so a missed result reads as coming *out of* the positions list). Give the positioned ancestor `relative` and pass offsets via `className`.
|
|
140
|
+
- Queueing is the consumer's too. This shows one settlement and reports completion via `onDismiss`; deciding what waits, what is stale, and what replays after an interruption is product logic.
|
|
141
|
+
- `onDismiss` is guaranteed exactly once per mounted card, whether it timed out or the consumer unmounted it early to make way for something louder — without that, a queue either stalls or skips.
|
|
142
|
+
- Motion: the card travels in from beyond its own height with a slight overshoot; the illustration pops 150ms later so the card lands first, untwisting in its outcome's direction; the figure counts up (500ms for a win, 250ms for a loss — a loss is stated, not savoured).
|
|
143
|
+
- **Reduced motion** is honoured across every moving part: the card cross-fades instead of travelling, the illustration appears at full size, the figure is final immediately, and the drain is shown already spent rather than animating. The shipped Rise/Fall version animated the drain regardless; that gap is closed here.
|
|
144
|
+
- Announced politely, not assertively — a settlement the player was not watching is news, not an interruption of what they are doing now. The `sr-only` sentence exists because a figure/colour pairing is unreadable to assistive technology.
|
|
145
|
+
|
|
146
|
+
## Do / Don't
|
|
147
|
+
|
|
148
|
+
**Do**
|
|
149
|
+
- Pass the illustration in; the component owns the box, not the artwork, so Digits can pass a settlement digit where Rise/Fall passes thumbs.
|
|
150
|
+
- Give `amountValue` when you want the count-up, and keep `amount` as the canonical string.
|
|
151
|
+
- Anchor it to something meaningful in the layout rather than an arbitrary inset.
|
|
152
|
+
|
|
153
|
+
**Don't**
|
|
154
|
+
- Don't use it for refunds — nothing was won or lost, and this surface exists to report a figure.
|
|
155
|
+
- Don't reach for `shadow-chrome-default` on surfaces that aren't floating over live content; the token names the floating-chrome class.
|
|
156
|
+
- Don't drive it as a stack — one settlement at a time is the contract; the queue belongs to the consumer.
|
|
@@ -77,7 +77,7 @@ import { Star } from "lucide-react"
|
|
|
77
77
|
- Radius is `rounded-md` (8px, the row radius); icons default to `size-4`.
|
|
78
78
|
- `spacing={0}` (default) joins segments: items lose their own radius (`rounded-none`), the first/last regain `rounded-l-md`/`rounded-r-md`, and segments share hairlines by dropping `border-l` on all but the first; any positive `spacing` separates them and each keeps `rounded-md`.
|
|
79
79
|
- 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.
|
|
80
|
-
- Disabled: `pointer-events-none
|
|
80
|
+
- Disabled: `pointer-events-none` + `text-disabled-default`. The pressed fill is left alone so a disabled-pressed toggle still reads as pressed.
|
|
81
81
|
|
|
82
82
|
## Do / Don't
|
|
83
83
|
|
|
@@ -14,9 +14,10 @@ Token grammar (Quill style): `{element}-{role}-{variant}` — e.g. `background-b
|
|
|
14
14
|
|
|
15
15
|
1. **Semantic tokens only in components.** A raw primitive is allowed only for alpha glazes (hovers, glass, scrims) — e.g. `bg-(--primitive-white-alpha-24)` for glass.
|
|
16
16
|
2. **Surfaces are always solid.** Alpha values are for *state layers on top of* a solid base (hover glazes, pressed glazes, glass, scrims, washes) — never for a resting surface. Dark surfaces are built as `color-mix()` of white over the canvas, producing a solid colour.
|
|
17
|
-
3. **
|
|
18
|
-
4. **
|
|
19
|
-
5. **
|
|
17
|
+
3. **The inverse family *is* the opposite theme's page ladder.** `text-{prominent,subtle,disabled}-inverse` carry exactly the values the other theme's page inks carry — light inverse == dark page (mono 50 · 600 · 900), dark inverse == light page (mono 1100 · 800 · 400). It is a mirror by definition, not a separate palette, so never invent a bespoke construction for it.
|
|
18
|
+
4. **Dark is not a mirror.** When a light construction fails on near-black (a 10% wash disappears, mid-blue ink loses contrast), the dark value diverges — that's what the `.dark` block is for. Never hardcode a light construction and assume it flips.
|
|
19
|
+
5. **Selection states use the `*-brand-selected` family** — never `brand-default/10` washes or `text-brand-default` ink for a selected/active element.
|
|
20
|
+
6. **No shadows anywhere,** bar two named tokens (`shadow-chrome-default` for the floating-chrome class — Bottom Navigation and Result Snackbar; `shadow-brand-glow` for the Slide to Confirm handle). Elevation is hairline borders and surface steps.
|
|
20
21
|
|
|
21
22
|
## Primitive scales
|
|
22
23
|
|
|
@@ -45,6 +46,7 @@ All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canva
|
|
|
45
46
|
| `background-tinted-canvas` | blue-600 12%→4% mixed into the canvas + a blue-400 aurora | blue-400 14% into the canvas + aurora | The Refined screen background — a layered gradient, consume via `background: var(--background-tinted-canvas)` on the page root only; ends on `primary-canvas` in both themes so long pages melt in seamlessly |
|
|
46
47
|
| `background-primary-surface` | mono-50 `#FFFFFF` | white 6% over canvas ≈ `#0F0F2C` | Cards, panels, menus, inputs, sheets — the default solid surface |
|
|
47
48
|
| `background-secondary-surface` | mono-100 `#F1F1F1` | white 12% over canvas ≈ `#1F1F3A` | One step up: muted fills, tracks, icon tiles, read-only fields |
|
|
49
|
+
| `background-disabled-default` | white-alpha-24 | black-alpha-50 | **The disabled glaze** — an alpha layer laid *over* whatever fill a control already has, so every variant keeps its identity while draining. Consume it on `background-image` (`[background-image:linear-gradient(var(--background-disabled-default),var(--background-disabled-default))]`), never `background-color`, or it replaces the fill instead of washing it. Polarity is the inverse of hover — hover advances (black in light, white in dark), disabled recedes (white in light, black in dark) — and the strengths differ because the ground does: white moves fast against a near-white page, black slowly against a near-black canvas |
|
|
48
50
|
| `background-hover-default` | black-alpha-4 | white-alpha-12 | The hover glaze — laid over any solid surface |
|
|
49
51
|
| `background-pressed-default` | black-alpha-8 | white-alpha-20 | The pressed glaze |
|
|
50
52
|
| `background-brand-default` | blue-600 `#2323FF` | blue-400 `#4D6BFF` | Primary CTAs, checked controls, solid selection chips |
|
|
@@ -67,13 +69,17 @@ All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canva
|
|
|
67
69
|
| Token | Light | Dark | Job |
|
|
68
70
|
|---|---|---|---|
|
|
69
71
|
| `text-prominent-default` | mono-1100 `#1C1C1C` | mono-50 `#FFFFFF` | Titles, values, primary content |
|
|
70
|
-
| `text-subtle-default` | mono-
|
|
71
|
-
| `text-disabled-default` | mono-
|
|
72
|
+
| `text-subtle-default` | mono-800 `#717171` | mono-600 `#AAAAAA` | Descriptions, labels, placeholders, secondary content. Light moved one step lighter than mono-900 to widen the drop off prominent from 2.3× to 3.5× (4.9:1 on surface, 4.6:1 on canvas — AA clear; 4.3:1 on `secondary-surface` is a marginal miss, accepted because mono-900 collapses the drop back). Dark is unchanged at mono-600 |
|
|
73
|
+
| `text-disabled-default` | mono-400 | mono-900 | Disabled ink — 1.7:1 light / 2.5:1 dark. Reads as *absent* rather than quiet, so the state is carried by the disabled glaze beneath it, not by the ink alone. WCAG 1.4.3 exempts inactive controls from contrast minima |
|
|
72
74
|
| `text-prominent-inverse` | mono-50 | mono-1100 | Ink on `background-inverse-surface` |
|
|
75
|
+
| `text-subtle-inverse` | mono-600 | mono-800 | Supporting copy on the inverse surface (Toast description) |
|
|
76
|
+
| `text-disabled-inverse` | mono-900 | mono-400 | Disabled ink on the inverse surface |
|
|
73
77
|
| `text-brand-default` | blue-600 | blue-400 `#4D6BFF` | Links, brand-coloured text on the canvas |
|
|
74
78
|
| `text-brand-hover` | blue-700 `#0606C7` | blue-300 `#7392FF` | Hover step for brand text (links, menu hover ink) |
|
|
75
79
|
| `text-brand-selected` | blue-600 | blue-200 `#99B7FF` | Ink on selected/active elements — two stops lighter in dark for contrast |
|
|
76
80
|
| `text-on-brand-static` | mono-50 | static | White text on brand fills — never flips |
|
|
81
|
+
| `text-on-brand-subtle-static` | white-alpha-64 | static | Secondary copy on a brand fill. On brand the ladder **inverts** — it descends from white through the alpha rungs instead of climbing the mono ramp (3.8:1 on blue-600, 2.7:1 on blue-400) |
|
|
82
|
+
| `text-on-brand-disabled-default` | white-alpha-32 | white-alpha-24 | Disabled ink on a brand fill — 1.8:1 over blue-600, 1.5:1 over blue-400. **Not static**, unlike the rest of the on-brand family: the fill itself flips blue-600 → blue-400, and blue-400 caps white at 4.3:1, so the rung comes down with it. Replaces the `opacity-24` wash on on-brand variants |
|
|
77
83
|
| `text-brand-container-static` | blue-600 | static | Ink on `background-brand-container` |
|
|
78
84
|
| `text-static-black` | mono-1100 | static | Ink on `background-static-white` |
|
|
79
85
|
| `text-success-default` / `-error-` / `-warning-` / `-information-` | status 600 | **static — same in both themes** (deliberate call) | Status text |
|
|
@@ -88,7 +94,9 @@ All scales run 50→1000 (mono 50→1200; blue adds 25 and 1000 as the two canva
|
|
|
88
94
|
| `border-default-default` | mono-200 `#E3E3E3` | white-alpha-12 | The hairline — every card, input, menu, divider |
|
|
89
95
|
| `border-prominent-default` | mono-1100 | mono-50 | Heavy outline (outline toggles) |
|
|
90
96
|
| `border-brand-selected` | blue-600 | blue-300 `#7392FF` | Border on selected elements (outline chip, active page) |
|
|
91
|
-
| `border-neutral-default` | mono-500 | white-alpha-
|
|
97
|
+
| `border-neutral-default` | mono-500 | white-alpha-24 | Soft hairline for neutral tint chips; also the Steps connector lines |
|
|
98
|
+
| `border-control-default` | mono-400 | white-alpha-20 | The outline edge on a bordered control (Button `secondary`). One step firmer than the shared hairline — a control's edge has to hold its own label. |
|
|
99
|
+
| `border-disabled-default` | mono-300 | white-alpha-8 | The edge of a disabled control — one step quieter than the enabled hairline, kept so the shape still reads. The glaze washes the fill; a border is a separate colour and needs its own rung |
|
|
92
100
|
| `border-success-default` / `-error-` / `-warning-` / `-information-` | status 600 | static — same | Status outlines |
|
|
93
101
|
| `ring-focus-default` | blue-600 | blue-400 | Base focus ink; `ring-focus-strong` (50% mix) is the 3px control ring, `ring-focus-soft` (8%) the text-field ring |
|
|
94
102
|
| `ring-error-default` / `ring-error-soft` | red-600 / 20% mix | same | Invalid-field ring |
|
|
@@ -53,4 +53,4 @@ Tokens: `--semantic-layout-grid-columns`, `--semantic-layout-gutter`, `--semanti
|
|
|
53
53
|
|
|
54
54
|
## Elevation
|
|
55
55
|
|
|
56
|
-
Inline components carry none — **no shadows** — every component's `shadow-*` has been removed. There are exactly two sanctioned exceptions, both per the Refined Mobile file. **
|
|
56
|
+
Inline components carry none — **no shadows** — every component's `shadow-*` has been removed. There are exactly two sanctioned exceptions, both per the Refined Mobile file. **The floating-chrome class**: a soft ambient lift (`shadow-chrome-default`, `0 12px 30px`) separates chrome from the live content beneath it — the Bottom Navigation, and the Result Snackbar sitting over a chart. The token names the class, not one component. **The Slide to Confirm handle**: a brand glow (`shadow-brand-glow`, `0 6px 16px` of `background-brand-default` at 35%, so it re-derives per theme) so a committed money action reads as liftable off its track. Neither token may be reused elsewhere. Elevation reads through the hairline (`border-default-default`) and surface steps (canvas → primary surface → secondary surface). On dark this is why surfaces are solid white-mixes: the step between `#00001F`, `#0F0F2C`, `#1F1F3A` is the elevation.
|
|
@@ -12,7 +12,7 @@ Layer 3 Component --button-font-size-md, --dialog-title-font-size (private
|
|
|
12
12
|
|
|
13
13
|
## Hard rules
|
|
14
14
|
|
|
15
|
-
1. **No bold (700) in components.** Semibold **600 is the maximum component weight** — Button, Chip, Badge, active states, everything. The 700/800 primitives exist only for the foundation heading scale (H1/H2, display).
|
|
15
|
+
1. **No bold (700) in components.** Semibold **600 is the maximum component weight** — Button, Chip, Badge, active states, everything. The 700/800 primitives exist only for the foundation heading scale (H1/H2, display), plus **one sanctioned component exception**: the Result Snackbar's amount (`--result-snackbar-amount-font-weight`) keeps bold 700, because the settled figure is the whole reason that surface exists and has to carry at a glance against a moving chart. It is named here so it stays one exception rather than a precedent.
|
|
16
16
|
2. **Every size traces to a primitive step.** No raw `text-[13px]`; no Tailwind `text-sm` in component code — components use their `.type-<component>-*` class.
|
|
17
17
|
3. **`.type-*` classes are plain CSS, not Tailwind utilities.** Two consequences:
|
|
18
18
|
- They **cannot be gated by variants** — `data-[state=active]:type-foo` silently does nothing. Pick the class in JSX, or write a CSS rule keyed on the data attribute.
|
|
@@ -22,7 +22,7 @@ Layer 3 Component --button-font-size-md, --dialog-title-font-size (private
|
|
|
22
22
|
|
|
23
23
|
- **Sizes (px)**: 10 · 12 · 14 · 16 · 18 · 20 · 22 · 24 · 26 · 30 · 32 · 36 · 40 · 56
|
|
24
24
|
- **Line heights (px)**: 16 · 20 · 24 · 28 · 32 · 36 · 40 · 48 · 64 — standard pairings: 12→16, 14→20, 16→24, 18→24, 20→28
|
|
25
|
-
- **Weights**: 400 regular · 500 medium · 600 semibold · 700 bold (foundation headings
|
|
25
|
+
- **Weights**: 400 regular · 500 medium · 600 semibold · 700 bold (foundation headings, plus the Result Snackbar amount) · 800 extrabold (display only)
|
|
26
26
|
- **Tracking**: tightest −0.03em (display) → normal 0 (h5/body) → widest +0.12em (overline)
|
|
27
27
|
|
|
28
28
|
## Foundation scale (page content — classes `.display`, `.h1` … `.h6`, `.body-lg/md/sm`, `.label-text`, `.caption`, `.overline`)
|
package/docs/patterns/menus.md
CHANGED
|
@@ -17,7 +17,7 @@ Panels are one radius step below cards (10px vs 18px) — that difference is wha
|
|
|
17
17
|
type-menu-item (14/regular/20) · rounded-md (8px) · px-2 py-1.5
|
|
18
18
|
focus/hover → bg-background-hover-default + text-text-prominent-default
|
|
19
19
|
icons → text-icon-subtle-default, size-4
|
|
20
|
-
disabled →
|
|
20
|
+
disabled → `text-disabled-default` ink (menu rows have no resting fill, so the ink is the whole signal)
|
|
21
21
|
destructive rows → text-text-error-default + focus:bg-background-error-default
|
|
22
22
|
checked/selected ink → text-text-brand-selected
|
|
23
23
|
```
|
|
@@ -12,7 +12,7 @@ alwaysApply: true
|
|
|
12
12
|
|
|
13
13
|
### Available components (check this list first)
|
|
14
14
|
|
|
15
|
-
Accordion, Alert, AlertDialog, AspectRatio, Avatar, AvatarGroup, Badge, **Banner**, Breadcrumb, Button, Calendar, Card, Carousel, Chart, Checkbox, **Chip**, Collapsible, Combobox, Command, ContextMenu, Dialog, Direction, Drawer, DropdownMenu, Empty, Field, Form, HoverCard, Input, InputGroup, InputOTP, Item, Kbd, Label, **Link**, Menubar, NativeSelect, NavigationButton, NavigationMenu, Pagination, Popover, Progress, RadioGroup, Resizable, ScrollArea, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Spinner, **Stepper**, Switch, Table, Tabs, Textarea, **TicketCard / CreditTicketCard**, Toast/Toaster, Toggle, ToggleGroup, Tooltip
|
|
15
|
+
Accordion, Alert, AlertDialog, AspectRatio, Avatar, AvatarGroup, Badge, **Banner**, Breadcrumb, Button, Calendar, Card, Carousel, Chart, Checkbox, **Chip**, Collapsible, Combobox, Command, ContextMenu, Dialog, Direction, Drawer, DropdownMenu, Empty, Field, Form, HoverCard, Input, InputGroup, InputOTP, Item, Kbd, Label, **Link**, Menubar, NativeSelect, NavigationButton, NavigationMenu, Pagination, Popover, Progress, RadioGroup, Resizable, ScrollArea, Select, Separator, Sheet, Sidebar, Skeleton, Slider, Spinner, **Stepper**, Switch, Table, Tabs, Textarea, **TicketCard / CreditTicketCard**, Toast/Toaster, **ResultSnackbar**, Toggle, ToggleGroup, Tooltip
|
|
16
16
|
|
|
17
17
|
### Decision flow
|
|
18
18
|
|
|
@@ -82,8 +82,8 @@ import { Button, Card, Badge } from "@trading-game/design-intelligence-layer"
|
|
|
82
82
|
✅ text-on-semantic-loss — paired with bg-semantic-loss (white)
|
|
83
83
|
✅ text-on-semantic-warning — paired with bg-semantic-warning (white)
|
|
84
84
|
✅ text-on-semantic-boost — paired with bg-semantic-boost (dark amber #713813)
|
|
85
|
-
✅ text-on-subtle — secondary text (
|
|
86
|
-
✅ text-on-disabled — disabled / inactive text (light
|
|
85
|
+
✅ text-on-subtle — secondary text (aliases text-subtle-default: mono-800 #717171 light / mono-600 #AAAAAA dark)
|
|
86
|
+
✅ text-on-disabled — disabled / inactive text (aliases text-disabled-default: mono-400 #C6C6C6 light / mono-900 #555555 dark)
|
|
87
87
|
|
|
88
88
|
✅ text-primary — brand blue #2323FF — inline brand text over neutral surfaces
|
|
89
89
|
✅ text-semantic-win — green — profit/positive inline text
|
|
@@ -178,6 +178,48 @@ If the text lives in a component slot, USE THE COMPONENT — every component car
|
|
|
178
178
|
|
|
179
179
|
---
|
|
180
180
|
|
|
181
|
+
## Rule 3.6 — Result Snackbar is for a settled game result, and nothing else
|
|
182
|
+
|
|
183
|
+
`ResultSnackbar` exists for exactly one thing: reporting a **settled contract the player was not watching** — a win or a loss, with its figure.
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
Did a contract just settle, win or loss, off-screen?
|
|
187
|
+
YES → ResultSnackbar
|
|
188
|
+
NO → toast(...) // everything else
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Use `toast(...)` — never `ResultSnackbar` — for:
|
|
192
|
+
|
|
193
|
+
- **Refunds.** Nothing was won or lost, so there is no figure to report. A refund is a non-event.
|
|
194
|
+
- **Trade rejections, live payout warnings, connection problems**, and every other notification.
|
|
195
|
+
- Anything that needs an **action** from the player. `ResultSnackbar` is pointer-transparent by design: it is a report, not a control.
|
|
196
|
+
|
|
197
|
+
And do not use it for the contract the player **was** watching as it ended — that gets the full-screen result moment. `ResultSnackbar` deliberately does not take the board.
|
|
198
|
+
|
|
199
|
+
It is never the surface of record either. Every result also lands in positions and history, which is where a player goes to study terms.
|
|
200
|
+
|
|
201
|
+
**Why the two look nothing alike:** that difference is intentional, not drift. `toast` is transient system messaging on the inverse surface. `ResultSnackbar` reports money: floating-chrome glass, an illustration, a signed figure in outcome ink, and a visible dwell timer. Do not "unify" them.
|
|
202
|
+
|
|
203
|
+
```tsx
|
|
204
|
+
import { ResultSnackbar } from "@trading-game/design-intelligence-layer"
|
|
205
|
+
|
|
206
|
+
<ResultSnackbar
|
|
207
|
+
outcome="win" // "win" | "loss" — the only two states
|
|
208
|
+
amount="+8.40" // pre-signed; use a true Unicode minus for losses
|
|
209
|
+
amountValue={8.4} // optional count-up target
|
|
210
|
+
currency="USDT"
|
|
211
|
+
contractLabel="Rise"
|
|
212
|
+
duration="15 seconds"
|
|
213
|
+
icon={<img src="/thumbs-up.webp" alt="" aria-hidden className="size-8" />}
|
|
214
|
+
onDismiss={advanceQueue} // fires once, after the exit finishes
|
|
215
|
+
className="absolute top-3 right-3" // placement is yours; anchor it to something meaningful
|
|
216
|
+
/>
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Placement and queueing are the consumer's: the meaningful anchor differs per game, and deciding what waits, what is stale, and what replays after an interruption is product logic. The component shows one settlement and reports when it is done.
|
|
220
|
+
|
|
221
|
+
---
|
|
222
|
+
|
|
181
223
|
## Rule 4 — Do NOT install or configure these separately
|
|
182
224
|
|
|
183
225
|
```
|
package/package.json
CHANGED