basalt-ui 1.19.1 → 1.20.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 +15 -1
- package/CHANGELOG.md +20 -0
- package/MIGRATING.md +228 -0
- package/README.md +53 -9
- package/agent/rules/basalt-app.md +43 -0
- package/agent/rules/basalt-charts.md +26 -14
- package/agent/rules/basalt-mantine.md +25 -10
- package/agent/rules/basalt-router.md +22 -10
- package/agent/rules/basalt-tokens.md +75 -6
- package/agent/skills/basalt-app/SKILL.md +42 -6
- package/configs/check.yml +11 -4
- package/configs/lefthook.yml +21 -2
- package/configs/oxlint-plugin.js +515 -25
- package/configs/oxlint-plugin.test.ts +383 -10
- package/configs/oxlint.json +10 -1
- package/dist/agent/stick-to-bottom.js +1 -1
- package/dist/agent-chat/thread-detail-panel.js +1 -1
- package/dist/agent-chat/thread-message.js +2 -2
- package/dist/agent-chat/tool-chip.js +1 -1
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +1 -1
- package/dist/charts/primitives/ChartLegend.js +2 -2
- package/dist/charts/primitives/ChartTooltip.js +1 -1
- package/dist/cli/index.d.ts +107 -15
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +610 -72
- package/dist/cli/toolchain-wiring.test.d.ts +2 -0
- package/dist/cli/toolchain-wiring.test.d.ts.map +1 -0
- package/dist/dashboard/delta-badge.js +2 -2
- package/dist/dashboard/stat-card.js +1 -1
- package/dist/data/virtual-list.js +5 -5
- package/dist/guard/index.d.ts +56 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +326 -59
- package/dist/guard/types.d.ts +25 -5
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/register.d.ts +25 -0
- package/dist/register.d.ts.map +1 -1
- package/dist/shell/app-mobile-nav.module.css +11 -3
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/index.d.ts +3 -3
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/palette.d.ts +35 -27
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +24 -18
- package/dist/tokens.css +19 -17
- package/dist/vite-color-mix.js +2 -2
- package/llms.txt +3 -3
- package/package.json +4 -3
- package/src/agent/stick-to-bottom.tsx +1 -1
- package/src/agent-chat/thread-detail-panel.tsx +1 -1
- package/src/agent-chat/thread-message.tsx +3 -3
- package/src/agent-chat/tool-chip.tsx +1 -1
- package/src/charts/kinds/Donut.tsx +2 -1
- package/src/charts/primitives/ChartLegend.tsx +2 -2
- package/src/charts/primitives/ChartTooltip.tsx +1 -1
- package/src/cli/index.ts +1070 -99
- package/src/cli/init-sync.test.ts +19 -3
- package/src/cli/tokens-css.test.ts +27 -14
- package/src/cli/toolchain-wiring.test.ts +694 -0
- package/src/dashboard/delta-badge.tsx +3 -3
- package/src/dashboard/stat-card.tsx +1 -1
- package/src/data/virtual-list.tsx +8 -8
- package/src/guard/check-source.test.ts +663 -11
- package/src/guard/index.ts +727 -94
- package/src/guard/types.ts +29 -4
- package/src/register.ts +32 -0
- package/src/shell/app-mobile-nav.module.css +11 -3
- package/src/shell/app-mobile-nav.tsx +1 -1
- package/src/surfaces.ts +1 -1
- package/src/theme/density-relations.test.ts +8 -5
- package/src/theme/spacing.test.ts +2 -0
- package/src/theme-toggle/index.tsx +2 -2
- package/src/tokens/index.ts +5 -3
- package/src/tokens/palette.ts +24 -18
- package/src/vite-color-mix.ts +2 -2
package/src/guard/types.ts
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Guard types — Mantine-free, dependency-free (zero imports beyond TS types).
|
|
3
3
|
*
|
|
4
|
-
* GuardKind is the closed set of
|
|
4
|
+
* GuardKind is the closed set of 25 violation kinds the theme guard can emit.
|
|
5
5
|
* Finding is the structured result per violation, replacing the old `Violation` shape.
|
|
6
6
|
* GuardConfig is the per-run configuration that drives checkSource.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
/** The
|
|
9
|
+
/** The 25 theme-guard violation kinds. */
|
|
10
10
|
export type GuardKind =
|
|
11
11
|
| 'raw-hex'
|
|
12
12
|
| 'raw-color-fn'
|
|
@@ -28,6 +28,11 @@ export type GuardKind =
|
|
|
28
28
|
| 'raw-form-control'
|
|
29
29
|
| 'sub-16-input-font'
|
|
30
30
|
| 'raw-font-family'
|
|
31
|
+
| 'theme-allow-unscoped'
|
|
32
|
+
| 'surface-shadow-override'
|
|
33
|
+
| 'css-raw-surface'
|
|
34
|
+
| 'inline-font-size'
|
|
35
|
+
| 'hidden-inline-style'
|
|
31
36
|
|
|
32
37
|
/**
|
|
33
38
|
* How hard a finding lands. `error` fails the build; `warn` reports and passes.
|
|
@@ -151,10 +156,30 @@ export type GuardConfig = {
|
|
|
151
156
|
*/
|
|
152
157
|
readonly sub16InputFont: boolean
|
|
153
158
|
/**
|
|
154
|
-
* Allow-comment policy
|
|
155
|
-
*
|
|
159
|
+
* Allow-comment policy. The token that opens an exception annotation inside a COMMENT, on the
|
|
160
|
+
* finding's own line or the line directly above it (matching the oxlint plugin's placement, and
|
|
161
|
+
* surviving an `oxfmt` reflow that moves the comment off the offending line). Default
|
|
162
|
+
* `'theme-allow'`.
|
|
163
|
+
*
|
|
164
|
+
* The accountable form is `theme-allow <rule-id>[, <rule-id>] — <reason>`: the rule ids scope the
|
|
165
|
+
* exception to those kinds only, and the reason says why. A `theme-allow` missing either half
|
|
166
|
+
* still suppresses (so an upgrade never breaks a build over it) but reports
|
|
167
|
+
* {@link GuardKind} `theme-allow-unscoped` on its own line.
|
|
156
168
|
*/
|
|
157
169
|
readonly allowComment: string
|
|
170
|
+
/**
|
|
171
|
+
* Which kinds apply to this consumer. `'mantine'` (default) is the full framework guard.
|
|
172
|
+
*
|
|
173
|
+
* `'tokens-only'` is for a consumer that installs the TOKEN LAYER and nothing else — no
|
|
174
|
+
* `@mantine/core`, often no React (`basalt-ui tokens:css` + `basalt-ui/tokens`). Every kind whose
|
|
175
|
+
* remedy is a Mantine component or prop is disabled there, because "use `TextInput` from
|
|
176
|
+
* `@mantine/core`" is not advice, it is a dependency the consumer deliberately does not have.
|
|
177
|
+
* The color/typography kinds stay on — those are the tokens, which is the whole surface they DO
|
|
178
|
+
* consume. See `TOKENS_ONLY_DISABLED_KINDS` in `./index` for the exact set.
|
|
179
|
+
*
|
|
180
|
+
* Optional so a caller built before this field existed keeps compiling; absent means `'mantine'`.
|
|
181
|
+
*/
|
|
182
|
+
readonly profile?: 'mantine' | 'tokens-only'
|
|
158
183
|
/**
|
|
159
184
|
* Per-rule, per-path exemptions — complements whole-file `exempt` (BasaltConfig, which skips
|
|
160
185
|
* ALL rules for a file) and code-level `appliesTo` (hardcoded per-kind path scoping, e.g.
|
package/src/register.ts
CHANGED
|
@@ -156,3 +156,35 @@ export namespace StandardSchemaV1 {
|
|
|
156
156
|
T['~standard']['types']
|
|
157
157
|
>['output']
|
|
158
158
|
}
|
|
159
|
+
|
|
160
|
+
// ── Ambient app globals ───────────────────────────────────────────────────────────────────────────
|
|
161
|
+
|
|
162
|
+
declare global {
|
|
163
|
+
/**
|
|
164
|
+
* The app's version string, injected as a build-time constant by basalt's own Vite preset —
|
|
165
|
+
* `basaltViteConfig` defines it from `BUILD_VERSION`, falling back to the app package.json
|
|
166
|
+
* `version` (see `../vite`, the `define` block).
|
|
167
|
+
*
|
|
168
|
+
* Declared HERE rather than beside the define because a consumer's `src/**` never loads
|
|
169
|
+
* `basalt-ui/vite`'s types (that subpath is imported from `vite.config.ts`, which most apps keep
|
|
170
|
+
* in a separate tsconfig project). This file is re-exported from the root barrel, so an app that
|
|
171
|
+
* imports from `'basalt-ui'` at all has it in scope with no `/// <reference>` and no
|
|
172
|
+
* `src/vite-env.d.ts` entry — which is the point: basalt shipped the define and no declaration,
|
|
173
|
+
* so every consumer hand-wrote this block, one of them needing an `oxlint-disable` in a file that
|
|
174
|
+
* existed only because basalt asked for it.
|
|
175
|
+
*
|
|
176
|
+
* A consumer importing ONLY a subpath (`basalt-ui/tokens`, `basalt-ui/charts`) does not load this
|
|
177
|
+
* file and does not get the global. That gap is the same set as the applicability gap: reaching
|
|
178
|
+
* for the root barrel is what `BasaltProvider` requires, and an app that never does is not on
|
|
179
|
+
* basalt's Vite preset either, so there is no `__APP_VERSION__` to declare.
|
|
180
|
+
*
|
|
181
|
+
* Precondition: only defined when your Vite config actually goes through `basaltViteConfig` (or
|
|
182
|
+
* you define it yourself). An app that hand-rolls its Vite config and reads this gets a
|
|
183
|
+
* ReferenceError at runtime — TypeScript is trusting the preset, not proving it.
|
|
184
|
+
*/
|
|
185
|
+
// The dangle IS the API: this declares the build-time constant basalt's own Vite preset defines,
|
|
186
|
+
// so the name is not ours to change. The SHIPPED preset allows it (configs/oxlint.json); this
|
|
187
|
+
// repo's own root .oxlintrc.json does not, hence the local suppression.
|
|
188
|
+
// oxlint-disable-next-line no-underscore-dangle
|
|
189
|
+
const __APP_VERSION__: string
|
|
190
|
+
}
|
|
@@ -46,9 +46,17 @@
|
|
|
46
46
|
display: inline-flex;
|
|
47
47
|
align-items: center;
|
|
48
48
|
justify-content: center;
|
|
49
|
-
/*
|
|
50
|
-
*
|
|
51
|
-
|
|
49
|
+
/* This span IS the active pill, so its box may not depend on what it contains: shipping no icon
|
|
50
|
+
* dependency is a supported configuration, and an empty span collapsed the pill to a 24x4px
|
|
51
|
+
* dash. The floor is the icon box PLUS its inset — everything is border-box under Mantine's
|
|
52
|
+
* reset — so the pill renders identically with and without a glyph, at every density. */
|
|
53
|
+
min-width: calc(
|
|
54
|
+
var(--vx-space-mobile-nav-icon-size) + 2 * var(--vx-space-mobile-nav-tab-inset-x)
|
|
55
|
+
);
|
|
56
|
+
min-height: calc(
|
|
57
|
+
var(--vx-space-mobile-nav-icon-size) + 2 * var(--vx-space-mobile-nav-tab-inset-y)
|
|
58
|
+
);
|
|
59
|
+
padding: var(--vx-space-mobile-nav-tab-inset-y) var(--vx-space-mobile-nav-tab-inset-x);
|
|
52
60
|
border-radius: var(--vx-radius-ctrl);
|
|
53
61
|
transition: transform 140ms cubic-bezier(0.32, 0.72, 0, 1);
|
|
54
62
|
}
|
|
@@ -38,7 +38,7 @@ import classes from './app-mobile-nav.module.css'
|
|
|
38
38
|
* bottom-anchored menu puts it UNDER the footer, off-screen — `menuMax` is what guarantees it fits
|
|
39
39
|
* above the bar (§2.2). `shift.padding` is a viewport inset in px, not a layout spacing token.
|
|
40
40
|
*/
|
|
41
|
-
const MENU_MIDDLEWARES = { flip: false, shift: { padding: 8 } } // theme-allow
|
|
41
|
+
const MENU_MIDDLEWARES = { flip: false, shift: { padding: 8 } } // theme-allow inline-spacing — a floating-ui viewport inset, not layout spacing
|
|
42
42
|
|
|
43
43
|
/** A slot that navigates on tap. */
|
|
44
44
|
type LinkSlot = Extract<MobileNavSlot, { kind: 'link' }>
|
package/src/surfaces.ts
CHANGED
|
@@ -467,7 +467,7 @@ export const SURFACES = {
|
|
|
467
467
|
kind: 'tooling',
|
|
468
468
|
layer: 'non-js-asset',
|
|
469
469
|
description:
|
|
470
|
-
'Prebuilt --vx-* token stylesheet — the default buildPaletteCss() output as a plain file, for a consumer
|
|
470
|
+
'Prebuilt --vx-* token stylesheet — the default buildPaletteCss() output as a plain file, for a consumer that has a bundler to resolve the subpath but no React and no Mantine; with no bundler, emit a file you own via `basalt-ui tokens:css`',
|
|
471
471
|
forbiddenImports: [],
|
|
472
472
|
},
|
|
473
473
|
'./configs/*': {
|
|
@@ -266,17 +266,20 @@ describe("Fix 7 — the knob's resolution is honest at every notch", () => {
|
|
|
266
266
|
expect(minRatio).toBeLessThan(0.7)
|
|
267
267
|
})
|
|
268
268
|
|
|
269
|
-
test('frozen-vs-baseline ceiling: fewer than
|
|
270
|
-
// Measured under the 0.1/±3 law: worst case is
|
|
271
|
-
// 0.06/±5 law.
|
|
272
|
-
//
|
|
269
|
+
test('frozen-vs-baseline ceiling: fewer than 27 of the ~129 values equal their level-0 value at any non-zero level', () => {
|
|
270
|
+
// Measured under the 0.1/±3 law: worst case is 25 (at level −1) — down from 43 under the old
|
|
271
|
+
// 0.06/±5 law. It is 25 rather than 24 because `mobileNavTabInsetY` (base 2) joined the set:
|
|
272
|
+
// a base that small rounds back to itself at ±1 and ±2 under any multiplier this law can
|
|
273
|
+
// produce, exactly as `mobileNavTabGap` (base 3) already did. That is the rounding law, not a
|
|
274
|
+
// resolution regression — the ceiling of 27 keeps the same margin over the measured worst case
|
|
275
|
+
// while still catching a future step reduction that reintroduces the real dead zone.
|
|
273
276
|
const baseline = flattenSpacing(0)
|
|
274
277
|
const keys = Object.keys(baseline)
|
|
275
278
|
for (const level of ALL_LEVELS) {
|
|
276
279
|
if (level === 0) continue
|
|
277
280
|
const values = flattenSpacing(level)
|
|
278
281
|
const frozen = keys.filter((k) => values[k] === baseline[k]).length
|
|
279
|
-
expect(frozen).toBeLessThan(
|
|
282
|
+
expect(frozen).toBeLessThan(27)
|
|
280
283
|
}
|
|
281
284
|
})
|
|
282
285
|
|
|
@@ -357,6 +357,8 @@ const SPACE_STEP_SWEEP: ReadonlyArray<
|
|
|
357
357
|
// `app-mobile-nav.tsx` hands `mobileNavMenuWidth` to `<Menu width={…}>`.
|
|
358
358
|
['mobileNavBarHeight', 56, null],
|
|
359
359
|
['mobileNavIconSize', 24, 'space-mobile-nav-icon-size'],
|
|
360
|
+
['mobileNavTabInsetY', 2, 'space-mobile-nav-tab-inset-y'],
|
|
361
|
+
['mobileNavTabInsetX', 12, 'space-mobile-nav-tab-inset-x'],
|
|
360
362
|
['mobileNavRowHeight', 44, 'space-mobile-nav-row-height'],
|
|
361
363
|
['mobileNavMenuWidth', 232, null],
|
|
362
364
|
['agentRailInsetX', 10, 'space-agent-rail-inset-x'],
|
|
@@ -84,7 +84,7 @@ function SchemeGlyph({ dark, reduceMotion }: { dark: boolean; reduceMotion: bool
|
|
|
84
84
|
{dark ? (
|
|
85
85
|
<motion.span
|
|
86
86
|
key="moon"
|
|
87
|
-
style={{ display: 'inline-flex' }} // theme-allow
|
|
87
|
+
style={{ display: 'inline-flex' }} // theme-allow inline-display — a framer-motion glyph wrapper, not a Mantine layout node
|
|
88
88
|
initial={{ opacity: 0, scale: 0.6, rotate: 30 }}
|
|
89
89
|
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
|
90
90
|
exit={{ opacity: 0, scale: 0.6, rotate: -30 }}
|
|
@@ -95,7 +95,7 @@ function SchemeGlyph({ dark, reduceMotion }: { dark: boolean; reduceMotion: bool
|
|
|
95
95
|
) : (
|
|
96
96
|
<motion.span
|
|
97
97
|
key="sun"
|
|
98
|
-
style={{ display: 'inline-flex' }} // theme-allow
|
|
98
|
+
style={{ display: 'inline-flex' }} // theme-allow inline-display — a framer-motion glyph wrapper, not a Mantine layout node
|
|
99
99
|
initial={{ opacity: 0, scale: 0.6, rotate: -30 }}
|
|
100
100
|
animate={{ opacity: 1, scale: 1, rotate: 0 }}
|
|
101
101
|
exit={{ opacity: 0, scale: 0.6, rotate: 30 }}
|
package/src/tokens/index.ts
CHANGED
|
@@ -533,6 +533,8 @@ function spaceDecls(space: SpaceValues): string[] {
|
|
|
533
533
|
decl('space-app-header-mobile-actions-height', `${space.step.appHeaderMobileActionsHeight}px`),
|
|
534
534
|
decl('space-mobile-nav-tab-gap', `${space.step.mobileNavTabGap}px`),
|
|
535
535
|
decl('space-mobile-nav-icon-size', `${space.step.mobileNavIconSize}px`),
|
|
536
|
+
decl('space-mobile-nav-tab-inset-y', `${space.step.mobileNavTabInsetY}px`),
|
|
537
|
+
decl('space-mobile-nav-tab-inset-x', `${space.step.mobileNavTabInsetX}px`),
|
|
536
538
|
decl('space-mobile-nav-row-height', `${space.step.mobileNavRowHeight}px`),
|
|
537
539
|
decl('space-agent-rail-inset-x', `${space.step.agentRailInsetX}px`),
|
|
538
540
|
decl('space-agent-part-gap-top', `${space.step.agentPartGapTop}px`),
|
|
@@ -758,13 +760,13 @@ export type BuildPaletteOpts = {
|
|
|
758
760
|
*/
|
|
759
761
|
mediaFallback?: boolean
|
|
760
762
|
/**
|
|
761
|
-
* How much of the SPACING set to emit. Default: `'all'` (every one of the
|
|
763
|
+
* How much of the SPACING set to emit. Default: `'all'` (every one of the 108 `--vx-space-*`
|
|
762
764
|
* declarations the framework's own components read).
|
|
763
765
|
*
|
|
764
766
|
* `'core'` keeps the 9 generic anchors — the stack rhythm, the two control heights, the row
|
|
765
|
-
* insets — and drops the
|
|
767
|
+
* insets — and drops the 99 named for a specific basalt component
|
|
766
768
|
* (`--vx-space-agent-transcript-inset`, `--vx-space-toc-sub-indent`, …), taking the emitted set
|
|
767
|
-
* from
|
|
769
|
+
* from 202 variables to 103. Nothing outside basalt's own React components reads those, so a
|
|
768
770
|
* framework-free consumer is carrying them for nothing.
|
|
769
771
|
*
|
|
770
772
|
* This is a SPACING filter only: color, radius, shadow, type and status variables are identical
|
package/src/tokens/palette.ts
CHANGED
|
@@ -97,12 +97,12 @@ export const p = (fam: readonly string[], light = 2, dark = 3): ColorPair => ({
|
|
|
97
97
|
*/
|
|
98
98
|
export const SHADOW = {
|
|
99
99
|
card: {
|
|
100
|
-
light: '0 1px 2px rgba(28,25,23,0.05), 0 0 0 1px var(--vx-surface-hairline)',
|
|
101
|
-
dark: '0 1px 3px rgba(0,0,0,0.4), inset 0 0 0 1px color-mix(in srgb, #ffffff 4%, transparent)',
|
|
100
|
+
light: '0 1px 2px rgba(28, 25, 23, 0.05), 0 0 0 1px var(--vx-surface-hairline)',
|
|
101
|
+
dark: '0 1px 3px rgba(0, 0, 0, 0.4), inset 0 0 0 1px color-mix(in srgb, #ffffff 4%, transparent)',
|
|
102
102
|
},
|
|
103
103
|
ctrl: {
|
|
104
|
-
light: '0 1px 2px rgba(28,25,23,0.12)',
|
|
105
|
-
dark: '0 1px 2px rgba(0,0,0,0.35)',
|
|
104
|
+
light: '0 1px 2px rgba(28, 25, 23, 0.12)',
|
|
105
|
+
dark: '0 1px 2px rgba(0, 0, 0, 0.35)',
|
|
106
106
|
},
|
|
107
107
|
/**
|
|
108
108
|
* Interactive-control depth — the ONE value every raised Button/ActionIcon/Input variant shares
|
|
@@ -132,16 +132,16 @@ export const SHADOW = {
|
|
|
132
132
|
* ever needs more, raise the DROP. Do not reintroduce an edge; both shapes have been tried.
|
|
133
133
|
*/
|
|
134
134
|
raised: {
|
|
135
|
-
light: '0 1px 2px rgba(28,25,23,0.10)',
|
|
136
|
-
dark: '0 1px 2px rgba(0,0,0,0.35), inset 0 0 0 1px color-mix(in srgb, #ffffff 8%, transparent)',
|
|
135
|
+
light: '0 1px 2px rgba(28, 25, 23, 0.10)',
|
|
136
|
+
dark: '0 1px 2px rgba(0, 0, 0, 0.35), inset 0 0 0 1px color-mix(in srgb, #ffffff 8%, transparent)',
|
|
137
137
|
},
|
|
138
138
|
// Floating-layer elevation (menus, popovers, tooltips, modals, drawers) — a REAL drop shadow, a
|
|
139
139
|
// step above `card`, so detached surfaces read as lifted off the page (`docs/DESIGN-SPEC.md` §5).
|
|
140
140
|
// Unlike `card`, no embedded ring: floating surfaces carry a real 1px `--vx-surface-border` so
|
|
141
141
|
// Mantine's rotated-square arrow inherits a proper edge.
|
|
142
142
|
overlay: {
|
|
143
|
-
light: '0 8px 24px rgba(28,25,23,0.10)',
|
|
144
|
-
dark: '0 8px 24px rgba(0,0,0,0.5)',
|
|
143
|
+
light: '0 8px 24px rgba(28, 25, 23, 0.10)',
|
|
144
|
+
dark: '0 8px 24px rgba(0, 0, 0, 0.5)',
|
|
145
145
|
},
|
|
146
146
|
} as const
|
|
147
147
|
|
|
@@ -613,8 +613,14 @@ const SPACE_STEP_BASE = {
|
|
|
613
613
|
* `env(safe-area-inset-bottom)` to it. JS-consumed (AppShell `footer.height`), no `--vx-*` var,
|
|
614
614
|
* same shape as `appShellHeaderHeight`. Floored at 48px in `deriveSpacing`. */
|
|
615
615
|
mobileNavBarHeight: 56,
|
|
616
|
-
/** Slot icon box.
|
|
616
|
+
/** Slot icon box. Also the MIN box of the icon slot itself, so the active pill keeps its shape
|
|
617
|
+
* when a destination ships no icon (an icon-less consumer is a supported configuration). */
|
|
617
618
|
mobileNavIconSize: 24,
|
|
619
|
+
/** Active-pill inset around the slot icon, vertical. Sub-scale on its own, but it is one half of
|
|
620
|
+
* a pair with `mobileNavTabInsetX` — a step keeps the pill's shape tracking density. */
|
|
621
|
+
mobileNavTabInsetY: 2,
|
|
622
|
+
/** Active-pill inset around the slot icon, horizontal. */
|
|
623
|
+
mobileNavTabInsetX: 12,
|
|
618
624
|
/** Menu/sheet row height. Floored at 44px in `deriveSpacing` (Apple HIG 44pt / WCAG 2.5.5 AAA). */
|
|
619
625
|
mobileNavRowHeight: 44,
|
|
620
626
|
/** More/group `<Menu width={…}>`. JS-consumed, no var — same rationale as
|
|
@@ -1059,22 +1065,22 @@ function buildPaletteDataUncached(config: DeriveConfig) {
|
|
|
1059
1065
|
const NEUTRAL = {
|
|
1060
1066
|
line: { light: BP.gray[0], dark: BP.gray[4] }, // primary line
|
|
1061
1067
|
line2: { light: BP.gray[1], dark: BP.gray[2] }, // secondary line
|
|
1062
|
-
axis: { light: inkRgba(inkLight, 0.6), dark: 'rgba(255,255,255,0.6)' },
|
|
1063
|
-
axisStroke: { light: inkRgba(inkLight, 0.12), dark: 'rgba(255,255,255,0.12)' },
|
|
1064
|
-
grid: { light: inkRgba(inkLight, 0.07), dark: 'rgba(255,255,255,0.06)' },
|
|
1065
|
-
crosshair: { light: inkRgba(inkLight, 0.32), dark: 'rgba(255,255,255,0.42)' },
|
|
1068
|
+
axis: { light: inkRgba(inkLight, 0.6), dark: 'rgba(255, 255, 255, 0.6)' },
|
|
1069
|
+
axisStroke: { light: inkRgba(inkLight, 0.12), dark: 'rgba(255, 255, 255, 0.12)' },
|
|
1070
|
+
grid: { light: inkRgba(inkLight, 0.07), dark: 'rgba(255, 255, 255, 0.06)' },
|
|
1071
|
+
crosshair: { light: inkRgba(inkLight, 0.32), dark: 'rgba(255, 255, 255, 0.42)' },
|
|
1066
1072
|
dotStroke: { light: BP.white, dark: BP.darkGray[1] }, // matches chart-area bg
|
|
1067
|
-
tooltipBg: { light: BP.white, dark: 'rgba(39,39,42,0.96)' },
|
|
1068
|
-
tooltipText: { light: inkRgba(inkLight, 0.88), dark: 'rgba(255,255,255,0.88)' },
|
|
1069
|
-
tooltipMuted: { light: inkRgba(inkLight, 0.5), dark: 'rgba(255,255,255,0.5)' },
|
|
1073
|
+
tooltipBg: { light: BP.white, dark: 'rgba(39, 39, 42, 0.96)' },
|
|
1074
|
+
tooltipText: { light: inkRgba(inkLight, 0.88), dark: 'rgba(255, 255, 255, 0.88)' },
|
|
1075
|
+
tooltipMuted: { light: inkRgba(inkLight, 0.5), dark: 'rgba(255, 255, 255, 0.5)' },
|
|
1070
1076
|
tooltipBorder: { light: `1px solid ${BP.lightGray[1]}`, dark: 'none' },
|
|
1071
1077
|
tooltipShadow: {
|
|
1072
1078
|
light: `0 2px 8px ${inkRgba(inkLight, 0.1)}`,
|
|
1073
|
-
dark: '0 2px 8px rgba(0,0,0,0.35)',
|
|
1079
|
+
dark: '0 2px 8px rgba(0, 0, 0, 0.35)',
|
|
1074
1080
|
},
|
|
1075
1081
|
// Chart legend text — same DERIVED-ink treatment as axis/grid/tooltip* above (light keys off
|
|
1076
1082
|
// `inkLight`; dark stays flat white like every other neutral chart-chrome pair here).
|
|
1077
|
-
legendText: { light: inkRgba(inkLight, 0.82), dark: 'rgba(255,255,255,0.92)' },
|
|
1083
|
+
legendText: { light: inkRgba(inkLight, 0.82), dark: 'rgba(255, 255, 255, 0.92)' },
|
|
1078
1084
|
// Base neutral for hairlines / muted / DIMMED text / overlays — apply opacity via alpha().
|
|
1079
1085
|
// Binds to --mantine-color-dimmed, so it is THE dimmed-text lever. Equal to `INK.muted` (the
|
|
1080
1086
|
// spec's "muted (secondary text)" row) — kept as its own pair for the pre-existing `--vx-neutral`
|
package/src/vite-color-mix.ts
CHANGED
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
* what a color parser must never do.
|
|
29
29
|
*/
|
|
30
30
|
const NAMED_COLORS: Record<string, string> = {
|
|
31
|
-
white: '#ffffff', // theme-allow — CSS keyword definition, not a design color
|
|
32
|
-
black: '#000000', // theme-allow — CSS keyword definition, not a design color
|
|
31
|
+
white: '#ffffff', // theme-allow raw-hex — the CSS keyword's definition, not a design color
|
|
32
|
+
black: '#000000', // theme-allow raw-hex — the CSS keyword's definition, not a design color
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
function normalizeHex(hex: string): string {
|