@squadbase/vantage 0.6.2-dev1.5aae04e → 0.6.2-dev1.72360fb
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/dist/chunk-2IAPIOPS.js +152 -0
- package/dist/chunk-2IAPIOPS.js.map +1 -0
- package/dist/chunk-57GHR5LN.js +163 -0
- package/dist/chunk-57GHR5LN.js.map +1 -0
- package/dist/chunk-6OTTGAPO.js +44 -0
- package/dist/chunk-6OTTGAPO.js.map +1 -0
- package/dist/{chunk-XPQSTZUU.js → chunk-7S52M2SL.js} +4 -3
- package/dist/chunk-7S52M2SL.js.map +1 -0
- package/dist/chunk-JXFCSF3K.js +673 -0
- package/dist/chunk-JXFCSF3K.js.map +1 -0
- package/dist/{chunk-HKSVYMYJ.js → chunk-QOXXOFGL.js} +61 -929
- package/dist/chunk-QOXXOFGL.js.map +1 -0
- package/dist/components/index.d.ts +11 -247
- package/dist/components/index.js +5 -2
- package/dist/delta-text-CvwBGJ95.d.ts +15 -0
- package/dist/doc/index.d.ts +215 -2
- package/dist/doc/index.js +300 -3
- package/dist/doc/index.js.map +1 -1
- package/dist/format-U0cQl069.d.ts +243 -0
- package/dist/slide/index.d.ts +272 -1
- package/dist/slide/index.js +966 -1
- package/dist/slide/index.js.map +1 -1
- package/dist/table/index.js +5 -2
- package/dist/table/index.js.map +1 -1
- package/dist/ui/index.d.ts +4 -1
- package/dist/ui/index.js +3 -3
- package/dist/ui/index.js.map +1 -1
- package/docs/en/components.md +13 -2
- package/docs/en/doc/delta-text.md +40 -0
- package/docs/en/doc/doc-action-items.md +49 -0
- package/docs/en/doc/doc-callout.md +38 -0
- package/docs/en/doc/doc-data-table.md +60 -0
- package/docs/en/doc/doc-executive-summary.md +64 -0
- package/docs/en/doc/doc-figure.md +56 -0
- package/docs/en/doc/doc-key-findings.md +61 -0
- package/docs/en/doc/doc-kpi-scorecard.md +80 -0
- package/docs/en/doc/doc-notes-and-sources.md +43 -0
- package/docs/en/doc/doc-report-header.md +46 -0
- package/docs/en/parts/area-chart.md +99 -0
- package/docs/en/parts/bar-chart.md +91 -0
- package/docs/en/parts/bar-list.md +50 -0
- package/docs/en/parts/category-bar.md +37 -0
- package/docs/en/parts/chart-frame.md +39 -0
- package/docs/en/parts/delta.md +4 -3
- package/docs/en/parts/donut-chart.md +72 -0
- package/docs/en/parts/funnel-steps.md +2 -1
- package/docs/en/parts/heatmap.md +53 -0
- package/docs/en/parts/kpi-card.md +2 -1
- package/docs/en/parts/legend.md +35 -0
- package/docs/en/parts/line-chart.md +94 -0
- package/docs/en/parts/progress-circle.md +33 -0
- package/docs/en/parts/sample-data.md +39 -0
- package/docs/en/parts/scatter-chart.md +56 -0
- package/docs/en/parts/tracker.md +36 -0
- package/docs/en/parts/updated-at.md +30 -0
- package/docs/en/parts/waterfall-chart.md +56 -0
- package/docs/en/slide/slide-chart-full.md +48 -0
- package/docs/en/slide/slide-chart-insight.md +52 -0
- package/docs/en/slide/slide-columns.md +46 -0
- package/docs/en/slide/slide-comparison.md +68 -0
- package/docs/en/slide/slide-cover.md +39 -0
- package/docs/en/slide/slide-exec-summary.md +47 -0
- package/docs/en/slide/slide-heading.md +38 -0
- package/docs/en/slide/slide-kpi-grid.md +55 -0
- package/docs/en/slide/slide-kpi-hero.md +58 -0
- package/docs/en/slide/slide-next-actions.md +49 -0
- package/docs/en/slide/slide-scorecard-table.md +54 -0
- package/docs/en/slide/slide-section.md +44 -0
- package/docs/en/slide/slide-source-line.md +34 -0
- package/docs/en/ui/alert-dialog.md +42 -0
- package/docs/en/ui/avatar.md +31 -0
- package/docs/en/ui/combobox.md +74 -0
- package/docs/en/ui/drawer.md +41 -0
- package/docs/en/ui/empty.md +1 -1
- package/docs/en/ui/error-state.md +4 -3
- package/docs/en/ui/field.md +42 -0
- package/docs/en/ui/hover-card.md +21 -0
- package/docs/en/ui/kbd.md +17 -0
- package/docs/en/ui/loading.md +1 -1
- package/docs/en/ui/pagination.md +36 -0
- package/docs/en/ui/radio-group.md +21 -0
- package/docs/en/ui/toast.md +44 -0
- package/docs/en/ui/toggle-group.md +4 -3
- package/docs/index.json +857 -77
- package/docs/ja/components.md +12 -1
- package/docs/ja/doc/delta-text.md +40 -0
- package/docs/ja/doc/doc-action-items.md +49 -0
- package/docs/ja/doc/doc-callout.md +38 -0
- package/docs/ja/doc/doc-data-table.md +60 -0
- package/docs/ja/doc/doc-executive-summary.md +64 -0
- package/docs/ja/doc/doc-figure.md +56 -0
- package/docs/ja/doc/doc-key-findings.md +61 -0
- package/docs/ja/doc/doc-kpi-scorecard.md +80 -0
- package/docs/ja/doc/doc-notes-and-sources.md +43 -0
- package/docs/ja/doc/doc-report-header.md +46 -0
- package/docs/ja/parts/area-chart.md +49 -25
- package/docs/ja/parts/bar-chart.md +50 -24
- package/docs/ja/parts/bar-list.md +31 -8
- package/docs/ja/parts/chart-frame.md +36 -0
- package/docs/ja/parts/delta.md +5 -4
- package/docs/ja/parts/donut-chart.md +45 -26
- package/docs/ja/parts/funnel-steps.md +27 -8
- package/docs/ja/parts/heatmap.md +45 -0
- package/docs/ja/parts/kpi-card.md +2 -1
- package/docs/ja/parts/legend.md +36 -0
- package/docs/ja/parts/line-chart.md +48 -22
- package/docs/ja/parts/sample-data.md +61 -0
- package/docs/ja/parts/scatter-chart.md +69 -0
- package/docs/ja/parts/updated-at.md +27 -0
- package/docs/ja/parts/waterfall-chart.md +61 -0
- package/docs/ja/slide/slide-chart-full.md +48 -0
- package/docs/ja/slide/slide-chart-insight.md +52 -0
- package/docs/ja/slide/slide-columns.md +46 -0
- package/docs/ja/slide/slide-comparison.md +68 -0
- package/docs/ja/slide/slide-cover.md +39 -0
- package/docs/ja/slide/slide-exec-summary.md +47 -0
- package/docs/ja/slide/slide-heading.md +38 -0
- package/docs/ja/slide/slide-kpi-grid.md +55 -0
- package/docs/ja/slide/slide-kpi-hero.md +58 -0
- package/docs/ja/slide/slide-next-actions.md +49 -0
- package/docs/ja/slide/slide-scorecard-table.md +54 -0
- package/docs/ja/slide/slide-section.md +44 -0
- package/docs/ja/slide/slide-source-line.md +34 -0
- package/docs/ja/ui/empty.md +1 -1
- package/docs/ja/ui/error-state.md +4 -3
- package/docs/ja/ui/loading.md +1 -1
- package/docs/ja/ui/toggle-group.md +4 -3
- package/package.json +2 -2
- package/registry/ui/area-chart.tsx +45 -19
- package/registry/ui/bar-chart.tsx +45 -19
- package/registry/ui/bar-list.tsx +44 -17
- package/registry/ui/box-plot-chart.tsx +45 -19
- package/registry/ui/category-bar.tsx +44 -17
- package/registry/ui/chart-frame.tsx +44 -17
- package/registry/ui/delta.tsx +11 -8
- package/registry/ui/donut-chart.tsx +46 -19
- package/registry/ui/echart.tsx +171 -54
- package/registry/ui/heatmap.tsx +45 -19
- package/registry/ui/histogram-chart.tsx +45 -19
- package/registry/ui/kpi-card.tsx +9 -4
- package/registry/ui/legend.tsx +44 -17
- package/registry/ui/line-chart.tsx +45 -19
- package/registry/ui/page-shell.tsx +7 -7
- package/registry/ui/paired-chart.tsx +45 -19
- package/registry/ui/sample-data.tsx +1 -1
- package/registry/ui/scatter-chart.tsx +45 -19
- package/registry/ui/slider.tsx +1 -1
- package/registry/ui/status-badge.tsx +8 -8
- package/registry/ui/tooltip.tsx +6 -2
- package/registry/ui/tracker.tsx +9 -3
- package/registry/ui/trend-indicator.tsx +2 -2
- package/registry/ui/waterfall-chart.tsx +45 -19
- package/skills/vantage-design/SKILL.md +27 -25
- package/skills/vantage-design/references/extract.md +16 -14
- package/skills/vantage-design/references/format.md +6 -5
- package/skills/vantage-design/references/presets.md +60 -110
- package/skills/vantage-slide/SKILL.md +54 -36
- package/templates/AGENTS.md +2 -2
- package/templates/agents/doc.md +48 -0
- package/templates/agents/page.md +22 -1
- package/templates/agents/slide.md +70 -2
- package/theme.css +16 -15
- package/dist/chunk-HKSVYMYJ.js.map +0 -1
- package/dist/chunk-XPQSTZUU.js.map +0 -1
|
@@ -16,7 +16,7 @@ import { forwardRef, useMemo, type ForwardedRef, type ReactElement, type Ref } f
|
|
|
16
16
|
import { ChartFrame } from "@squadbase/vantage/components"
|
|
17
17
|
import { EChart } from "@squadbase/vantage/components"
|
|
18
18
|
import type { ChartComparison, ChartForecast, NumericKeys } from "@squadbase/vantage/components"
|
|
19
|
-
import { createContext, useCallback, useEffect, useRef, useState } from "react"
|
|
19
|
+
import { createContext, useCallback, useEffect, useRef, useState, type RefObject } from "react"
|
|
20
20
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
21
21
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter, type ValueFormat } from "@squadbase/vantage/components"
|
|
22
22
|
import type { ChartColor, ChartColors, ChartSeriesColor, ValueFormatter } from "@squadbase/vantage/components"
|
|
@@ -59,10 +59,23 @@ interface ChartTokens {
|
|
|
59
59
|
border: string | null
|
|
60
60
|
/** Tooltip surface. */
|
|
61
61
|
popover: string | null
|
|
62
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
63
|
+
colors?: Record<string, string>
|
|
64
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
65
|
+
surface?: string | null
|
|
62
66
|
/** Tooltip text. */
|
|
63
67
|
popoverForeground: string | null
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
71
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
72
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
73
|
+
const OPTION_COLOR_TOKENS = [
|
|
74
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
75
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
76
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
77
|
+
]
|
|
78
|
+
|
|
66
79
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
67
80
|
|
|
68
81
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -118,8 +131,10 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
118
131
|
const color = read(token)
|
|
119
132
|
if (color) series.push(color)
|
|
120
133
|
}
|
|
121
|
-
|
|
134
|
+
const tokens: ChartTokens = {
|
|
122
135
|
series,
|
|
136
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
137
|
+
surface: readChartSurface(target),
|
|
123
138
|
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
124
139
|
foreground: read("--foreground"),
|
|
125
140
|
mutedForeground: read("--muted-foreground"),
|
|
@@ -127,6 +142,8 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
127
142
|
popover: read("--popover"),
|
|
128
143
|
popoverForeground: read("--popover-foreground"),
|
|
129
144
|
}
|
|
145
|
+
tokenScopes.set(tokens, target)
|
|
146
|
+
return tokens
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -134,6 +151,10 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
134
151
|
if (a === b) return true
|
|
135
152
|
if (!a || !b) return false
|
|
136
153
|
return (
|
|
154
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
155
|
+
a.surface === b.surface &&
|
|
156
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
157
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
137
158
|
a.series.length === b.series.length &&
|
|
138
159
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
139
160
|
a.fontFamily === b.fontFamily &&
|
|
@@ -159,7 +180,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
159
180
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
160
181
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
161
182
|
*/
|
|
162
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
183
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
163
184
|
if (typeof document === "undefined") return () => {}
|
|
164
185
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
165
186
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -174,8 +195,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
174
195
|
|
|
175
196
|
const themeObserver = new MutationObserver(schedule)
|
|
176
197
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
199
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
200
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
201
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
202
|
+
}
|
|
179
203
|
|
|
180
204
|
const styleObserver = new MutationObserver(schedule)
|
|
181
205
|
if (document.head) {
|
|
@@ -263,22 +287,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
263
287
|
* components that build an ECharts option from token colours themselves (the
|
|
264
288
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
265
289
|
*/
|
|
266
|
-
function useChartTokens(): ChartTokens | null {
|
|
290
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
267
291
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
268
292
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
269
293
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
270
294
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
271
295
|
const tokensRef = useRef(tokens)
|
|
272
296
|
useEffect(() => {
|
|
297
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
273
298
|
const sync = () => {
|
|
274
|
-
const next = readChartTokens()
|
|
299
|
+
const next = readChartTokens(target)
|
|
275
300
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
276
301
|
tokensRef.current = next
|
|
277
302
|
setTokens(next)
|
|
278
303
|
}
|
|
279
304
|
sync()
|
|
280
|
-
return watchChartTokens(sync)
|
|
281
|
-
}, [])
|
|
305
|
+
return watchChartTokens(sync, target)
|
|
306
|
+
}, [scope])
|
|
282
307
|
return tokens
|
|
283
308
|
}
|
|
284
309
|
|
|
@@ -290,7 +315,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
290
315
|
/**
|
|
291
316
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
292
317
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
293
|
-
* variants `"positive-fill"`, …) are read
|
|
318
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
294
319
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
295
320
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
296
321
|
*/
|
|
@@ -301,12 +326,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
301
326
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
302
327
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
303
328
|
if (typeof document === "undefined") return ""
|
|
304
|
-
|
|
329
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
330
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
305
331
|
}
|
|
306
332
|
if (typeof document === "undefined") return color
|
|
307
333
|
let value = color.trim()
|
|
308
334
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
309
|
-
const styles = getComputedStyle(document.documentElement)
|
|
335
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
310
336
|
if (cssVar) {
|
|
311
337
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
312
338
|
} else if (value.includes("var(")) {
|
|
@@ -352,7 +378,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
352
378
|
if (color) return color
|
|
353
379
|
}
|
|
354
380
|
}
|
|
355
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
381
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
356
382
|
}
|
|
357
383
|
|
|
358
384
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -366,9 +392,10 @@ const WIDTH_BUCKET = 40
|
|
|
366
392
|
*/
|
|
367
393
|
function useChartSurface<E extends Element>(
|
|
368
394
|
forwarded: ForwardedRef<E>,
|
|
369
|
-
tokens
|
|
370
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
395
|
+
tokens?: ChartTokens | null,
|
|
396
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
371
397
|
const [node, setNode] = useState<E | null>(null)
|
|
398
|
+
const scopedTokens = useChartTokens(node)
|
|
372
399
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
373
400
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
374
401
|
const ref = useCallback(
|
|
@@ -382,7 +409,7 @@ function useChartSurface<E extends Element>(
|
|
|
382
409
|
useEffect(() => {
|
|
383
410
|
if (!node) return
|
|
384
411
|
setSurface(readChartSurface(node) ?? undefined)
|
|
385
|
-
}, [node, tokens])
|
|
412
|
+
}, [node, tokens, scopedTokens])
|
|
386
413
|
useEffect(() => {
|
|
387
414
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
388
415
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -391,7 +418,7 @@ function useChartSurface<E extends Element>(
|
|
|
391
418
|
observer.observe(node)
|
|
392
419
|
return () => observer.disconnect()
|
|
393
420
|
}, [node])
|
|
394
|
-
return { ref, surface, width }
|
|
421
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
395
422
|
}
|
|
396
423
|
|
|
397
424
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -3554,9 +3581,8 @@ function LineChartImpl<T extends Record<string, unknown>>(
|
|
|
3554
3581
|
}: LineChartProps<T>,
|
|
3555
3582
|
ref: ForwardedRef<HTMLDivElement>,
|
|
3556
3583
|
) {
|
|
3557
|
-
const tokens = useChartTokens()
|
|
3558
3584
|
// Highlight labels are backed by the surface the chart sits on (a card, the page).
|
|
3559
|
-
const { ref: frameRef, surface, width: plotWidth } = useChartSurface(ref
|
|
3585
|
+
const { ref: frameRef, surface, width: plotWidth, tokens } = useChartSurface(ref)
|
|
3560
3586
|
// Ticks and on-plot labels read short (¥1億); the tooltip reads the value as `format` says.
|
|
3561
3587
|
const tooltipFormatter = useMemo(() => resolveValueFormatter(valueFormatter, format), [valueFormatter, format])
|
|
3562
3588
|
const axisFormatter = useMemo(() => resolveAxisFormatter(valueFormatter, format), [valueFormatter, format])
|
|
@@ -72,12 +72,12 @@ export const PageShellHeader = forwardRef<HTMLDivElement, PageShellHeaderProps>(
|
|
|
72
72
|
{hasChips ? (
|
|
73
73
|
<div data-slot="page-shell-chips" className="order-1 flex basis-full flex-wrap gap-1.5">
|
|
74
74
|
{source ? (
|
|
75
|
-
<Badge variant="outline" className="h-auto max-w-full whitespace-normal">
|
|
75
|
+
<Badge variant="outline" className="h-auto max-w-full whitespace-normal text-muted-foreground">
|
|
76
76
|
出典:{source}
|
|
77
77
|
</Badge>
|
|
78
78
|
) : null}
|
|
79
79
|
{periodText && periodText !== EMPTY_VALUE ? (
|
|
80
|
-
<Badge variant="outline" className="tabular-nums">
|
|
80
|
+
<Badge variant="outline" className="tabular-nums text-muted-foreground">
|
|
81
81
|
{periodText}
|
|
82
82
|
</Badge>
|
|
83
83
|
) : null}
|
|
@@ -204,11 +204,11 @@ PageShellSummary.displayName = "PageShellSummary"
|
|
|
204
204
|
* emits classes it can see literally in the source.
|
|
205
205
|
*/
|
|
206
206
|
const summaryCardTones = {
|
|
207
|
-
positive: "border-positive/
|
|
208
|
-
negative: "border-negative/
|
|
209
|
-
warning: "border-warning/
|
|
210
|
-
info: "border-info/
|
|
211
|
-
neutral: "border-neutral/
|
|
207
|
+
positive: "border-positive/20 dark:border-positive/25 bg-positive-subtle [&>svg]:text-positive",
|
|
208
|
+
negative: "border-negative/20 dark:border-negative/25 bg-negative-subtle [&>svg]:text-negative",
|
|
209
|
+
warning: "border-warning/20 dark:border-warning/25 bg-warning-subtle [&>svg]:text-warning",
|
|
210
|
+
info: "border-info/20 dark:border-info/25 bg-info-subtle [&>svg]:text-info",
|
|
211
|
+
neutral: "border-neutral/20 dark:border-neutral/25 bg-neutral-subtle [&>svg]:text-neutral",
|
|
212
212
|
violet: "border-chart-4/30 bg-chart-4/5 [&>svg]:text-chart-4",
|
|
213
213
|
pink: "border-chart-5/30 bg-chart-5/5 [&>svg]:text-chart-5",
|
|
214
214
|
} as const
|
|
@@ -17,7 +17,7 @@ import { ChartFrame } from "@squadbase/vantage/components"
|
|
|
17
17
|
import { EChart } from "@squadbase/vantage/components"
|
|
18
18
|
import type { ChartCommonProps, ChartColors, NumericKeys, ValueFormatter } from "@squadbase/vantage/components"
|
|
19
19
|
import type { ValueFormat } from "@squadbase/vantage/components"
|
|
20
|
-
import { createContext, useCallback, useEffect, useRef, useState } from "react"
|
|
20
|
+
import { createContext, useCallback, useEffect, useRef, useState, type RefObject } from "react"
|
|
21
21
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
22
22
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter } from "@squadbase/vantage/components"
|
|
23
23
|
import type { ChartColor, ChartSeriesColor } from "@squadbase/vantage/components"
|
|
@@ -59,10 +59,23 @@ interface ChartTokens {
|
|
|
59
59
|
border: string | null
|
|
60
60
|
/** Tooltip surface. */
|
|
61
61
|
popover: string | null
|
|
62
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
63
|
+
colors?: Record<string, string>
|
|
64
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
65
|
+
surface?: string | null
|
|
62
66
|
/** Tooltip text. */
|
|
63
67
|
popoverForeground: string | null
|
|
64
68
|
}
|
|
65
69
|
|
|
70
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
71
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
72
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
73
|
+
const OPTION_COLOR_TOKENS = [
|
|
74
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
75
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
76
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
77
|
+
]
|
|
78
|
+
|
|
66
79
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
67
80
|
|
|
68
81
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -118,8 +131,10 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
118
131
|
const color = read(token)
|
|
119
132
|
if (color) series.push(color)
|
|
120
133
|
}
|
|
121
|
-
|
|
134
|
+
const tokens: ChartTokens = {
|
|
122
135
|
series,
|
|
136
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
137
|
+
surface: readChartSurface(target),
|
|
123
138
|
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
124
139
|
foreground: read("--foreground"),
|
|
125
140
|
mutedForeground: read("--muted-foreground"),
|
|
@@ -127,6 +142,8 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
127
142
|
popover: read("--popover"),
|
|
128
143
|
popoverForeground: read("--popover-foreground"),
|
|
129
144
|
}
|
|
145
|
+
tokenScopes.set(tokens, target)
|
|
146
|
+
return tokens
|
|
130
147
|
}
|
|
131
148
|
|
|
132
149
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -134,6 +151,10 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
134
151
|
if (a === b) return true
|
|
135
152
|
if (!a || !b) return false
|
|
136
153
|
return (
|
|
154
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
155
|
+
a.surface === b.surface &&
|
|
156
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
157
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
137
158
|
a.series.length === b.series.length &&
|
|
138
159
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
139
160
|
a.fontFamily === b.fontFamily &&
|
|
@@ -159,7 +180,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
159
180
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
160
181
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
161
182
|
*/
|
|
162
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
183
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
163
184
|
if (typeof document === "undefined") return () => {}
|
|
164
185
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
165
186
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -174,8 +195,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
174
195
|
|
|
175
196
|
const themeObserver = new MutationObserver(schedule)
|
|
176
197
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
177
|
-
|
|
178
|
-
|
|
198
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
199
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
200
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
201
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
202
|
+
}
|
|
179
203
|
|
|
180
204
|
const styleObserver = new MutationObserver(schedule)
|
|
181
205
|
if (document.head) {
|
|
@@ -263,22 +287,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
263
287
|
* components that build an ECharts option from token colours themselves (the
|
|
264
288
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
265
289
|
*/
|
|
266
|
-
function useChartTokens(): ChartTokens | null {
|
|
290
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
267
291
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
268
292
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
269
293
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
270
294
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
271
295
|
const tokensRef = useRef(tokens)
|
|
272
296
|
useEffect(() => {
|
|
297
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
273
298
|
const sync = () => {
|
|
274
|
-
const next = readChartTokens()
|
|
299
|
+
const next = readChartTokens(target)
|
|
275
300
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
276
301
|
tokensRef.current = next
|
|
277
302
|
setTokens(next)
|
|
278
303
|
}
|
|
279
304
|
sync()
|
|
280
|
-
return watchChartTokens(sync)
|
|
281
|
-
}, [])
|
|
305
|
+
return watchChartTokens(sync, target)
|
|
306
|
+
}, [scope])
|
|
282
307
|
return tokens
|
|
283
308
|
}
|
|
284
309
|
|
|
@@ -290,7 +315,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
290
315
|
/**
|
|
291
316
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
292
317
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
293
|
-
* variants `"positive-fill"`, …) are read
|
|
318
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
294
319
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
295
320
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
296
321
|
*/
|
|
@@ -301,12 +326,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
301
326
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
302
327
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
303
328
|
if (typeof document === "undefined") return ""
|
|
304
|
-
|
|
329
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
330
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
305
331
|
}
|
|
306
332
|
if (typeof document === "undefined") return color
|
|
307
333
|
let value = color.trim()
|
|
308
334
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
309
|
-
const styles = getComputedStyle(document.documentElement)
|
|
335
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
310
336
|
if (cssVar) {
|
|
311
337
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
312
338
|
} else if (value.includes("var(")) {
|
|
@@ -352,7 +378,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
352
378
|
if (color) return color
|
|
353
379
|
}
|
|
354
380
|
}
|
|
355
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
381
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
356
382
|
}
|
|
357
383
|
|
|
358
384
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -366,9 +392,10 @@ const WIDTH_BUCKET = 40
|
|
|
366
392
|
*/
|
|
367
393
|
function useChartSurface<E extends Element>(
|
|
368
394
|
forwarded: ForwardedRef<E>,
|
|
369
|
-
tokens
|
|
370
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
395
|
+
tokens?: ChartTokens | null,
|
|
396
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
371
397
|
const [node, setNode] = useState<E | null>(null)
|
|
398
|
+
const scopedTokens = useChartTokens(node)
|
|
372
399
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
373
400
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
374
401
|
const ref = useCallback(
|
|
@@ -382,7 +409,7 @@ function useChartSurface<E extends Element>(
|
|
|
382
409
|
useEffect(() => {
|
|
383
410
|
if (!node) return
|
|
384
411
|
setSurface(readChartSurface(node) ?? undefined)
|
|
385
|
-
}, [node, tokens])
|
|
412
|
+
}, [node, tokens, scopedTokens])
|
|
386
413
|
useEffect(() => {
|
|
387
414
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
388
415
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -391,7 +418,7 @@ function useChartSurface<E extends Element>(
|
|
|
391
418
|
observer.observe(node)
|
|
392
419
|
return () => observer.disconnect()
|
|
393
420
|
}, [node])
|
|
394
|
-
return { ref, surface, width }
|
|
421
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
395
422
|
}
|
|
396
423
|
|
|
397
424
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -3707,8 +3734,7 @@ function PairedChartImpl<T extends Record<string, unknown>>(
|
|
|
3707
3734
|
}: PairedChartProps<T>,
|
|
3708
3735
|
ref: ForwardedRef<HTMLDivElement>,
|
|
3709
3736
|
) {
|
|
3710
|
-
const tokens =
|
|
3711
|
-
const { ref: frameRef, width: plotWidth } = useChartSurface(ref, tokens)
|
|
3737
|
+
const { ref: frameRef, width: plotWidth, tokens } = useChartSurface(ref)
|
|
3712
3738
|
const barKeys = (bars?.categories ?? NO_KEYS) as string[]
|
|
3713
3739
|
const lineKeys = (line?.categories ?? NO_KEYS) as string[]
|
|
3714
3740
|
const barFormatter = useMemo(() => resolveValueFormatter(bars?.valueFormatter, bars?.format), [bars?.valueFormatter, bars?.format])
|
|
@@ -61,7 +61,7 @@ export function SampleBadge({ label = DEFAULT_SAMPLE_LABEL, className, ...props
|
|
|
61
61
|
<Badge
|
|
62
62
|
data-slot="sample-badge"
|
|
63
63
|
title={SAMPLE_TITLE}
|
|
64
|
-
className={cn("border-warning/
|
|
64
|
+
className={cn("border-warning/20 dark:border-warning/25 bg-warning-subtle text-warning align-middle", className)}
|
|
65
65
|
{...props}
|
|
66
66
|
>
|
|
67
67
|
{label}
|
|
@@ -29,7 +29,7 @@ import { ChartFrame } from "@squadbase/vantage/components"
|
|
|
29
29
|
import { EChart } from "@squadbase/vantage/components"
|
|
30
30
|
import { EMPTY_VALUE, type ValueFormat } from "@squadbase/vantage/components"
|
|
31
31
|
import type { ChartCommonProps, ChartEmptyState, ChartSeriesColor, NumericKeys, ValueFormatter } from "@squadbase/vantage/components"
|
|
32
|
-
import { createContext, useCallback, useEffect, useRef, useState } from "react"
|
|
32
|
+
import { createContext, useCallback, useEffect, useRef, useState, type RefObject } from "react"
|
|
33
33
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
34
34
|
import { type ReactNode } from "react"
|
|
35
35
|
import { computeComparison, defaultChangeDisplay, formatChange, toneForValue, type ComparisonKind, type PeriodGrain } from "@squadbase/vantage/components"
|
|
@@ -72,10 +72,23 @@ interface ChartTokens {
|
|
|
72
72
|
border: string | null
|
|
73
73
|
/** Tooltip surface. */
|
|
74
74
|
popover: string | null
|
|
75
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
76
|
+
colors?: Record<string, string>
|
|
77
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
78
|
+
surface?: string | null
|
|
75
79
|
/** Tooltip text. */
|
|
76
80
|
popoverForeground: string | null
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
84
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
85
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
86
|
+
const OPTION_COLOR_TOKENS = [
|
|
87
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
88
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
89
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
90
|
+
]
|
|
91
|
+
|
|
79
92
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
80
93
|
|
|
81
94
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -131,8 +144,10 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
131
144
|
const color = read(token)
|
|
132
145
|
if (color) series.push(color)
|
|
133
146
|
}
|
|
134
|
-
|
|
147
|
+
const tokens: ChartTokens = {
|
|
135
148
|
series,
|
|
149
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
150
|
+
surface: readChartSurface(target),
|
|
136
151
|
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
137
152
|
foreground: read("--foreground"),
|
|
138
153
|
mutedForeground: read("--muted-foreground"),
|
|
@@ -140,6 +155,8 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
140
155
|
popover: read("--popover"),
|
|
141
156
|
popoverForeground: read("--popover-foreground"),
|
|
142
157
|
}
|
|
158
|
+
tokenScopes.set(tokens, target)
|
|
159
|
+
return tokens
|
|
143
160
|
}
|
|
144
161
|
|
|
145
162
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -147,6 +164,10 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
147
164
|
if (a === b) return true
|
|
148
165
|
if (!a || !b) return false
|
|
149
166
|
return (
|
|
167
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
168
|
+
a.surface === b.surface &&
|
|
169
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
170
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
150
171
|
a.series.length === b.series.length &&
|
|
151
172
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
152
173
|
a.fontFamily === b.fontFamily &&
|
|
@@ -172,7 +193,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
172
193
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
173
194
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
174
195
|
*/
|
|
175
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
196
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
176
197
|
if (typeof document === "undefined") return () => {}
|
|
177
198
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
178
199
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -187,8 +208,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
187
208
|
|
|
188
209
|
const themeObserver = new MutationObserver(schedule)
|
|
189
210
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
190
|
-
|
|
191
|
-
|
|
211
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
212
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
213
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
214
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
215
|
+
}
|
|
192
216
|
|
|
193
217
|
const styleObserver = new MutationObserver(schedule)
|
|
194
218
|
if (document.head) {
|
|
@@ -276,22 +300,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
276
300
|
* components that build an ECharts option from token colours themselves (the
|
|
277
301
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
278
302
|
*/
|
|
279
|
-
function useChartTokens(): ChartTokens | null {
|
|
303
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
280
304
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
281
305
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
282
306
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
283
307
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
284
308
|
const tokensRef = useRef(tokens)
|
|
285
309
|
useEffect(() => {
|
|
310
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
286
311
|
const sync = () => {
|
|
287
|
-
const next = readChartTokens()
|
|
312
|
+
const next = readChartTokens(target)
|
|
288
313
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
289
314
|
tokensRef.current = next
|
|
290
315
|
setTokens(next)
|
|
291
316
|
}
|
|
292
317
|
sync()
|
|
293
|
-
return watchChartTokens(sync)
|
|
294
|
-
}, [])
|
|
318
|
+
return watchChartTokens(sync, target)
|
|
319
|
+
}, [scope])
|
|
295
320
|
return tokens
|
|
296
321
|
}
|
|
297
322
|
|
|
@@ -303,7 +328,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
303
328
|
/**
|
|
304
329
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
305
330
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
306
|
-
* variants `"positive-fill"`, …) are read
|
|
331
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
307
332
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
308
333
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
309
334
|
*/
|
|
@@ -314,12 +339,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
314
339
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
315
340
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
316
341
|
if (typeof document === "undefined") return ""
|
|
317
|
-
|
|
342
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
343
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
318
344
|
}
|
|
319
345
|
if (typeof document === "undefined") return color
|
|
320
346
|
let value = color.trim()
|
|
321
347
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
322
|
-
const styles = getComputedStyle(document.documentElement)
|
|
348
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
323
349
|
if (cssVar) {
|
|
324
350
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
325
351
|
} else if (value.includes("var(")) {
|
|
@@ -365,7 +391,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
365
391
|
if (color) return color
|
|
366
392
|
}
|
|
367
393
|
}
|
|
368
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
394
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
369
395
|
}
|
|
370
396
|
|
|
371
397
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -379,9 +405,10 @@ const WIDTH_BUCKET = 40
|
|
|
379
405
|
*/
|
|
380
406
|
function useChartSurface<E extends Element>(
|
|
381
407
|
forwarded: ForwardedRef<E>,
|
|
382
|
-
tokens
|
|
383
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
408
|
+
tokens?: ChartTokens | null,
|
|
409
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
384
410
|
const [node, setNode] = useState<E | null>(null)
|
|
411
|
+
const scopedTokens = useChartTokens(node)
|
|
385
412
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
386
413
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
387
414
|
const ref = useCallback(
|
|
@@ -395,7 +422,7 @@ function useChartSurface<E extends Element>(
|
|
|
395
422
|
useEffect(() => {
|
|
396
423
|
if (!node) return
|
|
397
424
|
setSurface(readChartSurface(node) ?? undefined)
|
|
398
|
-
}, [node, tokens])
|
|
425
|
+
}, [node, tokens, scopedTokens])
|
|
399
426
|
useEffect(() => {
|
|
400
427
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
401
428
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -404,7 +431,7 @@ function useChartSurface<E extends Element>(
|
|
|
404
431
|
observer.observe(node)
|
|
405
432
|
return () => observer.disconnect()
|
|
406
433
|
}, [node])
|
|
407
|
-
return { ref, surface, width }
|
|
434
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
408
435
|
}
|
|
409
436
|
|
|
410
437
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-options.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -4090,8 +4117,7 @@ function ScatterChartImpl<T extends Record<string, unknown>>(
|
|
|
4090
4117
|
}: ScatterChartProps<T>,
|
|
4091
4118
|
ref: ForwardedRef<HTMLDivElement>,
|
|
4092
4119
|
) {
|
|
4093
|
-
const tokens =
|
|
4094
|
-
const { ref: frameRef, width: plotWidth } = useChartSurface(ref, tokens)
|
|
4120
|
+
const { ref: frameRef, width: plotWidth, tokens } = useChartSurface(ref)
|
|
4095
4121
|
// Tooltips and the table read in full; axis ticks read short.
|
|
4096
4122
|
const formatters = useMemo(
|
|
4097
4123
|
() => ({
|
package/registry/ui/slider.tsx
CHANGED
|
@@ -45,7 +45,7 @@ function Slider({
|
|
|
45
45
|
<SliderPrimitive.Thumb
|
|
46
46
|
data-slot="slider-thumb"
|
|
47
47
|
key={index}
|
|
48
|
-
className="relative block size-3 shrink-0 rounded-full border border-ring bg-
|
|
48
|
+
className="relative block size-3 shrink-0 rounded-full border border-ring bg-background ring-ring/50 transition-[color,box-shadow] select-none after:absolute after:-inset-2 hover:ring-3 focus-visible:ring-3 focus-visible:outline-hidden active:ring-3 disabled:pointer-events-none disabled:opacity-50"
|
|
49
49
|
/>
|
|
50
50
|
))}
|
|
51
51
|
</SliderPrimitive.Control>
|
|
@@ -75,12 +75,12 @@ const legacyColorTones: Record<string, StatusTone> = {
|
|
|
75
75
|
* One literal class set per tone — Tailwind only emits classes it can see
|
|
76
76
|
* literally in the source.
|
|
77
77
|
*/
|
|
78
|
-
const toneStyles: Record<StatusTone, { dot: string; bg: string; text: string }> = {
|
|
79
|
-
positive: { dot: "bg-positive", bg: "bg-positive-subtle", text: "text-positive" },
|
|
80
|
-
negative: { dot: "bg-negative", bg: "bg-negative-subtle", text: "text-negative" },
|
|
81
|
-
warning: { dot: "bg-warning", bg: "bg-warning-subtle", text: "text-warning" },
|
|
82
|
-
info: { dot: "bg-info", bg: "bg-info-subtle", text: "text-info" },
|
|
83
|
-
neutral: { dot: "bg-neutral", bg: "bg-neutral-subtle", text: "text-neutral" },
|
|
78
|
+
const toneStyles: Record<StatusTone, { dot: string; bg: string; text: string; border: string }> = {
|
|
79
|
+
positive: { dot: "bg-positive", bg: "bg-positive-subtle", text: "text-positive", border: "border-positive/20 dark:border-positive/25" },
|
|
80
|
+
negative: { dot: "bg-negative", bg: "bg-negative-subtle", text: "text-negative", border: "border-negative/20 dark:border-negative/25" },
|
|
81
|
+
warning: { dot: "bg-warning", bg: "bg-warning-subtle", text: "text-warning", border: "border-warning/20 dark:border-warning/25" },
|
|
82
|
+
info: { dot: "bg-info", bg: "bg-info-subtle", text: "text-info", border: "border-info/20 dark:border-info/25" },
|
|
83
|
+
neutral: { dot: "bg-neutral", bg: "bg-neutral-subtle", text: "text-neutral", border: "border-neutral/20 dark:border-neutral/25" },
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
const resolveTone = (color: string | undefined): StatusTone => {
|
|
@@ -110,10 +110,10 @@ export const StatusBadge = forwardRef<HTMLSpanElement, StatusBadgeProps>(functio
|
|
|
110
110
|
ref={ref}
|
|
111
111
|
data-slot="status-badge"
|
|
112
112
|
variant="outline"
|
|
113
|
-
className={cn("gap-1.5
|
|
113
|
+
className={cn("gap-1.5 rounded-md", style.border, style.bg, style.text, className)}
|
|
114
114
|
aria-label={displayLabel}
|
|
115
115
|
>
|
|
116
|
-
<span className={cn("inline-block size-
|
|
116
|
+
<span className={cn("inline-block size-1.5 rounded-full", style.dot)} aria-hidden="true" />
|
|
117
117
|
{displayLabel}
|
|
118
118
|
</Badge>
|
|
119
119
|
)
|