basalt-ui 1.21.0 → 1.23.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 +11 -1
- package/CHANGELOG.md +33 -0
- package/MIGRATING.md +205 -9
- package/README.md +63 -16
- package/agent/rules/basalt-app.md +39 -9
- package/agent/rules/basalt-charts.md +41 -6
- package/agent/rules/basalt-router.md +1 -1
- package/agent/rules/basalt-state.md +4 -4
- package/agent/rules/basalt-tokens.md +25 -4
- package/agent/skills/basalt-app/SKILL.md +4 -3
- package/agent/skills/basalt-charts/SKILL.md +23 -8
- package/agent/templates/DESIGN.md.tpl +3 -1
- package/configs/oxlint-plugin.js +86 -9
- package/configs/oxlint-plugin.test.ts +173 -0
- 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 +113 -0
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -0
- package/dist/charts/kinds/BandStrip.js +218 -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 +93 -0
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -0
- package/dist/charts/kinds/MirroredBars.js +315 -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 +9 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +275 -48
- 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 +67 -0
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +70 -11
- package/dist/vite.d.ts +44 -2
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +71 -26
- 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 +200 -0
- package/src/charts/kinds/BandStrip.tsx +364 -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 +193 -0
- package/src/charts/kinds/MirroredBars.tsx +455 -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/check-theme.test.ts +35 -0
- package/src/cli/help.test.ts +5 -1
- package/src/cli/index.ts +552 -79
- package/src/cli/shipped-versions.test.ts +83 -0
- package/src/cli/toolchain-wiring.test.ts +362 -6
- package/src/cli/waiver-audit.test.ts +88 -4
- package/src/guard/check-source.test.ts +293 -0
- package/src/guard/index.ts +209 -20
- package/src/vite.test.ts +112 -0
- package/src/vite.ts +147 -31
|
@@ -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
|
+
}
|
|
@@ -742,3 +742,38 @@ describe('severity — warnings report, errors fail', () => {
|
|
|
742
742
|
expect(err).toContain('✖ Theme guard')
|
|
743
743
|
})
|
|
744
744
|
})
|
|
745
|
+
|
|
746
|
+
// ── the manifest waiver hint, gated on the profile ────────────────────────────────────────────
|
|
747
|
+
//
|
|
748
|
+
// rollhook. The hint leads with `basaltAppPlugin`, which is right for a Mantine app — a hex in a
|
|
749
|
+
// manifest can never be right, and the plugin removes the hand-copy entirely. A `tokens-only`
|
|
750
|
+
// consumer has definitionally opted out of that layer: rollhook's Astro site has no index.html to
|
|
751
|
+
// transform, imports no basalt JavaScript at all, and owns maskable icons the plugin does not emit.
|
|
752
|
+
// Leading with it there is advice the consumer cannot take.
|
|
753
|
+
|
|
754
|
+
describe('check-theme — the manifest waiver hint follows the profile', () => {
|
|
755
|
+
function manifestFixture(basalt: Record<string, unknown>): void {
|
|
756
|
+
writeFileSync(resolve(dir, 'package.json'), JSON.stringify({ name: 'fixture', basalt }))
|
|
757
|
+
mkdirSync(resolve(dir, 'src'), { recursive: true })
|
|
758
|
+
writeFileSync(resolve(dir, 'src', 'app.tsx'), 'export const App = () => null\n')
|
|
759
|
+
mkdirSync(resolve(dir, 'public'), { recursive: true })
|
|
760
|
+
writeFileSync(
|
|
761
|
+
resolve(dir, 'public', 'site.webmanifest'),
|
|
762
|
+
JSON.stringify({ name: 'x', theme_color: '#18181b' }, null, 2),
|
|
763
|
+
)
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
it('offers basaltAppPlugin first to a framework consumer', () => {
|
|
767
|
+
manifestFixture({ roots: ['src'] })
|
|
768
|
+
const { err } = run()
|
|
769
|
+
expect(err).toContain('let basaltAppPlugin emit it')
|
|
770
|
+
})
|
|
771
|
+
|
|
772
|
+
it('leads with the member for a tokens-only consumer, and says which remedy it withheld', () => {
|
|
773
|
+
manifestFixture({ roots: ['src'], profile: 'tokens-only' })
|
|
774
|
+
const { err } = run()
|
|
775
|
+
expect(err).toContain('JSON has no comments — declare a deliberate exception')
|
|
776
|
+
expect(err).not.toContain('let basaltAppPlugin emit it')
|
|
777
|
+
expect(err).toContain('a tokens-only consumer has opted out of')
|
|
778
|
+
})
|
|
779
|
+
})
|
package/src/cli/help.test.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
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 { existsSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
|
|
8
8
|
import { tmpdir } from 'node:os'
|
|
9
9
|
import { resolve } from 'node:path'
|
|
10
10
|
|
|
@@ -86,6 +86,10 @@ describe('run() --help short-circuits before dispatch', () => {
|
|
|
86
86
|
})
|
|
87
87
|
|
|
88
88
|
it('`sync` without --help still runs for real and writes the manifest (control case)', () => {
|
|
89
|
+
// `sync` refuses to scaffold where nothing was ever installed, so the control case has to be a
|
|
90
|
+
// repo that HAS an install — which is what `sync` is for.
|
|
91
|
+
mkdirSync(resolve(dir, '.basalt'), { recursive: true })
|
|
92
|
+
writeFileSync(resolve(dir, MANIFEST_PATH), JSON.stringify({ version: 1, files: {} }))
|
|
89
93
|
const { code } = capture(() => run(['sync'], dir))
|
|
90
94
|
expect(code).toBe(0)
|
|
91
95
|
expect(existsSync(resolve(dir, MANIFEST_PATH))).toBe(true)
|