@squadbase/vantage 0.6.2-dev1.5aae04e → 0.6.2-dev1.81c1bbe
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-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-RU6WKEOG.js} +56 -927
- package/dist/chunk-RU6WKEOG.js.map +1 -0
- package/dist/components/index.d.ts +3 -243
- package/dist/components/index.js +5 -2
- package/dist/doc/index.d.ts +194 -2
- package/dist/doc/index.js +334 -3
- package/dist/doc/index.js.map +1 -1
- package/dist/format-U0cQl069.d.ts +243 -0
- package/dist/slide/index.d.ts +177 -1
- package/dist/slide/index.js +864 -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 +11 -2
- 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-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/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/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-insight.md +52 -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-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/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 +697 -77
- package/docs/ja/components.md +10 -1
- 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-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/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/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-insight.md +52 -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-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/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 +4 -4
- 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/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 +45 -0
- package/templates/agents/page.md +22 -1
- package/templates/agents/slide.md +55 -2
- package/theme.css +16 -15
- package/dist/chunk-HKSVYMYJ.js.map +0 -1
- package/dist/chunk-XPQSTZUU.js.map +0 -1
|
@@ -20,7 +20,7 @@ import { ChartFrame } from "@squadbase/vantage/components"
|
|
|
20
20
|
import { EChart } from "@squadbase/vantage/components"
|
|
21
21
|
import { EMPTY_VALUE } from "@squadbase/vantage/components"
|
|
22
22
|
import type { ChartCommonProps, ValueFormatter } from "@squadbase/vantage/components"
|
|
23
|
-
import { createContext, useCallback, useEffect, useRef, useState } from "react"
|
|
23
|
+
import { createContext, useCallback, useEffect, useRef, useState, type RefObject } from "react"
|
|
24
24
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
25
25
|
import { type ReactNode } from "react"
|
|
26
26
|
import { computeComparison, defaultChangeDisplay, formatChange, toneForValue, type ComparisonKind, type PeriodGrain } from "@squadbase/vantage/components"
|
|
@@ -63,10 +63,23 @@ interface ChartTokens {
|
|
|
63
63
|
border: string | null
|
|
64
64
|
/** Tooltip surface. */
|
|
65
65
|
popover: string | null
|
|
66
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
67
|
+
colors?: Record<string, string>
|
|
68
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
69
|
+
surface?: string | null
|
|
66
70
|
/** Tooltip text. */
|
|
67
71
|
popoverForeground: string | null
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
75
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
76
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
77
|
+
const OPTION_COLOR_TOKENS = [
|
|
78
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
79
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
80
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
81
|
+
]
|
|
82
|
+
|
|
70
83
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
71
84
|
|
|
72
85
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -122,8 +135,10 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
122
135
|
const color = read(token)
|
|
123
136
|
if (color) series.push(color)
|
|
124
137
|
}
|
|
125
|
-
|
|
138
|
+
const tokens: ChartTokens = {
|
|
126
139
|
series,
|
|
140
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
141
|
+
surface: readChartSurface(target),
|
|
127
142
|
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
128
143
|
foreground: read("--foreground"),
|
|
129
144
|
mutedForeground: read("--muted-foreground"),
|
|
@@ -131,6 +146,8 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
131
146
|
popover: read("--popover"),
|
|
132
147
|
popoverForeground: read("--popover-foreground"),
|
|
133
148
|
}
|
|
149
|
+
tokenScopes.set(tokens, target)
|
|
150
|
+
return tokens
|
|
134
151
|
}
|
|
135
152
|
|
|
136
153
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -138,6 +155,10 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
138
155
|
if (a === b) return true
|
|
139
156
|
if (!a || !b) return false
|
|
140
157
|
return (
|
|
158
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
159
|
+
a.surface === b.surface &&
|
|
160
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
161
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
141
162
|
a.series.length === b.series.length &&
|
|
142
163
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
143
164
|
a.fontFamily === b.fontFamily &&
|
|
@@ -163,7 +184,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
163
184
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
164
185
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
165
186
|
*/
|
|
166
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
187
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
167
188
|
if (typeof document === "undefined") return () => {}
|
|
168
189
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
169
190
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -178,8 +199,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
178
199
|
|
|
179
200
|
const themeObserver = new MutationObserver(schedule)
|
|
180
201
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
181
|
-
|
|
182
|
-
|
|
202
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
203
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
204
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
205
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
206
|
+
}
|
|
183
207
|
|
|
184
208
|
const styleObserver = new MutationObserver(schedule)
|
|
185
209
|
if (document.head) {
|
|
@@ -267,22 +291,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
267
291
|
* components that build an ECharts option from token colours themselves (the
|
|
268
292
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
269
293
|
*/
|
|
270
|
-
function useChartTokens(): ChartTokens | null {
|
|
294
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
271
295
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
272
296
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
273
297
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
274
298
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
275
299
|
const tokensRef = useRef(tokens)
|
|
276
300
|
useEffect(() => {
|
|
301
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
277
302
|
const sync = () => {
|
|
278
|
-
const next = readChartTokens()
|
|
303
|
+
const next = readChartTokens(target)
|
|
279
304
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
280
305
|
tokensRef.current = next
|
|
281
306
|
setTokens(next)
|
|
282
307
|
}
|
|
283
308
|
sync()
|
|
284
|
-
return watchChartTokens(sync)
|
|
285
|
-
}, [])
|
|
309
|
+
return watchChartTokens(sync, target)
|
|
310
|
+
}, [scope])
|
|
286
311
|
return tokens
|
|
287
312
|
}
|
|
288
313
|
|
|
@@ -294,7 +319,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
294
319
|
/**
|
|
295
320
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
296
321
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
297
|
-
* variants `"positive-fill"`, …) are read
|
|
322
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
298
323
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
299
324
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
300
325
|
*/
|
|
@@ -305,12 +330,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
305
330
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
306
331
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
307
332
|
if (typeof document === "undefined") return ""
|
|
308
|
-
|
|
333
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
334
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
309
335
|
}
|
|
310
336
|
if (typeof document === "undefined") return color
|
|
311
337
|
let value = color.trim()
|
|
312
338
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
313
|
-
const styles = getComputedStyle(document.documentElement)
|
|
339
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
314
340
|
if (cssVar) {
|
|
315
341
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
316
342
|
} else if (value.includes("var(")) {
|
|
@@ -356,7 +382,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
356
382
|
if (color) return color
|
|
357
383
|
}
|
|
358
384
|
}
|
|
359
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
385
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
360
386
|
}
|
|
361
387
|
|
|
362
388
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -370,9 +396,10 @@ const WIDTH_BUCKET = 40
|
|
|
370
396
|
*/
|
|
371
397
|
function useChartSurface<E extends Element>(
|
|
372
398
|
forwarded: ForwardedRef<E>,
|
|
373
|
-
tokens
|
|
374
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
399
|
+
tokens?: ChartTokens | null,
|
|
400
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
375
401
|
const [node, setNode] = useState<E | null>(null)
|
|
402
|
+
const scopedTokens = useChartTokens(node)
|
|
376
403
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
377
404
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
378
405
|
const ref = useCallback(
|
|
@@ -386,7 +413,7 @@ function useChartSurface<E extends Element>(
|
|
|
386
413
|
useEffect(() => {
|
|
387
414
|
if (!node) return
|
|
388
415
|
setSurface(readChartSurface(node) ?? undefined)
|
|
389
|
-
}, [node, tokens])
|
|
416
|
+
}, [node, tokens, scopedTokens])
|
|
390
417
|
useEffect(() => {
|
|
391
418
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
392
419
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -395,7 +422,7 @@ function useChartSurface<E extends Element>(
|
|
|
395
422
|
observer.observe(node)
|
|
396
423
|
return () => observer.disconnect()
|
|
397
424
|
}, [node])
|
|
398
|
-
return { ref, surface, width }
|
|
425
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
399
426
|
}
|
|
400
427
|
|
|
401
428
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-options.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -3843,8 +3870,7 @@ export const BoxPlotChart = forwardRef(function BoxPlotChart(
|
|
|
3843
3870
|
}: BoxPlotChartProps,
|
|
3844
3871
|
ref: ForwardedRef<HTMLDivElement>,
|
|
3845
3872
|
) {
|
|
3846
|
-
const tokens =
|
|
3847
|
-
const { ref: frameRef } = useChartSurface(ref, tokens)
|
|
3873
|
+
const { ref: frameRef, tokens } = useChartSurface(ref)
|
|
3848
3874
|
const valueFormatter = useMemo(() => resolveValueFormatter(valueFormatterProp, format), [valueFormatterProp, format])
|
|
3849
3875
|
const entries = useMemo(() => boxPlotEntries(groups, whisker), [groups, whisker])
|
|
3850
3876
|
const [color = ""] = useMemo(() => resolveChartColors(["chart-1"], tokens), [tokens])
|
|
@@ -13,7 +13,7 @@ import { cn } from "@squadbase/vantage/ui"
|
|
|
13
13
|
import { Tooltip, TooltipContent, TooltipTrigger } from "@squadbase/vantage/ui"
|
|
14
14
|
import type { ValueFormat } from "@squadbase/vantage/components"
|
|
15
15
|
import type { ChartColor, ValueFormatter } from "@squadbase/vantage/components"
|
|
16
|
-
import { createContext, useCallback, useEffect, useRef, useState, type ForwardedRef } from "react"
|
|
16
|
+
import { createContext, useCallback, useEffect, useRef, useState, type ForwardedRef, type RefObject } from "react"
|
|
17
17
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
18
18
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter } from "@squadbase/vantage/components"
|
|
19
19
|
import type { ChartColors, ChartSeriesColor } from "@squadbase/vantage/components"
|
|
@@ -52,10 +52,23 @@ interface ChartTokens {
|
|
|
52
52
|
border: string | null
|
|
53
53
|
/** Tooltip surface. */
|
|
54
54
|
popover: string | null
|
|
55
|
+
/** Semantic and surface dependencies used by typed chart options. Optional for hand-built tokens. */
|
|
56
|
+
colors?: Record<string, string>
|
|
57
|
+
/** Actual inherited painting surface (may change without a token changing). */
|
|
58
|
+
surface?: string | null
|
|
55
59
|
/** Tooltip text. */
|
|
56
60
|
popoverForeground: string | null
|
|
57
61
|
}
|
|
58
62
|
|
|
63
|
+
// Keep scope out of the public token shape. A resolved snapshot remembers where arbitrary
|
|
64
|
+
// var(--token) expressions must be read, without retaining unmounted charts globally.
|
|
65
|
+
const tokenScopes = new WeakMap<ChartTokens, Element>()
|
|
66
|
+
const OPTION_COLOR_TOKENS = [
|
|
67
|
+
"--background", "--card", "--muted", "--primary", "--destructive",
|
|
68
|
+
...["positive", "negative", "warning", "neutral", "info"].flatMap((tone) =>
|
|
69
|
+
["", "-fill", "-subtle"].map((suffix) => `--${tone}${suffix}`)),
|
|
70
|
+
]
|
|
71
|
+
|
|
59
72
|
let normalizeContext: CanvasRenderingContext2D | null | undefined
|
|
60
73
|
|
|
61
74
|
/** The forms `fillStyle` serializes legacy colours to, and zrender can parse. */
|
|
@@ -111,8 +124,10 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
111
124
|
const color = read(token)
|
|
112
125
|
if (color) series.push(color)
|
|
113
126
|
}
|
|
114
|
-
|
|
127
|
+
const tokens: ChartTokens = {
|
|
115
128
|
series,
|
|
129
|
+
colors: Object.fromEntries(OPTION_COLOR_TOKENS.map((name) => [name, styles.getPropertyValue(name).trim()])),
|
|
130
|
+
surface: readChartSurface(target),
|
|
116
131
|
...(styles.fontFamily ? { fontFamily: styles.fontFamily } : {}),
|
|
117
132
|
foreground: read("--foreground"),
|
|
118
133
|
mutedForeground: read("--muted-foreground"),
|
|
@@ -120,6 +135,8 @@ function readChartTokens(el?: Element | null): ChartTokens | null {
|
|
|
120
135
|
popover: read("--popover"),
|
|
121
136
|
popoverForeground: read("--popover-foreground"),
|
|
122
137
|
}
|
|
138
|
+
tokenScopes.set(tokens, target)
|
|
139
|
+
return tokens
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
/** Structural equality, so a theme toggle that changes nothing doesn't re-init. */
|
|
@@ -127,6 +144,10 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
127
144
|
if (a === b) return true
|
|
128
145
|
if (!a || !b) return false
|
|
129
146
|
return (
|
|
147
|
+
tokenScopes.get(a) === tokenScopes.get(b) &&
|
|
148
|
+
a.surface === b.surface &&
|
|
149
|
+
Object.keys(a.colors ?? {}).length === Object.keys(b.colors ?? {}).length &&
|
|
150
|
+
Object.entries(a.colors ?? {}).every(([name, value]) => b.colors?.[name] === value) &&
|
|
130
151
|
a.series.length === b.series.length &&
|
|
131
152
|
a.series.every((color, i) => color === b.series[i]) &&
|
|
132
153
|
a.fontFamily === b.fontFamily &&
|
|
@@ -152,7 +173,7 @@ function sameChartTokens(a: ChartTokens | null, b: ChartTokens | null): boolean
|
|
|
152
173
|
* attributes alone leaves the chart painted in the previous palette until a
|
|
153
174
|
* reload — the tokens in the DOM are new, the canvas is not.
|
|
154
175
|
*/
|
|
155
|
-
function watchChartTokens(onChange: () => void): () => void {
|
|
176
|
+
function watchChartTokens(onChange: () => void, scope?: Element | null): () => void {
|
|
156
177
|
if (typeof document === "undefined") return () => {}
|
|
157
178
|
// Style edits arrive as bursts of mutations; coalesce them into one read, and
|
|
158
179
|
// take it after the next frame so a swapped stylesheet has been applied.
|
|
@@ -167,8 +188,11 @@ function watchChartTokens(onChange: () => void): () => void {
|
|
|
167
188
|
|
|
168
189
|
const themeObserver = new MutationObserver(schedule)
|
|
169
190
|
const attributeFilter = ["class", "style", "data-theme"]
|
|
170
|
-
|
|
171
|
-
|
|
191
|
+
// Only the chart and its ancestors can change its inherited theme. Never observe
|
|
192
|
+
// the document subtree: canvas/tooltip/rendering mutations would trigger every chart.
|
|
193
|
+
for (let node: Element | null = scope ?? document.body ?? document.documentElement; node; node = node.parentElement) {
|
|
194
|
+
themeObserver.observe(node, { attributes: true, attributeFilter })
|
|
195
|
+
}
|
|
172
196
|
|
|
173
197
|
const styleObserver = new MutationObserver(schedule)
|
|
174
198
|
if (document.head) {
|
|
@@ -256,22 +280,23 @@ function buildChartTheme(tokens: ChartTokens): Record<string, unknown> {
|
|
|
256
280
|
* components that build an ECharts option from token colours themselves (the
|
|
257
281
|
* typed charts), so their palette follows light/dark like `EChart`'s theme.
|
|
258
282
|
*/
|
|
259
|
-
function useChartTokens(): ChartTokens | null {
|
|
283
|
+
function useChartTokens(scope?: Element | null | RefObject<Element | null>): ChartTokens | null {
|
|
260
284
|
const [tokens, setTokens] = useState<ChartTokens | null>(() => readChartTokens())
|
|
261
285
|
// Compared here, not inside a `setTokens` updater: an updater that reads the DOM is
|
|
262
286
|
// replayed by React on every rebase and returns a fresh object each time, so the state
|
|
263
287
|
// never settles ("Maximum update depth exceeded" once the tokens change after mount).
|
|
264
288
|
const tokensRef = useRef(tokens)
|
|
265
289
|
useEffect(() => {
|
|
290
|
+
const target = scope && "current" in scope ? scope.current : scope
|
|
266
291
|
const sync = () => {
|
|
267
|
-
const next = readChartTokens()
|
|
292
|
+
const next = readChartTokens(target)
|
|
268
293
|
if (sameChartTokens(tokensRef.current, next)) return
|
|
269
294
|
tokensRef.current = next
|
|
270
295
|
setTokens(next)
|
|
271
296
|
}
|
|
272
297
|
sync()
|
|
273
|
-
return watchChartTokens(sync)
|
|
274
|
-
}, [])
|
|
298
|
+
return watchChartTokens(sync, target)
|
|
299
|
+
}, [scope])
|
|
275
300
|
return tokens
|
|
276
301
|
}
|
|
277
302
|
|
|
@@ -283,7 +308,7 @@ const EMBEDDED_VAR_RE = /var\(\s*(--[\w-]+)\s*\)/g
|
|
|
283
308
|
/**
|
|
284
309
|
* Turn chart colours into canvas-safe strings. `"chart-1"`〜`"chart-5"` come from
|
|
285
310
|
* the resolved series tokens; `"positive"` / `"warning"` / `"negative"` / `"neutral"` (and the area
|
|
286
|
-
* variants `"positive-fill"`, …) are read
|
|
311
|
+
* variants `"positive-fill"`, …) are read in the snapshot's inherited scope (`--positive`, …); `var(--token)` uses the same scope; any
|
|
287
312
|
* other CSS colour is normalized like the tokens are. A colour that can't be
|
|
288
313
|
* resolved comes back as `""`, which ECharts treats as "use the palette".
|
|
289
314
|
*/
|
|
@@ -294,12 +319,13 @@ function resolveChartColors(colors: readonly string[], tokens: ChartTokens | nul
|
|
|
294
319
|
// Meaning colours are the theme tokens of the same name: same path as `var(--positive)`.
|
|
295
320
|
if (TONE_NAME_RE.test(color.trim())) {
|
|
296
321
|
if (typeof document === "undefined") return ""
|
|
297
|
-
|
|
322
|
+
const target = (tokens && tokenScopes.get(tokens)) ?? document.documentElement
|
|
323
|
+
return toCanvasColor(getComputedStyle(target).getPropertyValue(`--${color.trim()}`)) ?? ""
|
|
298
324
|
}
|
|
299
325
|
if (typeof document === "undefined") return color
|
|
300
326
|
let value = color.trim()
|
|
301
327
|
const cssVar = CSS_VAR_RE.exec(value)
|
|
302
|
-
const styles = getComputedStyle(document.documentElement)
|
|
328
|
+
const styles = getComputedStyle((tokens && tokenScopes.get(tokens)) ?? document.documentElement)
|
|
303
329
|
if (cssVar) {
|
|
304
330
|
value = styles.getPropertyValue(cssVar[1]!).trim() || (cssVar[2] ?? "").trim()
|
|
305
331
|
} else if (value.includes("var(")) {
|
|
@@ -345,7 +371,7 @@ function readChartSurface(el: Element | null): string | null {
|
|
|
345
371
|
if (color) return color
|
|
346
372
|
}
|
|
347
373
|
}
|
|
348
|
-
return toCanvasColor(getComputedStyle(document.documentElement).getPropertyValue("--background"))
|
|
374
|
+
return toCanvasColor(getComputedStyle(el ?? document.documentElement).getPropertyValue("--background"))
|
|
349
375
|
}
|
|
350
376
|
|
|
351
377
|
/** Widths are bucketed so a resize only rebuilds the option when the label budget can change. */
|
|
@@ -359,9 +385,10 @@ const WIDTH_BUCKET = 40
|
|
|
359
385
|
*/
|
|
360
386
|
function useChartSurface<E extends Element>(
|
|
361
387
|
forwarded: ForwardedRef<E>,
|
|
362
|
-
tokens
|
|
363
|
-
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined } {
|
|
388
|
+
tokens?: ChartTokens | null,
|
|
389
|
+
): { ref: (el: E | null) => void; surface: string | undefined; width: number | undefined; tokens: ChartTokens | null } {
|
|
364
390
|
const [node, setNode] = useState<E | null>(null)
|
|
391
|
+
const scopedTokens = useChartTokens(node)
|
|
365
392
|
const [surface, setSurface] = useState<string | undefined>(undefined)
|
|
366
393
|
const [width, setWidth] = useState<number | undefined>(undefined)
|
|
367
394
|
const ref = useCallback(
|
|
@@ -375,7 +402,7 @@ function useChartSurface<E extends Element>(
|
|
|
375
402
|
useEffect(() => {
|
|
376
403
|
if (!node) return
|
|
377
404
|
setSurface(readChartSurface(node) ?? undefined)
|
|
378
|
-
}, [node, tokens])
|
|
405
|
+
}, [node, tokens, scopedTokens])
|
|
379
406
|
useEffect(() => {
|
|
380
407
|
if (!node || typeof ResizeObserver === "undefined") return
|
|
381
408
|
const update = (w: number) => setWidth(Math.floor(w / WIDTH_BUCKET) * WIDTH_BUCKET)
|
|
@@ -384,7 +411,7 @@ function useChartSurface<E extends Element>(
|
|
|
384
411
|
observer.observe(node)
|
|
385
412
|
return () => observer.disconnect()
|
|
386
413
|
}, [node])
|
|
387
|
-
return { ref, surface, width }
|
|
414
|
+
return { ref, surface, width, tokens: scopedTokens }
|
|
388
415
|
}
|
|
389
416
|
|
|
390
417
|
/* ---------- inlined from @squadbase/squadkit registry/lib/chart-utils.ts (internal to @squadbase/vantage) ---------- */
|
|
@@ -20,7 +20,7 @@ import { forwardRef, useId, type HTMLAttributes, type ReactNode } from "react"
|
|
|
20
20
|
|
|
21
21
|
import { cn } from "@squadbase/vantage/ui"
|
|
22
22
|
import type { ChartEmptyState } from "@squadbase/vantage/components"
|
|
23
|
-
import { createContext, useCallback, useEffect, useRef, useState, type ForwardedRef } from "react"
|
|
23
|
+
import { createContext, useCallback, useEffect, useRef, useState, type ForwardedRef, type RefObject } from "react"
|
|
24
24
|
import type { ChartTone } from "@squadbase/vantage/components"
|
|
25
25
|
import { DEFAULT_LOCALE, EMPTY_VALUE, formatValue, toValueFormatter, type ValueFormat } from "@squadbase/vantage/components"
|
|
26
26
|
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) ---------- */
|
package/registry/ui/delta.tsx
CHANGED
|
@@ -56,7 +56,7 @@ const deltaVariants = cva(
|
|
|
56
56
|
{
|
|
57
57
|
variants: {
|
|
58
58
|
variant: {
|
|
59
|
-
badge: "h-5 rounded-
|
|
59
|
+
badge: "h-5 rounded-md border px-2 py-0.5 text-xs font-medium [&>svg]:size-3",
|
|
60
60
|
inline: "text-sm [&>svg]:size-3.5",
|
|
61
61
|
},
|
|
62
62
|
sentiment: {
|
|
@@ -66,9 +66,9 @@ const deltaVariants = cva(
|
|
|
66
66
|
},
|
|
67
67
|
},
|
|
68
68
|
compoundVariants: [
|
|
69
|
-
{ variant: "badge", sentiment: "positive", className: "bg-positive-subtle" },
|
|
70
|
-
{ variant: "badge", sentiment: "negative", className: "bg-negative-subtle" },
|
|
71
|
-
{ variant: "badge", sentiment: "neutral", className: "bg-neutral-subtle" },
|
|
69
|
+
{ variant: "badge", sentiment: "positive", className: "border-positive/20 bg-positive-subtle dark:border-positive/25" },
|
|
70
|
+
{ variant: "badge", sentiment: "negative", className: "border-negative/20 bg-negative-subtle dark:border-negative/25" },
|
|
71
|
+
{ variant: "badge", sentiment: "neutral", className: "border-neutral/20 bg-neutral-subtle dark:border-neutral/25" },
|
|
72
72
|
],
|
|
73
73
|
defaultVariants: {
|
|
74
74
|
variant: "badge",
|