basalt-ui 1.5.0 → 1.7.0
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 +1 -1
- package/CHANGELOG.md +25 -0
- package/agent/rules/basalt-mantine.md +80 -40
- package/agent/rules/basalt-tokens.md +3 -1
- package/agent/skills/basalt-design/SKILL.md +21 -7
- package/agent/templates/CLAUDE-block.md.tpl +3 -2
- package/agent/templates/DESIGN.md.tpl +2 -1
- package/configs/oxlint-plugin.js +75 -1
- package/configs/oxlint.json +1 -0
- package/dist/agent/stick-to-bottom.d.ts.map +1 -1
- package/dist/agent/stick-to-bottom.js +2 -1
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +5 -4
- package/dist/cli/index.d.ts +9 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +2 -0
- package/dist/dashboard/index.d.ts +1 -1
- package/dist/dashboard/index.d.ts.map +1 -1
- package/dist/dashboard/stat-card.d.ts +6 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +37 -4
- package/dist/guard/index.d.ts +7 -1
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +16 -1
- package/dist/guard/types.d.ts +18 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/shell/app-sidebar.module.css +4 -6
- package/dist/shell/sidebar-search.module.css +4 -2
- package/dist/surfaces.d.ts +2 -2
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +8 -2
- package/dist/theme/controls.module.css +167 -26
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +36 -19
- package/dist/theme/nav-link.module.css +10 -10
- package/dist/tokens/index.d.ts +1 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +4 -0
- package/dist/tokens/palette.d.ts +31 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +31 -0
- package/dist/tokens.css +2 -0
- package/dist/vite.d.ts +6 -0
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +21 -1
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/agent/stick-to-bottom.tsx +2 -1
- package/src/agent-chat/composer.tsx +5 -4
- package/src/cli/index.ts +11 -0
- package/src/dashboard/index.ts +1 -1
- package/src/dashboard/stat-card.tsx +61 -2
- package/src/guard/check-source.test.ts +63 -1
- package/src/guard/index.ts +39 -3
- package/src/guard/types.ts +18 -2
- package/src/index.ts +1 -0
- package/src/shell/app-sidebar.module.css +4 -6
- package/src/shell/sidebar-search.module.css +4 -2
- package/src/surfaces.ts +8 -2
- package/src/theme/controls.module.css +167 -26
- package/src/theme/index.ts +50 -19
- package/src/theme/nav-link.module.css +10 -10
- package/src/theme/shadow-surfaces.test.ts +72 -33
- package/src/tokens/index.ts +4 -0
- package/src/tokens/palette.ts +31 -0
- package/src/vite.ts +62 -1
package/AGENTS.md
CHANGED
|
@@ -8,7 +8,7 @@ Toolchain: Bun runtime, oxlint + oxfmt, conventional commits (empty scope), `mas
|
|
|
8
8
|
|
|
9
9
|
| Subpath | Layer | Purpose |
|
|
10
10
|
| --------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
11
|
-
| `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard, EmptyState, SettingsSection/SettingsRow/DangerZone)
|
|
11
|
+
| `basalt-ui` | mantine-coupled | BasaltProvider, createBasaltTheme, BasaltShell + sidebar/mobile-nav/breadcrumbs, NavCountBadge, ThemeToggle, ThreadWorkspace + thread-chat components, dashboard composites (DeltaBadge, StatCard with threshold tone, EmptyState, SettingsSection/SettingsRow/DangerZone) |
|
|
12
12
|
| `basalt-ui/charts` | headless | visx chart primitives, sparklines, hooks, and token re-exports (Mantine-free) |
|
|
13
13
|
| `basalt-ui/tokens` | headless | VX token refs, buildPaletteCss, defineSeries, seriesTokens, groupTokens, alpha (Mantine-free) |
|
|
14
14
|
| `basalt-ui/theme-lab` | mantine-coupled | ThemeLabControls, applyOverrides, COLOR_GROUPS — a low-level inspector for the non-derived structural tokens; identity/color tuning lives in DeriveControls |
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
# [1.7.0](https://github.com/jkrumm/basalt-ui/compare/v1.6.0...v1.7.0) (2026-08-02)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* correct the shipped depth and active-nav doctrine in the agent rules ([38e0f39](https://github.com/jkrumm/basalt-ui/commit/38e0f396012064aaee0ca7b252dabea3c281d5e3))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
* close the three gaps that let a consumer fork the card idiom ([d83988b](https://github.com/jkrumm/basalt-ui/commit/d83988b12c38287c42b3c3148493c614380e27b9))
|
|
12
|
+
|
|
13
|
+
# [1.6.0](https://github.com/jkrumm/basalt-ui/compare/v1.5.0...v1.6.0) (2026-07-31)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
|
|
18
|
+
* **dev:** move marketing off the playground's port, allow the tailnet doors ([488f9e8](https://github.com/jkrumm/basalt-ui/commit/488f9e8d0824ff785c2eee1b2f43e7e456f9beca))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Features
|
|
22
|
+
|
|
23
|
+
* quiet the active nav row into a selection ([767aed2](https://github.com/jkrumm/basalt-ui/commit/767aed2ec72ca92bfdbf5d0c30162b0f64d74e84))
|
|
24
|
+
* unify every interactive control on one raised depth ([7e6adc6](https://github.com/jkrumm/basalt-ui/commit/7e6adc6ef936f312f24112d4e15cd88e5d5d95b3))
|
|
25
|
+
|
|
1
26
|
# [1.5.0](https://github.com/jkrumm/basalt-ui/compare/v1.4.1...v1.5.0) (2026-07-28)
|
|
2
27
|
|
|
3
28
|
|
|
@@ -55,11 +55,13 @@ Restraint is the #1 lever — neutrals carry the surface, the accent only points
|
|
|
55
55
|
labels, links, primary buttons, focus rings, and the leader bar in meters. It does **NOT** appear
|
|
56
56
|
on borders, large fills, every icon, or secondary/routine buttons. Don't flood blue — "ink earns
|
|
57
57
|
its color."
|
|
58
|
-
- **Active nav =
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
- **Active nav = an ink-9% tint** + an **accent-colored icon** + weight-600 ink text — **no panel
|
|
59
|
+
fill and no shadow**. It is a _selected row_, not a raised control; a panel-filled, shadowed row
|
|
60
|
+
reads as a button sitting in the nav rather than as the current location. Inactive = muted text,
|
|
61
|
+
faint icon, hover ink-6%; an active row hovers one step further to ink-13%. This is baked into
|
|
62
|
+
the theme's NavLink defaults (`--nl-*` vars) and holds for _every_ render path, including a
|
|
63
|
+
consumer's router `<Link>` passed via `renderNavLink`. Child items indent with a 1px `divider`
|
|
64
|
+
left border; active child = accent text, weight 600.
|
|
63
65
|
- **Buttons**: the primary action = filled accent (`variant="filled"`), **exactly one per view**.
|
|
64
66
|
Every other/secondary action = `variant="default"` (neutral). Do **not** use a colored
|
|
65
67
|
`variant="light"` for routine actions.
|
|
@@ -67,7 +69,7 @@ Restraint is the #1 lever — neutrals carry the surface, the accent only points
|
|
|
67
69
|
`color="teal"` (vivid turquoise) or a saturated emerald. Status (`red`/`green`/`orange`/`yellow`)
|
|
68
70
|
is for signal only, kept muted — never raw Material/AntD.
|
|
69
71
|
- **Dark mode** is cool-neutral zinc (`#27272a`-family panels on a darker `#18181b`-mixed page) —
|
|
70
|
-
not steel-blue, not pure black. Lift elevation via
|
|
72
|
+
not steel-blue, not pure black. Lift elevation via the depth tokens' whisper shadow + inset ring;
|
|
71
73
|
the accent uses its lighter shade (`#8ec5ff`) on dark to avoid glow/bleed **as INK** — links,
|
|
72
74
|
active-nav icons, chart lines, focus ring. A FILLED control is the opposite case: it keeps the
|
|
73
75
|
deep `#0077bd` fill and a white label in both schemes, because a light fill cannot carry white
|
|
@@ -75,7 +77,8 @@ Restraint is the #1 lever — neutrals carry the surface, the accent only points
|
|
|
75
77
|
`--vx-accent`.
|
|
76
78
|
- **Light mode** is never pure-white page + pure-black text (harsh halation). Page is a
|
|
77
79
|
slightly-darker zinc mix (`#ececee`-ish) than the panel (`#f4f4f5`) so cards lift subtly off it;
|
|
78
|
-
text = near-black ink (`#262626`). Depth comes from
|
|
80
|
+
text = near-black ink (`#262626`). Depth comes from a whisper shadow with a 1px ring baked in
|
|
81
|
+
(`shadow-card` for panels, `shadow-raised` for controls — see "Elevation, density & shape"), not
|
|
79
82
|
a plain hairline border.
|
|
80
83
|
|
|
81
84
|
## Bridging Mantine ↔ charts
|
|
@@ -143,12 +146,23 @@ section divider, not card depth) are both fine.
|
|
|
143
146
|
consumer code must compose Mantine layout/surface primitives — `Box`, `Flex`, `Grid`,
|
|
144
147
|
`SimpleGrid`, `Stack`, `Group`, `Paper`, `Card` — instead of raw `<div>`/`<span>` with inline
|
|
145
148
|
`style`. Raw HTML with inline layout/surface styling defeats the token system.
|
|
146
|
-
- **Mechanical enforcement.** `basalt-ui check-theme` adds
|
|
149
|
+
- **Mechanical enforcement.** `basalt-ui check-theme` adds six surface guard kinds (each a config knob,
|
|
147
150
|
default ON; `theme-allow` line-comment escape): `off-system-surface-var` (raw ramp-step vars),
|
|
148
|
-
`card-with-border` (`withBorder` on a `Card`/`Paper`), `
|
|
149
|
-
|
|
150
|
-
(inline
|
|
151
|
-
allowed — and it must still use `VX.*`
|
|
151
|
+
`card-with-border` (`withBorder` on a `Card`/`Paper`), `mantine-shade-index` (a shade-pinned color —
|
|
152
|
+
see below), `raw-html-layout` (raw `<div>`/`<span>` with inline layout/surface styling),
|
|
153
|
+
`inline-spacing` (inline spacing literals), `inline-display` (inline `display` literals). The
|
|
154
|
+
Mantine-free `src/charts/**` is the only place raw `<div>` is allowed — and it must still use `VX.*`
|
|
155
|
+
tokens. `oxlint` adds `basalt/card-inset`, which catches the other half of the same card drift: an
|
|
156
|
+
explicit `p`/`padding`/`radius` on a `Card`/`Paper` instead of the xs/sm inset and the theme radius.
|
|
157
|
+
|
|
158
|
+
**Never pin a shade index on a color.** `c="yellow.7"`, `bg="blue.4"`, `var(--mantine-color-red-6)` —
|
|
159
|
+
each names one fixed swatch that is identical in light and dark, so a shade legible on the dark page
|
|
160
|
+
is the one that fails contrast on the light one. Two correct forms: `VX.status.*` /
|
|
161
|
+
`--vx-status-warn|bad|good` for a verdict color (emitted per scheme, contrast guaranteed by the
|
|
162
|
+
derivation), or the bare hue name (`c="red"`, `color="green"`) to let the theme pick the step. The
|
|
163
|
+
`mantine-shade-index` guard kind catches the pinned form; `gray`/`dark` steps in `var()` belong to
|
|
164
|
+
`off-system-surface-var` instead. Note the distinction from `off-identity-accent`, which polices
|
|
165
|
+
_which hue_ you reach for and permits the bare status names on purpose — this kind polices the index.
|
|
152
166
|
|
|
153
167
|
## Scroll regions
|
|
154
168
|
|
|
@@ -190,34 +204,60 @@ shape doctrine lives here; the spacing/radius/type **tokens** are in basalt-toke
|
|
|
190
204
|
radius token (`radius="md"` / `var(--vx-radius-card)`) + `VX.shadowCard` + `VX.surface.*`.
|
|
191
205
|
Mechanically enforced by `basalt-ui check-theme`'s `raw-surface` guard. Outer spacing comes from the
|
|
192
206
|
parent `Stack`/`SimpleGrid` gap, not an intrinsic card margin.
|
|
193
|
-
- **Depth =
|
|
194
|
-
doctrine inversion #1 — this supersedes the old
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
207
|
+
- **Depth = a whisper shadow with a 1px ring baked in, not a plain hairline** (see
|
|
208
|
+
`docs/DESIGN-SPEC.md` in the basalt-ui repo, doctrine inversion #1 — this supersedes the old
|
|
209
|
+
"never a drop shadow" rule). **Three depth tiers, split control-vs-panel-vs-floating:**
|
|
210
|
+
- **`shadow-card` — panels.** A surface that HOLDS content: `Card`, `Paper`, `Notification`,
|
|
211
|
+
`ChartCard`, `SettingsSection`. Its light-mode ring is an **outset** 1px hairline; dark is a
|
|
212
|
+
deeper drop + an inset 4%-white rim.
|
|
213
|
+
- **`shadow-raised` — interactive controls.** Anything you click or type into: Button,
|
|
214
|
+
ActionIcon, the field idiom (Input/TextInput/PillsInput/…), the sidebar search trigger, header
|
|
215
|
+
icon buttons, `Chip`, `CheckboxCard`/`RadioCard`, the agent composer. **Never an outset ring** —
|
|
216
|
+
dark is drop + an inset 8%-white rim, light is drop only. That is what lets ONE value ride a
|
|
217
|
+
saturated fill, a 13% tint and a neutral panel alike: an outset ring paints in the color of
|
|
218
|
+
whatever it is drawn over, so `shadow-card`'s pale hairline would read as a grey outline around
|
|
219
|
+
a colored button, while an inset edge is drawn over the control's own background. The
|
|
220
|
+
light/dark asymmetry is deliberate — a light-mode edge would have to be a _darkening_, which
|
|
221
|
+
reads as a border (uniform) or a double bottom rule (bottom-only); both were tried and
|
|
222
|
+
rejected. Don't tidy the two schemes into one shape.
|
|
223
|
+
- **`shadow-overlay` — detached floating surfaces.** Tooltip, Popover, Menu, Combobox, Modal,
|
|
224
|
+
Drawer — they sit _above_ the page rather than on it. Resolved in `theme/index.ts`; not
|
|
225
|
+
Mantine's own shadow scale.
|
|
226
|
+
|
|
227
|
+
Focus is a fourth tier and **layers**: a 2px accent ring drawn OVER the resting depth, never
|
|
228
|
+
replacing it.
|
|
229
|
+
|
|
230
|
+
**The dividing line is control-vs-panel, not component-by-component.** Two controls sitting side
|
|
231
|
+
by side must never land on different tokens — a `size="md"` Input beside a `size="md"` Button
|
|
232
|
+
(pinned to the same height by `--vx-space-control-height`), or the header's search trigger and
|
|
233
|
+
icon button. Closing exactly that split is why `shadow-raised` exists.
|
|
234
|
+
|
|
235
|
+
**Emphasis is fill weight, not depth.** Every Button/ActionIcon variant that owns a box takes the
|
|
236
|
+
SAME resting depth: `default` / `filled` / `light` → `shadow-raised` unmodified; `outline` →
|
|
237
|
+
ring-free `shadow-ctrl` (its real 1px accent border already is its edge); `subtle` → **flat in
|
|
238
|
+
both states** (a text affordance, not an object — its hover is a neutral ink-6% tint and nothing
|
|
239
|
+
more). **Depth is static — no variant moves or changes depth on hover or press.** A hover lift
|
|
240
|
+
and a materializing `subtle` shadow were both tried and reverted; don't reintroduce either.
|
|
241
|
+
Disabled is flat and grounded. Reduced motion drops the transition, never the resting depth.
|
|
242
|
+
|
|
243
|
+
Mechanics: every default-variant control renders its bg + depth with a _transparent_ 1px border
|
|
244
|
+
box (never `border: none` — the box stays so focus/error can recolor it with no layout shift).
|
|
245
|
+
`--mantine-color-default-border` is pinned to `transparent` in `cssVariablesResolver`, the ONE
|
|
246
|
+
lever that kills the stock hairline for every default-variant component at once
|
|
247
|
+
(`defaultVariantColorsResolver`'s `variant === 'default'` branch reads that single var); the
|
|
248
|
+
shadow itself is added per-component in `controls.module.css`, since Mantine never declares one
|
|
249
|
+
for `default`. `PillsInput` reuses the Input field idiom's `classNames` wiring directly (it
|
|
250
|
+
renders `InputBase` under its own `__staticSelector`, so the base `Input` theme entry never
|
|
251
|
+
reached it). Layout dividers (header bottom border, sidebar section separators) still use plain
|
|
252
|
+
borders — only card/control depth moved to shadow. A dedicated test,
|
|
253
|
+
`src/theme/border-coverage.test.ts`, mechanically enumerates every `@mantine/core` component
|
|
254
|
+
whose shipped CSS declares a border and asserts it's either a themed `baseTheme.components` key
|
|
255
|
+
or a reasoned `BORDER_ALLOWLIST` entry — this is what catches the NEXT unthemed bordered
|
|
256
|
+
component (Button/ActionIcon shipped with no `.extend()` block at all, which no consumer-source
|
|
257
|
+
guard can ever see).
|
|
258
|
+
|
|
259
|
+
- **The ring must land on the box that carries the surface's `border-radius`.**
|
|
260
|
+
`shadow-card`/`shadow-raised` bakes a 1px ring into the shadow value itself, and the ring is drawn
|
|
221
261
|
by the shadowed box's OWN corners — it never reads correctly against a different element's radius
|
|
222
262
|
(the Input wrapper bug: a `position: relative` box with `border-radius: 0` drew a square ring
|
|
223
263
|
around the 8px-rounded `<input>` inside it). Background usually co-locates with the radius since
|
|
@@ -13,7 +13,9 @@ basalt-ui ships one color/type/spacing/radius identity shared by the Mantine chr
|
|
|
13
13
|
charts. The **identity is modern zinc** (see `docs/DESIGN-SPEC.md` in the basalt-ui repo) —
|
|
14
14
|
**cool-neutral zinc surfaces**
|
|
15
15
|
(Tailwind zinc family) on both light and dark, low-contrast panel lift on a slightly darker page,
|
|
16
|
-
depth via a whisper shadow + 1px ring (`shadow-card`
|
|
16
|
+
depth via a whisper shadow + 1px ring (`shadow-card` on panels, `shadow-raised` on interactive
|
|
17
|
+
controls, `shadow-overlay` on floating surfaces — see basalt-mantine.md) rather than a hairline
|
|
18
|
+
border, carrying
|
|
17
19
|
**one saturated sky-blue accent**, split by ROLE: as INK (links, active-nav icon, chart
|
|
18
20
|
lines, focus ring) it is `#0077bd` light / `#8ec5ff` dark; as a FILLED SURFACE it is
|
|
19
21
|
`#0077bd` in BOTH schemes with a WHITE label (`--vx-accent-fill`, never `--vx-accent`). Neutrals do ~90% of the
|
|
@@ -19,7 +19,7 @@ one that exists. Lower layers fill gaps; they never override a higher layer.
|
|
|
19
19
|
2. **Shipped `basalt-*` rules** (`.claude/rules/basalt-{tokens,charts,mantine,state}.md`) — the
|
|
20
20
|
universal law AND its enforcement: the Basalt modern-zinc palette + saturated sky accent,
|
|
21
21
|
one-hue-per-metric, neutral structure, gradient defaults, the three-tier token contract, the
|
|
22
|
-
|
|
22
|
+
elevation/density doctrine.
|
|
23
23
|
3. **This skill** — only the loop and the judgment, where the above are silent.
|
|
24
24
|
|
|
25
25
|
If a consumer `DESIGN.md` does not exist yet, the app has not been scaffolded — run `/basalt-app`
|
|
@@ -93,8 +93,9 @@ links, primary buttons, focus rings, and the leader bar in meters. It stays off:
|
|
|
93
93
|
borders, large fills, every icon, secondary buttons. "Ink earns its color" — neutral does the
|
|
94
94
|
structure, the accent only points.
|
|
95
95
|
|
|
96
|
-
- **Active nav =
|
|
97
|
-
full accent fill.
|
|
96
|
+
- **Active nav = an ink-9% tint + accent-colored icon + weight-600 ink text** — no panel fill, no
|
|
97
|
+
shadow, and never a full accent fill. It's a selected row, not a raised control. The theme bakes
|
|
98
|
+
this into `NavLink` for every render path — don't re-color it.
|
|
98
99
|
- **Buttons:** exactly one filled-accent primary CTA per view; every other action is
|
|
99
100
|
`variant="default"` (neutral). Avoid colored `variant="light"` for routine actions.
|
|
100
101
|
- **Status:** positive deltas `color="green"` (forest), never `teal`/turquoise/saturated emerald.
|
|
@@ -115,15 +116,28 @@ structure, the accent only points.
|
|
|
115
116
|
inline-override a surface's `border`/`borderRadius`/`boxShadow`/`backgroundColor` — use the
|
|
116
117
|
radius token + `VX.surface.*` / the shadow-card token. Mechanically enforced by
|
|
117
118
|
`basalt-ui check-theme`'s `raw-surface` guard.
|
|
119
|
+
- **Depth splits control-vs-panel-vs-floating** — three tokens, not one. Panels holding content
|
|
120
|
+
(Card/Paper/Notification/ChartCard) take `shadow-card`; anything you click or type into (Button,
|
|
121
|
+
ActionIcon, inputs, chips, the search trigger) takes `shadow-raised`, whose ring is INSET so one
|
|
122
|
+
value rides a fill, a tint and a panel alike; detached floating surfaces (Tooltip/Popover/Menu/
|
|
123
|
+
Modal/Drawer) take `shadow-overlay`. Depth is static — nothing lifts or gains a shadow on hover,
|
|
124
|
+
and emphasis is carried by fill weight, never z-height. Full doctrine in the `basalt-mantine`
|
|
125
|
+
rule.
|
|
118
126
|
- **Strict surfaces & primitives.** The theme runs a strict surface system: Mantine components read
|
|
119
127
|
raw ramp steps directly, so `cssVariablesResolver` **collapses the ramp steps onto the
|
|
120
|
-
`--vx-surface-*` tokens** — one
|
|
128
|
+
`--vx-surface-*` tokens** — one depth token per tier, one card bg, one radius across every
|
|
121
129
|
component (AppShell, Table, Input, Divider, Tabs, Popover, Accordion, cards). Consumer code must
|
|
122
130
|
**use Mantine layout primitives** (`Box`/`Flex`/`Grid`/`SimpleGrid`/`Stack`/`Group`/`Paper`/`Card`),
|
|
123
|
-
not raw `<div>`/`<span>` with inline `style`. `check-theme` adds
|
|
131
|
+
not raw `<div>`/`<span>` with inline `style`. `check-theme` adds five guard kinds to enforce this
|
|
124
132
|
(default ON, `theme-allow` escape): `off-system-surface-var` (raw ramp-step vars),
|
|
125
|
-
`
|
|
126
|
-
use
|
|
133
|
+
`mantine-shade-index` (a shade-pinned color — `c="yellow.7"` is one fixed swatch in both schemes;
|
|
134
|
+
use `VX.status.*` or the bare hue name), `raw-html-layout`, `inline-spacing`, `inline-display`.
|
|
135
|
+
Only the Mantine-free `src/charts/**` may use raw `<div>` (still with `VX.*` tokens).
|
|
136
|
+
- **Reach for the shipped composite before wrapping one.** A `StatCard` past a threshold takes
|
|
137
|
+
`tone="warn" | "bad"` (an accent rail down its leading edge); a chart goes in `ChartCard`. Wrapping
|
|
138
|
+
a shipped card in a hand-positioned `Box` to add a mark it already has is how an app grows a second
|
|
139
|
+
card idiom — one the guard cannot recognize as a card, and therefore cannot police. If a composite
|
|
140
|
+
genuinely can't express what you need, that is a gap to report, not to route around.
|
|
127
141
|
|
|
128
142
|
**Anti-slop checklist:** no pure `#000`/`#fff`; one accent locked page-wide; neutral does the
|
|
129
143
|
structure, accent only points; don't flood blue; hierarchy via scale/weight/contrast/space, not
|
|
@@ -31,8 +31,9 @@ defer to DESIGN.md.
|
|
|
31
31
|
dark-first professional surface — not a showcase. Ignore `/frontend-design`'s push toward a "BOLD
|
|
32
32
|
aesthetic direction", gradient meshes, noise/grain, and dramatic motion. Here: the shipped
|
|
33
33
|
three-font system (Nunito Sans body, Hubot Sans condensed headings, JetBrains Mono for every
|
|
34
|
-
numeral/micro-label), depth via
|
|
35
|
-
shadow
|
|
34
|
+
numeral/micro-label), depth via a whisper shadow + 1px ring (`shadow-card` on panels,
|
|
35
|
+
`shadow-raised` on controls — never a decorative drop shadow, never a hover lift), neutral
|
|
36
|
+
zinc-by-default with the single saturated accent spent only when earned (trend /
|
|
36
37
|
signal / categorical separation). Restraint **is** the identity.
|
|
37
38
|
|
|
38
39
|
<!-- basalt:end -->
|
|
@@ -21,7 +21,8 @@ law, the law wins.
|
|
|
21
21
|
## Identity
|
|
22
22
|
|
|
23
23
|
{{APP_NAME}} inherits the basalt-ui identity verbatim: modern zinc surfaces, one earned saturated
|
|
24
|
-
sky-blue accent, `shadow-card`
|
|
24
|
+
sky-blue accent, the `shadow-card` / `shadow-raised` / `shadow-overlay` depth split,
|
|
25
|
+
dense-by-default spacing, and the three-font system. The
|
|
25
26
|
law itself — every hex, role split, and enforcement rule — lives in the `basalt-tokens` and
|
|
26
27
|
`basalt-mantine` rules (`.claude/rules/basalt-{tokens,mantine}.md`) and `docs/DESIGN-SPEC.md` in
|
|
27
28
|
the basalt-ui repo; it is **not** restated here. Confirm or restate any intentional identity shift
|
package/configs/oxlint-plugin.js
CHANGED
|
@@ -81,6 +81,27 @@ function isNumericLiteral(node) {
|
|
|
81
81
|
return node !== null && node.type === 'Literal' && typeof node.value === 'number'
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
/**
|
|
85
|
+
* An absolute CSS length written as a STRING — `'10px'`, `"1.5rem"`, `'0.75em'`.
|
|
86
|
+
*
|
|
87
|
+
* Mantine's `size` / `fz` props accept a scale token (`"xs"`…`"xl"`) OR any CSS length, and the
|
|
88
|
+
* second form is the one that leaves the token system. A string is not a numeric literal, so every
|
|
89
|
+
* `size="10px"` in an app slipped past the numeric-only check these two rules used to be.
|
|
90
|
+
*
|
|
91
|
+
* `em`/relative ratios are included on `size` (a `2em` icon is still an off-scale dimension) but
|
|
92
|
+
* the message names the right escape for each rule, so the two are not merged into one check.
|
|
93
|
+
*/
|
|
94
|
+
const CSS_LENGTH_STRING = /^-?\d*\.?\d+(?:px|rem|em)$/
|
|
95
|
+
|
|
96
|
+
function isCssLengthString(node) {
|
|
97
|
+
return (
|
|
98
|
+
node !== null &&
|
|
99
|
+
node.type === 'Literal' &&
|
|
100
|
+
typeof node.value === 'string' &&
|
|
101
|
+
CSS_LENGTH_STRING.test(node.value.trim())
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
|
|
84
105
|
/** Unwraps a JSXExpressionContainer to its inner expression; passes any other node through. */
|
|
85
106
|
function unwrapExpressionContainer(node) {
|
|
86
107
|
return node !== null && node.type === 'JSXExpressionContainer' ? node.expression : node
|
|
@@ -126,7 +147,59 @@ const noRawFontSize = {
|
|
|
126
147
|
},
|
|
127
148
|
}
|
|
128
149
|
|
|
129
|
-
// ── Rule 2 —
|
|
150
|
+
// ── Rule 2 — raw-size-literal ───────────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
const RAW_SIZE_LITERAL_MESSAGE =
|
|
153
|
+
'Raw CSS length on a size prop — use the scale ("xs".."xl"), or fz={VX.text.*} when the type ' +
|
|
154
|
+
'scale has no step for it. (basalt/raw-size-literal)'
|
|
155
|
+
|
|
156
|
+
/** The size-ish JSX props a CSS-length string can leave the token system through. */
|
|
157
|
+
const SIZE_ATTRS = new Set(['size', 'fz', 'fontSize'])
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A CSS length written as a STRING on a size prop — `<Text size="10px">`, `<ThemeIcon size="2rem">`,
|
|
161
|
+
* `<Text fz="0.8rem">`. This is the hole `no-raw-font-size` left open: it only ever tested for a
|
|
162
|
+
* NUMERIC literal, so every string form walked straight past it.
|
|
163
|
+
*
|
|
164
|
+
* Its own rule id rather than a widening of `no-raw-font-size`, for two reasons. It covers `size`,
|
|
165
|
+
* which is not always a font size — on `Text` it is, on `ThemeIcon`/`ActionIcon` it is a box
|
|
166
|
+
* dimension — so one shared id would mean a consumer silencing the icon case also silences every
|
|
167
|
+
* off-scale font size, the bundled-rule mistake the three boundary rules were split apart to avoid.
|
|
168
|
+
* And a separate id can carry its own severity: this rule ships `warn` in the consumer preset for
|
|
169
|
+
* one minor (the grace-minor doctrine in the package CLAUDE.md — it rejects code that previously
|
|
170
|
+
* passed, and majors are banned here, so a consumer has no semver channel warning them). Widening
|
|
171
|
+
* `no-raw-font-size` in place would have promoted it to `error` on upgrade with no such runway.
|
|
172
|
+
*
|
|
173
|
+
* Numeric `size={32}` is deliberately NOT flagged. It is the documented Mantine idiom for icon
|
|
174
|
+
* dimensions and flagging it would fire on nearly every icon in an app for no design-system gain —
|
|
175
|
+
* the token system has an opinion about type scale and spacing, not about how many pixels wide one
|
|
176
|
+
* glyph box is.
|
|
177
|
+
*/
|
|
178
|
+
const rawSizeLiteral = {
|
|
179
|
+
meta: {
|
|
180
|
+
type: 'suggestion',
|
|
181
|
+
docs: {
|
|
182
|
+
description: 'Disallow raw CSS-length strings on size/fz/fontSize props.',
|
|
183
|
+
},
|
|
184
|
+
schema: [],
|
|
185
|
+
},
|
|
186
|
+
create(context) {
|
|
187
|
+
if (getFilename(context).includes('/src/tokens/')) return {}
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
JSXAttribute(node) {
|
|
191
|
+
const name = node.name?.name
|
|
192
|
+
if (typeof name !== 'string' || !SIZE_ATTRS.has(name)) return
|
|
193
|
+
const value = unwrapExpressionContainer(node.value)
|
|
194
|
+
if (!isCssLengthString(value)) return
|
|
195
|
+
if (hasThemeAllow(context, node)) return
|
|
196
|
+
context.report({ node, message: RAW_SIZE_LITERAL_MESSAGE })
|
|
197
|
+
},
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// ── Rule 3 — card-inset ─────────────────────────────────────────────────────────────────────────
|
|
130
203
|
|
|
131
204
|
const CARD_TAGS = new Set(['Card', 'Paper'])
|
|
132
205
|
|
|
@@ -456,6 +529,7 @@ export default {
|
|
|
456
529
|
meta: { name: 'basalt' },
|
|
457
530
|
rules: {
|
|
458
531
|
'no-raw-font-size': noRawFontSize,
|
|
532
|
+
'raw-size-literal': rawSizeLiteral,
|
|
459
533
|
'card-inset': cardInset,
|
|
460
534
|
'chart-in-raw-surface': chartInRawSurface,
|
|
461
535
|
'raw-scroll-container': rawScrollContainer,
|
package/configs/oxlint.json
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
"import/no-duplicates": "error",
|
|
18
18
|
"import/no-unassigned-import": "off",
|
|
19
19
|
"basalt/no-raw-font-size": "error",
|
|
20
|
+
"basalt/raw-size-literal": "warn",
|
|
20
21
|
"basalt/card-inset": "error",
|
|
21
22
|
"basalt/chart-in-raw-surface": "error",
|
|
22
23
|
"basalt/raw-scroll-container": "off",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stick-to-bottom.d.ts","sourceRoot":"","sources":["../../src/agent/stick-to-bottom.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"stick-to-bottom.d.ts","sourceRoot":"","sources":["../../src/agent/stick-to-bottom.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAwF1D,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAA;IAC5B,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,kDAAkD;IAClD,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,SAAS,EACT,KAAK,GACN,EAAE,wBAAwB,GAAG,GAAG,CAAC,OAAO,CAcxC"}
|
|
@@ -37,7 +37,8 @@ const LazyStickToBottom = lazy(
|
|
|
37
37
|
border: "none",
|
|
38
38
|
cursor: "pointer",
|
|
39
39
|
backgroundColor: "var(--vx-surface-panel)",
|
|
40
|
-
|
|
40
|
+
// Control depth, not panel depth — this is a real <button> (DESIGN-SPEC §2).
|
|
41
|
+
boxShadow: "var(--vx-shadow-raised)",
|
|
41
42
|
color: "var(--vx-faint)",
|
|
42
43
|
fontFamily: "var(--basalt-font-mono)",
|
|
43
44
|
fontSize: VX.text.md
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/agent-chat/composer.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAkB,GAAG,EAAiB,MAAM,OAAO,CAAA;AAoB/D,MAAM,MAAM,aAAa,GAAG;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,QAAgB,EAChB,WAAW,EACX,SAAS,GACV,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"composer.d.ts","sourceRoot":"","sources":["../../src/agent-chat/composer.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAkB,GAAG,EAAiB,MAAM,OAAO,CAAA;AAoB/D,MAAM,MAAM,aAAa,GAAG;IAC1B,yFAAyF;IACzF,QAAQ,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;IACzC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,yCAAyC;IACzC,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;CAC7B,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,QAAgB,EAChB,WAAW,EACX,SAAS,GACV,EAAE,aAAa,GAAG,GAAG,CAAC,OAAO,CA8E7B"}
|
|
@@ -41,13 +41,14 @@ function Composer({
|
|
|
41
41
|
disabled,
|
|
42
42
|
onChange: (event) => setValue(event.currentTarget.value),
|
|
43
43
|
onKeyDown: handleKeyDown,
|
|
44
|
-
// Composer input surface (docs/DESIGN-SPEC.md §5): panel +
|
|
45
|
-
//
|
|
46
|
-
//
|
|
44
|
+
// Composer input surface (docs/DESIGN-SPEC.md §5): panel + control radius (6px, VX.radiusCtrl);
|
|
45
|
+
// the ring lives in the shadow, so the input carries no separate border. Depth is deliberately
|
|
46
|
+
// NOT set here — it arrives from the themed `.input` class (theme/controls.module.css), which
|
|
47
|
+
// also grounds it when disabled. A `boxShadow` in this `styles` object renders as an INLINE
|
|
48
|
+
// style and would beat that class unconditionally, leaving a disabled composer raised.
|
|
47
49
|
styles: {
|
|
48
50
|
input: {
|
|
49
51
|
backgroundColor: "var(--vx-surface-panel)",
|
|
50
|
-
boxShadow: "var(--vx-shadow-card)",
|
|
51
52
|
borderRadius: VX.radiusCtrl,
|
|
52
53
|
border: "none"
|
|
53
54
|
}
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -41,6 +41,15 @@ export type BasaltConfig = {
|
|
|
41
41
|
* surface tokens. Default: `true` (ON). Set `false` to disable the `off-system-surface-var` check.
|
|
42
42
|
*/
|
|
43
43
|
offSystemSurfaceVar?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Flag a SHADE-PINNED Mantine color — `c="yellow.7"`, `bg="blue.4"`,
|
|
46
|
+
* `var(--mantine-color-red-6)`. A pinned step is one fixed swatch in BOTH color schemes, so it
|
|
47
|
+
* cannot stay legible in either; route a verdict color through `VX.status.*` / `--vx-status-*`,
|
|
48
|
+
* or drop the index (`c="red"`) and let the theme resolve the shade per scheme. `gray-*`/`dark-*`
|
|
49
|
+
* in `var()` form belong to `off-system-surface-var` instead. Default: `true` (ON). Set `false`
|
|
50
|
+
* to disable the `mantine-shade-index` check.
|
|
51
|
+
*/
|
|
52
|
+
mantineShadeIndex?: boolean;
|
|
44
53
|
/**
|
|
45
54
|
* Flag raw lowercase JSX layout/surface elements (`div`/`span`/`section`/…) carrying an inline
|
|
46
55
|
* `style={{}}` with a layout/surface property — steer to a Mantine layout primitive
|
package/dist/cli/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAwB,SAAS,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAE9E,OAAO,EAAE,UAAU,EAAE,WAAW,EAA2C,MAAM,aAAa,CAAA;AAI9F,yEAAyE;AACzE,MAAM,MAAM,YAAY,GAAG;IACzB,sFAAsF;IACtF,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IACjB,uGAAuG;IACvG,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB;wFACoF;IACpF,QAAQ,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC,CAAA;IACpD,6FAA6F;IAC7F,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC3B,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAA;IAC7B;;;;;OAKG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IACrB;;;;OAIG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;;OAIG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAA;IAC/B;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC,CAAA;CACnD,CAAA;AAkLD;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,GAAG,GAAE,MAAsB,GAAG,MAAM,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -109,6 +109,7 @@ function checkTheme(cwd = process.cwd()) {
|
|
|
109
109
|
spacingSteps: cfg.spacingSteps ?? DEFAULT_GUARD_CONFIG.spacingSteps,
|
|
110
110
|
rawRadius: cfg.rawRadius ?? DEFAULT_GUARD_CONFIG.rawRadius,
|
|
111
111
|
forbiddenAccents: cfg.forbiddenAccents ?? DEFAULT_GUARD_CONFIG.forbiddenAccents,
|
|
112
|
+
mantineShadeIndex: cfg.mantineShadeIndex ?? DEFAULT_GUARD_CONFIG.mantineShadeIndex,
|
|
112
113
|
rawSurface: cfg.rawSurface ?? DEFAULT_GUARD_CONFIG.rawSurface,
|
|
113
114
|
cardWithBorder: cfg.cardWithBorder ?? DEFAULT_GUARD_CONFIG.cardWithBorder,
|
|
114
115
|
offSystemSurfaceVar: cfg.offSystemSurfaceVar ?? DEFAULT_GUARD_CONFIG.offSystemSurfaceVar,
|
|
@@ -962,6 +963,7 @@ async function guardHook(cwd = process.cwd()) {
|
|
|
962
963
|
spacingSteps: cfg.spacingSteps ?? DEFAULT_GUARD_CONFIG.spacingSteps,
|
|
963
964
|
rawRadius: cfg.rawRadius ?? DEFAULT_GUARD_CONFIG.rawRadius,
|
|
964
965
|
forbiddenAccents: cfg.forbiddenAccents ?? DEFAULT_GUARD_CONFIG.forbiddenAccents,
|
|
966
|
+
mantineShadeIndex: cfg.mantineShadeIndex ?? DEFAULT_GUARD_CONFIG.mantineShadeIndex,
|
|
965
967
|
rawSurface: cfg.rawSurface ?? DEFAULT_GUARD_CONFIG.rawSurface,
|
|
966
968
|
cardWithBorder: cfg.cardWithBorder ?? DEFAULT_GUARD_CONFIG.cardWithBorder,
|
|
967
969
|
offSystemSurfaceVar: cfg.offSystemSurfaceVar ?? DEFAULT_GUARD_CONFIG.offSystemSurfaceVar,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { DeltaBadge, type DeltaBadgeProps } from './delta-badge.js';
|
|
2
|
-
export { StatCard, type StatCardProps } from './stat-card.js';
|
|
2
|
+
export { StatCard, type StatCardProps, type StatCardTone } from './stat-card.js';
|
|
3
3
|
export { EmptyState, type EmptyStateProps } from './empty-state.js';
|
|
4
4
|
export { SettingsSection, type SettingsSectionProps, SettingsRow, type SettingsRowProps, DangerZone, type DangerZoneProps, } from './settings-section.js';
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/dashboard/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAC7E,OAAO,EAAE,UAAU,EAAE,KAAK,eAAe,EAAE,MAAM,eAAe,CAAA;AAChE,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,EACzB,WAAW,EACX,KAAK,gBAAgB,EACrB,UAAU,EACV,KAAK,eAAe,GACrB,MAAM,oBAAoB,CAAA"}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { ReactNode } from 'react';
|
|
2
|
+
/** A threshold verdict on the card's value. `undefined` is never tinted — see the module docblock. */
|
|
3
|
+
export type StatCardTone = 'warn' | 'bad';
|
|
2
4
|
export type StatCardProps = {
|
|
3
5
|
/** Mono uppercase micro-label (docs/DESIGN-SPEC.md §3: 12.5px, tracking 0.06em, faint). */
|
|
4
6
|
label: string;
|
|
@@ -13,6 +15,9 @@ export type StatCardProps = {
|
|
|
13
15
|
sparkline?: ReactNode;
|
|
14
16
|
/** Optional header-right slot (e.g. a ghost "..." menu trigger) — consumer-owned. */
|
|
15
17
|
menu?: ReactNode;
|
|
18
|
+
/** Threshold verdict — draws an accent rail down the card's leading edge and announces itself to
|
|
19
|
+
* assistive tech. Omit it for a reading that is fine, and for one that is absent. */
|
|
20
|
+
tone?: StatCardTone;
|
|
16
21
|
};
|
|
17
|
-
export declare function StatCard({ label, value, delta, deltaPeriod, sparkline, menu }: StatCardProps): import("react").JSX.Element;
|
|
22
|
+
export declare function StatCard({ label, value, delta, deltaPeriod, sparkline, menu, tone, }: StatCardProps): import("react").JSX.Element;
|
|
18
23
|
//# sourceMappingURL=stat-card.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"stat-card.d.ts","sourceRoot":"","sources":["../../src/dashboard/stat-card.tsx"],"names":[],"mappings":"AAmCA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAItC,sGAAsG;AACtG,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,CAAA;AAWzC,MAAM,MAAM,aAAa,GAAG;IAC1B,2FAA2F;IAC3F,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,oGAAoG;IACpG,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB;kGAC8F;IAC9F,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,qFAAqF;IACrF,IAAI,CAAC,EAAE,SAAS,CAAA;IAChB;yFACqF;IACrF,IAAI,CAAC,EAAE,YAAY,CAAA;CACpB,CAAA;AAED,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,KAAK,EACL,KAAK,EACL,WAAW,EACX,SAAS,EACT,IAAI,EACJ,IAAI,GACL,EAAE,aAAa,+BAqFf"}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { Card, Group } from "@mantine/core";
|
|
1
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Card, Group, VisuallyHidden } from "@mantine/core";
|
|
3
3
|
import { DeltaBadge } from "./delta-badge.js";
|
|
4
4
|
import { VX } from "../tokens/index.js";
|
|
5
|
-
|
|
5
|
+
const TONE_RAIL_WIDTH = 3;
|
|
6
|
+
const TONE_LABEL = {
|
|
7
|
+
warn: "Past the warning threshold",
|
|
8
|
+
bad: "Past the severe threshold"
|
|
9
|
+
};
|
|
10
|
+
function StatCard({
|
|
11
|
+
label,
|
|
12
|
+
value,
|
|
13
|
+
delta,
|
|
14
|
+
deltaPeriod,
|
|
15
|
+
sparkline,
|
|
16
|
+
menu,
|
|
17
|
+
tone
|
|
18
|
+
}) {
|
|
6
19
|
return /* @__PURE__ */ jsxs(
|
|
7
20
|
Card,
|
|
8
21
|
{
|
|
@@ -19,9 +32,29 @@ function StatCard({ label, value, delta, deltaPeriod, sparkline, menu }) {
|
|
|
19
32
|
// theme-allow: Card root as a flex column — Mantine Card takes no layout props
|
|
20
33
|
flexDirection: "column",
|
|
21
34
|
gap: "var(--vx-space-stat-card-gap)",
|
|
22
|
-
overflow: "hidden"
|
|
35
|
+
overflow: "hidden",
|
|
36
|
+
// Anchors the tone rail. Set unconditionally so a card's stacking context does not change
|
|
37
|
+
// depending on whether it happens to have crossed a threshold this render.
|
|
38
|
+
position: "relative"
|
|
23
39
|
},
|
|
40
|
+
"data-tone": tone,
|
|
24
41
|
children: [
|
|
42
|
+
tone !== void 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
+
/* @__PURE__ */ jsx(VisuallyHidden, { children: TONE_LABEL[tone] }),
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
Box,
|
|
46
|
+
{
|
|
47
|
+
"aria-hidden": "true",
|
|
48
|
+
style: {
|
|
49
|
+
position: "absolute",
|
|
50
|
+
insetBlock: 0,
|
|
51
|
+
insetInlineStart: 0,
|
|
52
|
+
width: TONE_RAIL_WIDTH,
|
|
53
|
+
background: tone === "bad" ? VX.status.bad : VX.status.warn
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
)
|
|
57
|
+
] }),
|
|
25
58
|
/* @__PURE__ */ jsxs(Group, { justify: "space-between", align: "flex-start", wrap: "nowrap", gap: "xs", children: [
|
|
26
59
|
/* @__PURE__ */ jsx(
|
|
27
60
|
"span",
|