basalt-ui 1.22.0 → 1.23.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -12
- package/CHANGELOG.md +28 -0
- package/MIGRATING.md +207 -12
- package/README.md +74 -21
- package/agent/rules/basalt-app.md +33 -3
- package/agent/rules/basalt-charts.md +78 -7
- package/agent/rules/basalt-content.md +3 -3
- package/agent/rules/basalt-mantine.md +4 -4
- package/agent/rules/basalt-tokens.md +35 -6
- package/agent/skills/basalt-app/SKILL.md +25 -12
- package/agent/skills/basalt-charts/SKILL.md +27 -10
- package/agent/templates/CLAUDE-block.md.tpl +3 -2
- package/agent/templates/DESIGN.md.tpl +3 -1
- package/configs/oxlint-plugin.js +49 -8
- package/configs/oxlint-plugin.test.ts +88 -31
- package/dist/charts/hooks/useBandPlot.d.ts +138 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -0
- package/dist/charts/hooks/useBandPlot.js +127 -0
- package/dist/charts/index.d.ts +4 -0
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +14 -0
- package/dist/charts/kinds/BandStrip.d.ts +127 -0
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -0
- package/dist/charts/kinds/BandStrip.js +271 -0
- package/dist/charts/kinds/BandStrip.test.d.ts +2 -0
- package/dist/charts/kinds/BandStrip.test.d.ts.map +1 -0
- package/dist/charts/kinds/Bars.d.ts +5 -1
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +2 -0
- package/dist/charts/kinds/MirroredBars.d.ts +95 -0
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -0
- package/dist/charts/kinds/MirroredBars.js +328 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts +2 -0
- package/dist/charts/kinds/MirroredBars.test.d.ts.map +1 -0
- package/dist/charts/kinds/MultiLine.d.ts +5 -1
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +2 -0
- package/dist/charts/kinds/StackedArea.d.ts +5 -1
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +2 -0
- package/dist/charts/kinds/ZonedLine.d.ts +5 -1
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +2 -0
- package/dist/charts/primitives/CartesianChart.d.ts +18 -2
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +5 -2
- package/dist/charts/primitives/HatchPattern.d.ts +31 -0
- package/dist/charts/primitives/HatchPattern.d.ts.map +1 -0
- package/dist/charts/primitives/HatchPattern.js +31 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +228 -31
- package/dist/cli/shipped-versions.test.d.ts +2 -0
- package/dist/cli/shipped-versions.test.d.ts.map +1 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +60 -13
- package/dist/vite.d.ts +42 -3
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +67 -30
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/charts/hooks/useBandPlot.ts +302 -0
- package/src/charts/index.ts +14 -0
- package/src/charts/kinds/BandStrip.test.tsx +282 -0
- package/src/charts/kinds/BandStrip.tsx +457 -0
- package/src/charts/kinds/Bars.tsx +7 -1
- package/src/charts/kinds/DualPanel.test.tsx +10 -4
- package/src/charts/kinds/MirroredBars.test.tsx +232 -0
- package/src/charts/kinds/MirroredBars.tsx +491 -0
- package/src/charts/kinds/MultiLine.tsx +7 -1
- package/src/charts/kinds/StackedArea.tsx +7 -1
- package/src/charts/kinds/ZonedLine.tsx +7 -1
- package/src/charts/primitives/CartesianChart.test.tsx +86 -4
- package/src/charts/primitives/CartesianChart.tsx +28 -3
- package/src/charts/primitives/HatchPattern.tsx +55 -0
- package/src/cli/help.test.ts +74 -1
- package/src/cli/index.ts +436 -52
- package/src/cli/shipped-versions.test.ts +83 -0
- package/src/cli/tokens-css.test.ts +62 -0
- package/src/cli/toolchain-wiring.test.ts +171 -4
- package/src/cli/waiver-audit.test.ts +7 -3
- package/src/guard/check-source.test.ts +211 -42
- package/src/guard/index.ts +149 -23
- package/src/vite.test.ts +79 -0
- package/src/vite.ts +141 -39
|
@@ -151,8 +151,24 @@ export type CartesianChartProps<T> = {
|
|
|
151
151
|
/** Passing `y2` is what makes the chart dual-axis: it draws the right axis and widens the
|
|
152
152
|
* right margin by measurement. Series opt in with `axis: 'right'`. */
|
|
153
153
|
y2?: AxisConfig<T>
|
|
154
|
-
/** Exact number of x ticks. Default: as many as fit (`smartTicks`).
|
|
154
|
+
/** Exact number of x ticks. Default: as many as fit (`smartTicks`). Ignored when
|
|
155
|
+
* `xTickValues` is set. */
|
|
155
156
|
xTicks?: number
|
|
157
|
+
/**
|
|
158
|
+
* Which domain keys get a tick, chosen from the full key list and the resolved plot width.
|
|
159
|
+
* Takes precedence over `xTicks`; omit both for `smartTicks`.
|
|
160
|
+
*
|
|
161
|
+
* A COUNT is not always enough. `smartTicks`/`smartTicksEvery` append the final key
|
|
162
|
+
* unconditionally, so when the step does not land on the last index that appended tick sits a
|
|
163
|
+
* partial step from its neighbour — measured on a 24h window, two `DD.MM HH:MM` labels printed
|
|
164
|
+
* on top of each other at the right edge, at every tick count. The only fix a count can express
|
|
165
|
+
* is thinning the whole axis to avoid one crowded pair. A consumer whose labels are richer than
|
|
166
|
+
* the default `DD.MM` needs to pick the VALUES, which until now meant measuring the container
|
|
167
|
+
* itself with `useChartSize` to derive a count — the chart already knows its own width.
|
|
168
|
+
*
|
|
169
|
+
* `BandStrip`/`MirroredBars` take the identical prop, so the seam does not fork by kind.
|
|
170
|
+
*/
|
|
171
|
+
xTickValues?: (keys: readonly string[], xMax: number) => readonly string[]
|
|
156
172
|
/** X tick label formatter. Default `fmtAxisDate` (DD.MM). */
|
|
157
173
|
formatX?: (key: string) => string
|
|
158
174
|
/** Value-range bands on the left scale, drawn behind the marks. */
|
|
@@ -267,6 +283,7 @@ export function CartesianChart<T>({
|
|
|
267
283
|
y,
|
|
268
284
|
y2,
|
|
269
285
|
xTicks,
|
|
286
|
+
xTickValues,
|
|
270
287
|
formatX = fmtAxisDate,
|
|
271
288
|
zones,
|
|
272
289
|
xZones,
|
|
@@ -305,6 +322,7 @@ export function CartesianChart<T>({
|
|
|
305
322
|
{...(y !== undefined && { y })}
|
|
306
323
|
{...(y2 !== undefined && { y2 })}
|
|
307
324
|
{...(xTicks !== undefined && { xTicks })}
|
|
325
|
+
{...(xTickValues !== undefined && { xTickValues })}
|
|
308
326
|
formatX={formatX}
|
|
309
327
|
{...(zones !== undefined && { zones })}
|
|
310
328
|
{...(xZones !== undefined && { xZones })}
|
|
@@ -343,6 +361,7 @@ function CartesianPlot<T>({
|
|
|
343
361
|
y,
|
|
344
362
|
y2,
|
|
345
363
|
xTicks,
|
|
364
|
+
xTickValues,
|
|
346
365
|
formatX = fmtAxisDate,
|
|
347
366
|
zones,
|
|
348
367
|
xZones,
|
|
@@ -459,9 +478,15 @@ function CartesianPlot<T>({
|
|
|
459
478
|
[rightDomain, yMax, y2?.nice],
|
|
460
479
|
)
|
|
461
480
|
|
|
481
|
+
// Resolution order: explicit VALUES win, then an explicit COUNT, then as many as fit.
|
|
462
482
|
const tickValues = useMemo(
|
|
463
|
-
() =>
|
|
464
|
-
|
|
483
|
+
() =>
|
|
484
|
+
xTickValues !== undefined
|
|
485
|
+
? [...xTickValues(keys, xMax)]
|
|
486
|
+
: xTicks === undefined
|
|
487
|
+
? smartTicks(keys, xMax)
|
|
488
|
+
: smartTicksEvery(keys, xTicks),
|
|
489
|
+
[keys, xMax, xTicks, xTickValues],
|
|
465
490
|
)
|
|
466
491
|
|
|
467
492
|
const cursor = useChartCursor<T>({
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ReactNode } from 'react'
|
|
2
|
+
import { alpha } from '../../tokens'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A diagonal-hatch `<pattern>` — the one fill that reads as **absence** rather than as a weak
|
|
6
|
+
* measurement.
|
|
7
|
+
*
|
|
8
|
+
* A faint solid tint cannot do this job: it sits on the same perceptual axis as every other
|
|
9
|
+
* intensity of the same hue, so "nothing was measured here" and "everything measured here was
|
|
10
|
+
* fine" collapse into neighbouring steps of one ramp. Hatching is off that axis entirely and
|
|
11
|
+
* survives being placed beside a colour ramp of the same hue.
|
|
12
|
+
*
|
|
13
|
+
* `id` must be unique per document — `BandStrip` / `MirroredBars` namespace theirs with `chartId`.
|
|
14
|
+
* `opacity` is applied through `alpha()` on the stroke rather than as an `opacity` attribute, so
|
|
15
|
+
* the hue keeps resolving per colour scheme.
|
|
16
|
+
*/
|
|
17
|
+
export function HatchPattern({
|
|
18
|
+
id,
|
|
19
|
+
color,
|
|
20
|
+
opacity = 0.55,
|
|
21
|
+
size = 6,
|
|
22
|
+
}: {
|
|
23
|
+
id: string
|
|
24
|
+
color: string
|
|
25
|
+
opacity?: number
|
|
26
|
+
size?: number
|
|
27
|
+
}): ReactNode {
|
|
28
|
+
return (
|
|
29
|
+
<pattern
|
|
30
|
+
id={id}
|
|
31
|
+
width={size}
|
|
32
|
+
height={size}
|
|
33
|
+
patternUnits="userSpaceOnUse"
|
|
34
|
+
patternTransform="rotate(45)"
|
|
35
|
+
>
|
|
36
|
+
<line x1={0} y1={0} x2={0} y2={size} stroke={alpha(color, opacity)} strokeWidth={size / 3} />
|
|
37
|
+
</pattern>
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** `fill` value referencing a {@link HatchPattern} by id. */
|
|
42
|
+
export function hatchFill(id: string): string {
|
|
43
|
+
return `url(#${id})`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The pattern's own repeat, shrunk to fit the band it textures.
|
|
48
|
+
*
|
|
49
|
+
* Left at a fixed 6px a sub-pixel band draws less than one diagonal rule, so a fill/hatch split
|
|
50
|
+
* inside one band antialiases into a single smudge no reader can tell from a fully-measured band.
|
|
51
|
+
* Floored at 2 — below that the stroke has nothing to render on.
|
|
52
|
+
*/
|
|
53
|
+
export function hatchSizeFor(bandWidth: number): number {
|
|
54
|
+
return Math.max(2, Math.min(6, Math.round(bandWidth)))
|
|
55
|
+
}
|
package/src/cli/help.test.ts
CHANGED
|
@@ -4,7 +4,15 @@
|
|
|
4
4
|
* matched `cmd === 'sync'` first and `--help` was just another item in `flags`, ignored).
|
|
5
5
|
*/
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it } from 'bun:test'
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
existsSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
readFileSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
writeFileSync,
|
|
15
|
+
} from 'node:fs'
|
|
8
16
|
import { tmpdir } from 'node:os'
|
|
9
17
|
import { resolve } from 'node:path'
|
|
10
18
|
|
|
@@ -95,3 +103,68 @@ describe('run() --help short-circuits before dispatch', () => {
|
|
|
95
103
|
expect(existsSync(resolve(dir, MANIFEST_PATH))).toBe(true)
|
|
96
104
|
})
|
|
97
105
|
})
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* `--version` and the unknown-flag gate — one failure mode, two halves.
|
|
109
|
+
*
|
|
110
|
+
* Six consumer repos in one upgrade round reached for `basalt-ui --version` to prove WHICH CLI ran
|
|
111
|
+
* (a `bunx` cache does not re-resolve, so a pinned version and the version that gates you can
|
|
112
|
+
* differ) and got a usage dump with no version in it. The other half is the same shape: an
|
|
113
|
+
* unrecognized FLAG was silently ignored and the command exited 0, so a mistyped gate read as a
|
|
114
|
+
* passing gate.
|
|
115
|
+
*/
|
|
116
|
+
describe('run() --version and the unknown-flag gate', () => {
|
|
117
|
+
const version = (
|
|
118
|
+
JSON.parse(readFileSync(resolve(import.meta.dir, '../../package.json'), 'utf8')) as {
|
|
119
|
+
version: string
|
|
120
|
+
}
|
|
121
|
+
).version
|
|
122
|
+
|
|
123
|
+
it('`--version` prints the bare resolved version and exits 0', () => {
|
|
124
|
+
const { code, log } = capture(() => run(['--version'], dir))
|
|
125
|
+
expect(code).toBe(0)
|
|
126
|
+
expect(log.trim()).toBe(version)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('`-v` and `version` are the same thing', () => {
|
|
130
|
+
expect(capture(() => run(['-v'], dir)).log.trim()).toBe(version)
|
|
131
|
+
expect(capture(() => run(['version'], dir)).log.trim()).toBe(version)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('the printed line is greppable on its own — no usage block around it', () => {
|
|
135
|
+
const { log } = capture(() => run(['--version'], dir))
|
|
136
|
+
expect(log).not.toContain('Usage: basalt')
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
it('an unrecognized flag exits 1 and NAMES it, on a command that would otherwise pass', () => {
|
|
140
|
+
const { code, log } = capture(() => run(['doctor', '--json'], dir))
|
|
141
|
+
expect(code).toBe(1)
|
|
142
|
+
expect(log).toContain("unrecognized flag '--json'")
|
|
143
|
+
expect(log).toContain('--tokens-only')
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('a near-miss flag does not slip through as a silent no-op', () => {
|
|
147
|
+
const { code, log } = capture(() => run(['check-theme', '--audit-allow'], dir))
|
|
148
|
+
expect(code).toBe(1)
|
|
149
|
+
expect(log).toContain("unrecognized flag '--audit-allow'")
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('a value-taking flag does not make its VALUE look like an unknown flag', () => {
|
|
153
|
+
const out = resolve(dir, 'tokens.css')
|
|
154
|
+
const { code } = capture(() => run(['tokens:css', '--out', out, '--only', 'core'], dir))
|
|
155
|
+
expect(code).toBe(0)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('an unknown COMMAND names what it did not understand, above the usage block', () => {
|
|
159
|
+
const { code, log } = capture(() => run(['check-theme --audit-allows'], dir))
|
|
160
|
+
expect(code).toBe(1)
|
|
161
|
+
expect(log).toContain("unknown command 'check-theme --audit-allows'")
|
|
162
|
+
expect(log).toContain('Usage: basalt')
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('`--help` still wins over the flag gate, so a reader can always find out what is accepted', () => {
|
|
166
|
+
const { code, log } = capture(() => run(['doctor', '--json', '--help'], dir))
|
|
167
|
+
expect(code).toBe(0)
|
|
168
|
+
expect(log).toContain('Usage: basalt')
|
|
169
|
+
})
|
|
170
|
+
})
|