@adea-ai/ui 0.99.1 → 0.99.3
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/components/layout/orbit-layout/orbit-layout.d.ts +5 -1
- package/dist/components/layout/orbit-layout/orbit-layout.d.ts.map +1 -1
- package/dist/components/layout/orbit-layout/orbit-layout.js.map +1 -1
- package/dist/components/layout/side-rail/side-rail.d.ts +4 -1
- package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
- package/dist/components/layout/side-rail/side-rail.js +58 -64
- package/dist/components/layout/side-rail/side-rail.js.map +1 -1
- package/dist/components/layout/sidebar-nav/sidebar-nav.js +2 -2
- package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
- package/dist/components/motion/presence.d.ts +17 -7
- package/dist/components/motion/presence.d.ts.map +1 -1
- package/dist/components/motion/presence.js.map +1 -1
- package/dist/components/theme/theme-picker.d.ts.map +1 -1
- package/dist/components/theme/theme-picker.js +1 -1
- package/dist/components/theme/theme-picker.js.map +1 -1
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +15 -4
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts +2 -2
- package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts.map +1 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.js +15 -1
- package/dist/components/ui/aspect-ratio/aspect-ratio.js.map +1 -1
- package/dist/components/ui/badge/badge.d.ts +1 -1
- package/dist/components/ui/button/button.d.ts +1 -1
- package/dist/components/ui/calendar/calendar.d.ts.map +1 -1
- package/dist/components/ui/calendar/calendar.js +1 -1
- package/dist/components/ui/calendar/calendar.js.map +1 -1
- package/dist/components/ui/card/card.d.ts +2 -2
- package/dist/components/ui/card/card.js +2 -2
- package/dist/components/ui/card/card.js.map +1 -1
- package/dist/components/ui/chart/chart-options.d.ts +50 -0
- package/dist/components/ui/chart/chart-options.d.ts.map +1 -0
- package/dist/components/ui/chart/chart-options.js +178 -0
- package/dist/components/ui/chart/chart-options.js.map +1 -0
- package/dist/components/ui/chart/chart.d.ts +21 -18
- package/dist/components/ui/chart/chart.d.ts.map +1 -1
- package/dist/components/ui/chart/chart.js +175 -155
- package/dist/components/ui/chart/chart.js.map +1 -1
- package/dist/components/ui/chart/index.js +2 -1
- package/dist/components/ui/context-menu/context-menu.d.ts +8 -0
- package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
- package/dist/components/ui/context-menu/context-menu.js +8 -0
- package/dist/components/ui/context-menu/context-menu.js.map +1 -1
- package/dist/components/ui/drawer/drawer.d.ts +8 -1
- package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
- package/dist/components/ui/drawer/drawer.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -0
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/input-otp/input-otp.d.ts.map +1 -1
- package/dist/components/ui/input-otp/input-otp.js +2 -2
- package/dist/components/ui/input-otp/input-otp.js.map +1 -1
- package/dist/components/ui/menubar/menubar.d.ts +8 -0
- package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
- package/dist/components/ui/menubar/menubar.js +8 -0
- package/dist/components/ui/menubar/menubar.js.map +1 -1
- package/dist/components/ui/navigation-menu/navigation-menu.d.ts +14 -2
- package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
- package/dist/components/ui/navigation-menu/navigation-menu.js +28 -23
- package/dist/components/ui/navigation-menu/navigation-menu.js.map +1 -1
- package/dist/components/ui/pagination/pagination.d.ts.map +1 -1
- package/dist/components/ui/pagination/pagination.js +1 -1
- package/dist/components/ui/pagination/pagination.js.map +1 -1
- package/dist/components/ui/resizable/resizable.d.ts +0 -12
- package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
- package/dist/components/ui/resizable/resizable.js +43 -9
- package/dist/components/ui/resizable/resizable.js.map +1 -1
- package/dist/components/ui/toast/toast.d.ts.map +1 -1
- package/dist/components/ui/toast/toast.js +19 -6
- package/dist/components/ui/toast/toast.js.map +1 -1
- package/dist/lib/cn-tables.generated.js +10 -10
- package/dist/lib/cn-tables.generated.js.map +1 -1
- package/dist/r/chart.json +5 -0
- package/dist/r/registry.json +5 -0
- package/dist/r/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +29 -16
- package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
- package/dist/r/src/components/motion/presence.tsx +17 -7
- package/dist/r/src/components/theme/theme-picker.tsx +5 -1
- package/dist/r/src/components/theme/theme-provider.tsx +19 -9
- package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
- package/dist/r/src/components/ui/calendar/calendar.tsx +6 -1
- package/dist/r/src/components/ui/card/card.tsx +2 -2
- package/dist/r/src/components/ui/chart/chart-options.ts +196 -0
- package/dist/r/src/components/ui/chart/chart.tsx +149 -101
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +8 -0
- package/dist/r/src/components/ui/drawer/drawer.tsx +8 -1
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
- package/dist/r/src/components/ui/input-otp/input-otp.tsx +14 -2
- package/dist/r/src/components/ui/menubar/menubar.tsx +8 -0
- package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
- package/dist/r/src/components/ui/pagination/pagination.tsx +8 -1
- package/dist/r/src/components/ui/resizable/resizable.tsx +46 -8
- package/dist/r/src/components/ui/toast/toast.tsx +21 -6
- package/dist/r/src/lib/cn-tables.generated.ts +10 -10
- package/dist/r/src/styles/base.css +4 -1
- package/dist/r/src/styles/theme.css +23 -43
- package/package.json +1 -1
- package/registry.json +5 -0
- package/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
- package/src/components/layout/side-rail/side-rail.tsx +29 -16
- package/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
- package/src/components/motion/presence.tsx +17 -7
- package/src/components/theme/theme-picker.tsx +5 -1
- package/src/components/theme/theme-provider.tsx +19 -9
- package/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
- package/src/components/ui/calendar/calendar.tsx +6 -1
- package/src/components/ui/card/card.tsx +2 -2
- package/src/components/ui/chart/chart-options.ts +196 -0
- package/src/components/ui/chart/chart.tsx +149 -101
- package/src/components/ui/context-menu/context-menu.tsx +8 -0
- package/src/components/ui/drawer/drawer.tsx +8 -1
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
- package/src/components/ui/input-otp/input-otp.tsx +14 -2
- package/src/components/ui/menubar/menubar.tsx +8 -0
- package/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
- package/src/components/ui/pagination/pagination.tsx +8 -1
- package/src/components/ui/resizable/resizable.tsx +46 -8
- package/src/components/ui/toast/toast.tsx +21 -6
- package/src/lib/cn-tables.generated.ts +10 -10
- package/src/styles/base.css +4 -1
- package/src/styles/theme.css +23 -43
|
@@ -6,8 +6,8 @@ import { cn } from '#lib/utils'
|
|
|
6
6
|
* Card.
|
|
7
7
|
*
|
|
8
8
|
* A surface that sits on the canvas. Every card carries its own edge — a
|
|
9
|
-
* border — because in the light theme `--card`
|
|
10
|
-
*
|
|
9
|
+
* border — because in the light theme `--card` is one faint step off a white
|
|
10
|
+
* `--background`, and a borderless card there all but disappears. Making the border
|
|
11
11
|
* structural rather than optional is what stops one app's cards from reading
|
|
12
12
|
* as floating panels and another's as nothing at all.
|
|
13
13
|
*
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import type { ChartOptions } from 'chart.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The chart's options, as data: no Chart.js runtime and no DOM, so the theme's
|
|
5
|
+
* defaults and the merge rules are testable on their own.
|
|
6
|
+
*
|
|
7
|
+
* Every colour here is written as `var(--token)`. A canvas cannot read a custom
|
|
8
|
+
* property — `fillStyle = 'var(--chart-1)'` is an invalid colour, which the
|
|
9
|
+
* canvas ignores and paints in its default black — so these strings are the
|
|
10
|
+
* *declaration* of which token a colour comes from. The components resolve them
|
|
11
|
+
* against their own element at render time (see `resolveCssVariables` and the
|
|
12
|
+
* resolver in `chart.tsx`).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** The five series colours, in the order a multi-series chart should use them. */
|
|
16
|
+
export const chartSeriesColors = [
|
|
17
|
+
'var(--chart-1)',
|
|
18
|
+
'var(--chart-2)',
|
|
19
|
+
'var(--chart-3)',
|
|
20
|
+
'var(--chart-4)',
|
|
21
|
+
'var(--chart-5)',
|
|
22
|
+
] as const
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The chart furniture, mapped onto existing semantic tokens rather than given
|
|
26
|
+
* tokens of its own. A grid line is a line, so it is `--border`; an axis tick and
|
|
27
|
+
* a legend label are secondary text, so they are `--muted-foreground`, whose
|
|
28
|
+
* contrast on `--card` (the frame's surface) is already measured in
|
|
29
|
+
* `tests/tokens.test.ts`. Dedicated `--chart-grid`/`--chart-axis`/`--chart-label`
|
|
30
|
+
* tokens would be three more values to keep in step with these in every theme
|
|
31
|
+
* for no difference anyone has asked for.
|
|
32
|
+
*/
|
|
33
|
+
const furniture = {
|
|
34
|
+
grid: 'var(--border)',
|
|
35
|
+
axis: 'var(--muted-foreground)',
|
|
36
|
+
label: 'var(--muted-foreground)',
|
|
37
|
+
surface: 'var(--card)',
|
|
38
|
+
} as const
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Which scales a chart type draws. Chart.js creates a scale for every key under
|
|
42
|
+
* `options.scales` whatever the chart type, so cartesian defaults handed to a
|
|
43
|
+
* doughnut draw an x and a y axis and a grid behind it. The shape keeps each
|
|
44
|
+
* type's defaults to the scales it actually has.
|
|
45
|
+
*/
|
|
46
|
+
export type ChartScaleShape = 'cartesian' | 'radial' | 'none'
|
|
47
|
+
|
|
48
|
+
const tickFont = { size: 10 }
|
|
49
|
+
|
|
50
|
+
function scalesFor(shape: ChartScaleShape): ChartOptions['scales'] {
|
|
51
|
+
if (shape === 'cartesian') {
|
|
52
|
+
return {
|
|
53
|
+
x: {
|
|
54
|
+
border: { color: furniture.grid },
|
|
55
|
+
grid: { color: furniture.grid, drawTicks: false },
|
|
56
|
+
ticks: { color: furniture.axis, font: tickFont },
|
|
57
|
+
},
|
|
58
|
+
y: {
|
|
59
|
+
border: { display: false },
|
|
60
|
+
grid: { color: furniture.grid, drawTicks: false },
|
|
61
|
+
ticks: { color: furniture.axis, font: tickFont },
|
|
62
|
+
},
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (shape === 'radial') {
|
|
66
|
+
return {
|
|
67
|
+
r: {
|
|
68
|
+
grid: { color: furniture.grid },
|
|
69
|
+
angleLines: { color: furniture.grid },
|
|
70
|
+
pointLabels: { color: furniture.axis, font: tickFont },
|
|
71
|
+
// The default backdrop is a translucent white box, which is a white box
|
|
72
|
+
// on a dark card. The frame's own surface makes it disappear in both.
|
|
73
|
+
ticks: { color: furniture.axis, backdropColor: furniture.surface, font: tickFont },
|
|
74
|
+
},
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
81
|
+
if (typeof value !== 'object' || value === null) return false
|
|
82
|
+
const prototype = Object.getPrototypeOf(value)
|
|
83
|
+
return prototype === Object.prototype || prototype === null
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function mergeInto(target: Record<string, unknown>, source: Record<string, unknown>) {
|
|
87
|
+
for (const [key, value] of Object.entries(source)) {
|
|
88
|
+
if (value === undefined) continue
|
|
89
|
+
const current = target[key]
|
|
90
|
+
target[key] =
|
|
91
|
+
isPlainObject(value) && isPlainObject(current)
|
|
92
|
+
? mergeInto({ ...current }, value)
|
|
93
|
+
: isPlainObject(value)
|
|
94
|
+
? mergeInto({}, value)
|
|
95
|
+
: value
|
|
96
|
+
}
|
|
97
|
+
return target
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Merge Chart.js options, later sources winning, object by object.
|
|
102
|
+
*
|
|
103
|
+
* Deep, because Chart.js options are deep: `{ scales: { x: { stacked: true } } }`
|
|
104
|
+
* means "also stack x", and a spread would replace the whole axis — its grid,
|
|
105
|
+
* its tick colour, its font — with that one flag. Arrays, functions (scriptable
|
|
106
|
+
* options) and class instances (gradients, patterns) are values and replace
|
|
107
|
+
* rather than merge. No source is mutated.
|
|
108
|
+
*/
|
|
109
|
+
export function mergeChartOptions(...sources: (ChartOptions | undefined)[]): ChartOptions {
|
|
110
|
+
const merged: Record<string, unknown> = {}
|
|
111
|
+
for (const source of sources) {
|
|
112
|
+
if (source) mergeInto(merged, source as Record<string, unknown>)
|
|
113
|
+
}
|
|
114
|
+
return merged as ChartOptions
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The theme's defaults as Chart.js options.
|
|
119
|
+
*
|
|
120
|
+
* Exported so a caller can spread it and change one thing, rather than
|
|
121
|
+
* restating every colour. Everything a chart draws that is not data — grid,
|
|
122
|
+
* ticks, legend, tooltip — is decided here once, which is what makes two charts
|
|
123
|
+
* in one application look like they came from the same place. `overrides` is
|
|
124
|
+
* merged deeply; `shape` selects the scales the chart type has.
|
|
125
|
+
*/
|
|
126
|
+
export function chartOptions(
|
|
127
|
+
overrides?: ChartOptions,
|
|
128
|
+
shape: ChartScaleShape = 'cartesian'
|
|
129
|
+
): ChartOptions {
|
|
130
|
+
return mergeChartOptions(
|
|
131
|
+
{
|
|
132
|
+
responsive: true,
|
|
133
|
+
maintainAspectRatio: false,
|
|
134
|
+
interaction: { mode: 'nearest', intersect: false },
|
|
135
|
+
plugins: {
|
|
136
|
+
legend: {
|
|
137
|
+
labels: {
|
|
138
|
+
color: furniture.label,
|
|
139
|
+
boxWidth: 8,
|
|
140
|
+
boxHeight: 8,
|
|
141
|
+
usePointStyle: true,
|
|
142
|
+
pointStyle: 'circle',
|
|
143
|
+
font: { size: 11 },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
tooltip: {
|
|
147
|
+
backgroundColor: 'var(--popover)',
|
|
148
|
+
titleColor: 'var(--popover-foreground)',
|
|
149
|
+
bodyColor: 'var(--popover-foreground)',
|
|
150
|
+
borderColor: 'var(--border)',
|
|
151
|
+
borderWidth: 1,
|
|
152
|
+
padding: 8,
|
|
153
|
+
cornerRadius: 6,
|
|
154
|
+
displayColors: true,
|
|
155
|
+
boxWidth: 8,
|
|
156
|
+
boxHeight: 8,
|
|
157
|
+
usePointStyle: true,
|
|
158
|
+
titleFont: { size: 11, weight: 600 },
|
|
159
|
+
bodyFont: { size: 11 },
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
scales: scalesFor(shape),
|
|
163
|
+
},
|
|
164
|
+
overrides
|
|
165
|
+
)
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const cssVariable = /^\s*var\(\s*(--[\w-]+)\s*(?:,\s*(.*?))?\s*\)\s*$/
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Replace every `var(--token)` string in `value` — options, datasets, nested
|
|
172
|
+
* arrays — with what `read` returns for the token. A token `read` cannot
|
|
173
|
+
* resolve falls back to the `var()`'s own fallback, then to the string as
|
|
174
|
+
* written. Functions and class instances pass through untouched, so scriptable
|
|
175
|
+
* options and gradients keep working.
|
|
176
|
+
*/
|
|
177
|
+
export function resolveCssVariables<T>(value: T, read: (name: string) => string | undefined): T {
|
|
178
|
+
if (typeof value === 'string') {
|
|
179
|
+
const match = cssVariable.exec(value)
|
|
180
|
+
if (!match) return value
|
|
181
|
+
const resolved = read(match[1]!)
|
|
182
|
+
if (resolved) return resolved as T
|
|
183
|
+
return (match[2] ? resolveCssVariables(match[2], read) : value) as T
|
|
184
|
+
}
|
|
185
|
+
if (Array.isArray(value)) {
|
|
186
|
+
return value.map((entry: unknown) => resolveCssVariables(entry, read)) as T
|
|
187
|
+
}
|
|
188
|
+
if (isPlainObject(value)) {
|
|
189
|
+
const resolved: Record<string, unknown> = {}
|
|
190
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
191
|
+
resolved[key] = resolveCssVariables(entry, read)
|
|
192
|
+
}
|
|
193
|
+
return resolved as T
|
|
194
|
+
}
|
|
195
|
+
return value
|
|
196
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { For, splitProps, type JSX } from 'solid-js'
|
|
1
|
+
import { For, Show, createSignal, onCleanup, onMount, splitProps, type JSX } from 'solid-js'
|
|
2
2
|
import { Dynamic } from 'solid-js/web'
|
|
3
3
|
import { Bar, Bubble, Doughnut, Line, Pie, PolarArea, Radar, Scatter } from 'solid-chartjs'
|
|
4
4
|
import {
|
|
@@ -17,6 +17,13 @@ import {
|
|
|
17
17
|
} from 'chart.js'
|
|
18
18
|
import type { ChartOptions, ChartType } from 'chart.js'
|
|
19
19
|
import { cn } from '#lib/utils'
|
|
20
|
+
import {
|
|
21
|
+
chartOptions,
|
|
22
|
+
chartSeriesColors,
|
|
23
|
+
mergeChartOptions,
|
|
24
|
+
resolveCssVariables,
|
|
25
|
+
type ChartScaleShape,
|
|
26
|
+
} from './chart-options'
|
|
20
27
|
|
|
21
28
|
/**
|
|
22
29
|
* Chart.
|
|
@@ -46,11 +53,16 @@ import { cn } from '#lib/utils'
|
|
|
46
53
|
*
|
|
47
54
|
* ## Theming
|
|
48
55
|
*
|
|
49
|
-
* Colours
|
|
50
|
-
* defines,
|
|
51
|
-
* chart follows the accent axis
|
|
52
|
-
* per-chart configuration — and a caller that wants a
|
|
53
|
-
* passes it explicitly
|
|
56
|
+
* Colours come from `--chart-1` through `--chart-5`, which every theme
|
|
57
|
+
* defines, and the grid, ticks and labels from `--border` and
|
|
58
|
+
* `--muted-foreground`. That means a chart follows the accent axis and the
|
|
59
|
+
* appearance with no per-chart configuration — and a caller that wants a
|
|
60
|
+
* specific series colour passes it explicitly, as a colour or as `var(--token)`.
|
|
61
|
+
*
|
|
62
|
+
* A canvas cannot read a custom property, so the options and datasets carry
|
|
63
|
+
* `var(--token)` strings and each chart resolves them against its own element
|
|
64
|
+
* when it renders, and again whenever the root's attributes change — the
|
|
65
|
+
* `.dark` class, `data-accent`, an inline override. See `createThemeResolver`.
|
|
54
66
|
*/
|
|
55
67
|
|
|
56
68
|
/**
|
|
@@ -78,68 +90,72 @@ ChartJs.register(
|
|
|
78
90
|
Legend
|
|
79
91
|
)
|
|
80
92
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
export { chartOptions, chartSeriesColors }
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* One pixel to paint a colour on, shared by every chart.
|
|
97
|
+
*
|
|
98
|
+
* The canvas *paints* `oklch()`, but Chart.js also *parses* colours — to derive a
|
|
99
|
+
* hover shade and to animate between two colours — and its parser reads only
|
|
100
|
+
* rgb, hsl, hex and names. Painting the computed value and reading the pixel
|
|
101
|
+
* back gives the sRGB colour the canvas would have drawn anyway, in a form both
|
|
102
|
+
* understand, whatever syntax a theme uses (`oklch()`, `color-mix()`, …).
|
|
103
|
+
*/
|
|
104
|
+
let swatch: CanvasRenderingContext2D | null | undefined
|
|
105
|
+
|
|
106
|
+
function toCanvasColor(value: string): string {
|
|
107
|
+
if (typeof CSS === 'undefined' || !CSS.supports('color', value)) return value
|
|
108
|
+
if (swatch === undefined) {
|
|
109
|
+
const canvas = document.createElement('canvas')
|
|
110
|
+
canvas.width = 1
|
|
111
|
+
canvas.height = 1
|
|
112
|
+
swatch = canvas.getContext('2d', { willReadFrequently: true })
|
|
113
|
+
}
|
|
114
|
+
if (!swatch) return value
|
|
115
|
+
swatch.globalCompositeOperation = 'copy'
|
|
116
|
+
swatch.fillStyle = value
|
|
117
|
+
swatch.fillRect(0, 0, 1, 1)
|
|
118
|
+
const [r, g, b, a] = swatch.getImageData(0, 0, 1, 1).data
|
|
119
|
+
return `rgba(${r}, ${g}, ${b}, ${Math.round(((a ?? 255) / 255) * 1000) / 1000})`
|
|
120
|
+
}
|
|
89
121
|
|
|
90
122
|
/**
|
|
91
|
-
*
|
|
123
|
+
* Resolves `var(--token)` strings against a chart's element, and re-resolves
|
|
124
|
+
* them when the theme changes.
|
|
92
125
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
126
|
+
* The element rather than the root, so a chart inside a subtree with its own
|
|
127
|
+
* inline tokens takes those. The observer watches every attribute on `<html>`,
|
|
128
|
+
* because that is where both applications switch appearance, accent and theme
|
|
129
|
+
* (`.dark`, `data-accent`, `data-theme`, inline variables); a theme axis added
|
|
130
|
+
* later is covered without touching this. It is released with the chart.
|
|
97
131
|
*/
|
|
98
|
-
|
|
132
|
+
function createThemeResolver() {
|
|
133
|
+
let element: HTMLElement | undefined
|
|
134
|
+
const [revision, invalidate] = createSignal(undefined, { equals: false })
|
|
135
|
+
|
|
136
|
+
onMount(() => {
|
|
137
|
+
const observer = new MutationObserver(() => invalidate())
|
|
138
|
+
observer.observe(document.documentElement, { attributes: true })
|
|
139
|
+
onCleanup(() => observer.disconnect())
|
|
140
|
+
})
|
|
141
|
+
|
|
99
142
|
return {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
interaction: { mode: 'nearest', intersect: false },
|
|
103
|
-
plugins: {
|
|
104
|
-
legend: {
|
|
105
|
-
labels: {
|
|
106
|
-
color: 'var(--chart-label)',
|
|
107
|
-
boxWidth: 8,
|
|
108
|
-
boxHeight: 8,
|
|
109
|
-
usePointStyle: true,
|
|
110
|
-
pointStyle: 'circle',
|
|
111
|
-
font: { size: 11 },
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
tooltip: {
|
|
115
|
-
backgroundColor: 'var(--popover)',
|
|
116
|
-
titleColor: 'var(--popover-foreground)',
|
|
117
|
-
bodyColor: 'var(--popover-foreground)',
|
|
118
|
-
borderColor: 'var(--border)',
|
|
119
|
-
borderWidth: 1,
|
|
120
|
-
padding: 8,
|
|
121
|
-
cornerRadius: 6,
|
|
122
|
-
displayColors: true,
|
|
123
|
-
boxWidth: 8,
|
|
124
|
-
boxHeight: 8,
|
|
125
|
-
usePointStyle: true,
|
|
126
|
-
titleFont: { size: 11, weight: 600 },
|
|
127
|
-
bodyFont: { size: 11 },
|
|
128
|
-
},
|
|
143
|
+
ref: (el: HTMLElement) => {
|
|
144
|
+
element = el
|
|
129
145
|
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
146
|
+
resolve<T>(value: T): T {
|
|
147
|
+
revision()
|
|
148
|
+
if (!element || typeof getComputedStyle === 'undefined') return value
|
|
149
|
+
const style = getComputedStyle(element)
|
|
150
|
+
const resolved = new Map<string, string | undefined>()
|
|
151
|
+
return resolveCssVariables(value, (name) => {
|
|
152
|
+
if (!resolved.has(name)) {
|
|
153
|
+
const raw = style.getPropertyValue(name).trim()
|
|
154
|
+
resolved.set(name, raw ? toCanvasColor(raw) : undefined)
|
|
155
|
+
}
|
|
156
|
+
return resolved.get(name)
|
|
157
|
+
})
|
|
141
158
|
},
|
|
142
|
-
...overrides,
|
|
143
159
|
}
|
|
144
160
|
}
|
|
145
161
|
|
|
@@ -163,7 +179,9 @@ export function ChartFrame(props: ChartFrameProps) {
|
|
|
163
179
|
<figcaption class="flex items-start justify-between gap-3">
|
|
164
180
|
<div>
|
|
165
181
|
<h3 class="text-sm font-semibold text-foreground">{local.title}</h3>
|
|
166
|
-
{local.description}
|
|
182
|
+
<Show when={local.description}>
|
|
183
|
+
<p class="text-xs text-muted-foreground">{local.description}</p>
|
|
184
|
+
</Show>
|
|
167
185
|
</div>
|
|
168
186
|
{local.actions}
|
|
169
187
|
</figcaption>
|
|
@@ -177,7 +195,7 @@ type SeriesChartProps = {
|
|
|
177
195
|
labels: readonly string[]
|
|
178
196
|
/** One entry per series. `data` is aligned to `labels`. */
|
|
179
197
|
series: readonly { label: string; data: readonly number[]; color?: string }[]
|
|
180
|
-
/** Chart.js options, merged over the theme's defaults. */
|
|
198
|
+
/** Chart.js options, merged deeply over the theme's defaults. */
|
|
181
199
|
options?: ChartOptions
|
|
182
200
|
/** Stack the series. */
|
|
183
201
|
stacked?: boolean
|
|
@@ -206,26 +224,31 @@ function datasetsFor(series: SeriesChartProps['series'], filled: boolean) {
|
|
|
206
224
|
}))
|
|
207
225
|
}
|
|
208
226
|
|
|
209
|
-
function seriesOptions(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
function seriesOptions(
|
|
228
|
+
props: SeriesChartProps,
|
|
229
|
+
extra?: ChartOptions,
|
|
230
|
+
shape: ChartScaleShape = 'cartesian'
|
|
231
|
+
): ChartOptions {
|
|
232
|
+
// Stacking is a cartesian idea; a radar has no x or y to stack, and naming
|
|
233
|
+
// them would make Chart.js draw them.
|
|
234
|
+
const stacked =
|
|
235
|
+
props.stacked && shape === 'cartesian'
|
|
236
|
+
? { scales: { x: { stacked: true }, y: { stacked: true } } }
|
|
237
|
+
: undefined
|
|
238
|
+
return chartOptions(mergeChartOptions(extra, stacked, props.options), shape)
|
|
217
239
|
}
|
|
218
240
|
|
|
219
241
|
/** A line chart. The default for a series over time. */
|
|
220
242
|
export function LineChart(props: SeriesChartProps & { area?: boolean }) {
|
|
243
|
+
const theme = createThemeResolver()
|
|
221
244
|
return (
|
|
222
|
-
<div class={cn('h-64', props.class)}>
|
|
245
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
223
246
|
<Line
|
|
224
|
-
data={{
|
|
247
|
+
data={theme.resolve({
|
|
225
248
|
labels: [...props.labels],
|
|
226
249
|
datasets: datasetsFor(props.series, props.area ?? false),
|
|
227
|
-
}}
|
|
228
|
-
options={seriesOptions(props)}
|
|
250
|
+
})}
|
|
251
|
+
options={theme.resolve(seriesOptions(props))}
|
|
229
252
|
/>
|
|
230
253
|
</div>
|
|
231
254
|
)
|
|
@@ -233,18 +256,19 @@ export function LineChart(props: SeriesChartProps & { area?: boolean }) {
|
|
|
233
256
|
|
|
234
257
|
/** A bar chart. `stacked` for parts of a whole over categories. */
|
|
235
258
|
export function BarChart(props: SeriesChartProps & { horizontal?: boolean }) {
|
|
259
|
+
const theme = createThemeResolver()
|
|
236
260
|
return (
|
|
237
|
-
<div class={cn('h-64', props.class)}>
|
|
261
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
238
262
|
<Bar
|
|
239
|
-
data={{
|
|
263
|
+
data={theme.resolve({
|
|
240
264
|
labels: [...props.labels],
|
|
241
265
|
datasets: datasetsFor(props.series, true).map((dataset) => ({
|
|
242
266
|
...dataset,
|
|
243
267
|
borderWidth: 0,
|
|
244
268
|
borderRadius: 3,
|
|
245
269
|
})),
|
|
246
|
-
}}
|
|
247
|
-
options={seriesOptions(props, { indexAxis: props.horizontal ? 'y' : 'x' })}
|
|
270
|
+
})}
|
|
271
|
+
options={theme.resolve(seriesOptions(props, { indexAxis: props.horizontal ? 'y' : 'x' }))}
|
|
248
272
|
/>
|
|
249
273
|
</div>
|
|
250
274
|
)
|
|
@@ -256,10 +280,11 @@ export function ScatterChart(props: {
|
|
|
256
280
|
options?: ChartOptions
|
|
257
281
|
class?: string
|
|
258
282
|
}) {
|
|
283
|
+
const theme = createThemeResolver()
|
|
259
284
|
return (
|
|
260
|
-
<div class={cn('h-64', props.class)}>
|
|
285
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
261
286
|
<Scatter
|
|
262
|
-
data={{
|
|
287
|
+
data={theme.resolve({
|
|
263
288
|
datasets: props.series.map((entry, index) => ({
|
|
264
289
|
label: entry.label,
|
|
265
290
|
data: entry.data.map((point) => ({ ...point })),
|
|
@@ -267,8 +292,8 @@ export function ScatterChart(props: {
|
|
|
267
292
|
pointRadius: 3,
|
|
268
293
|
pointHoverRadius: 5,
|
|
269
294
|
})),
|
|
270
|
-
}}
|
|
271
|
-
options={chartOptions(props.options)}
|
|
295
|
+
})}
|
|
296
|
+
options={theme.resolve(chartOptions(props.options))}
|
|
272
297
|
/>
|
|
273
298
|
</div>
|
|
274
299
|
)
|
|
@@ -284,17 +309,18 @@ export function BubbleChart(props: {
|
|
|
284
309
|
options?: ChartOptions
|
|
285
310
|
class?: string
|
|
286
311
|
}) {
|
|
312
|
+
const theme = createThemeResolver()
|
|
287
313
|
return (
|
|
288
|
-
<div class={cn('h-64', props.class)}>
|
|
314
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
289
315
|
<Bubble
|
|
290
|
-
data={{
|
|
316
|
+
data={theme.resolve({
|
|
291
317
|
datasets: props.series.map((entry, index) => ({
|
|
292
318
|
label: entry.label,
|
|
293
319
|
data: entry.data.map((point) => ({ ...point })),
|
|
294
320
|
backgroundColor: entry.color ?? chartSeriesColors[index % chartSeriesColors.length],
|
|
295
321
|
})),
|
|
296
|
-
}}
|
|
297
|
-
options={chartOptions(props.options)}
|
|
322
|
+
})}
|
|
323
|
+
options={theme.resolve(chartOptions(props.options))}
|
|
298
324
|
/>
|
|
299
325
|
</div>
|
|
300
326
|
)
|
|
@@ -302,27 +328,39 @@ export function BubbleChart(props: {
|
|
|
302
328
|
|
|
303
329
|
/** A radar chart, for a profile across fixed axes. */
|
|
304
330
|
export function RadarChart(props: SeriesChartProps) {
|
|
331
|
+
const theme = createThemeResolver()
|
|
305
332
|
return (
|
|
306
|
-
<div class={cn('h-64', props.class)}>
|
|
333
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
307
334
|
<Radar
|
|
308
|
-
data={
|
|
309
|
-
|
|
335
|
+
data={theme.resolve({
|
|
336
|
+
labels: [...props.labels],
|
|
337
|
+
datasets: datasetsFor(props.series, true),
|
|
338
|
+
})}
|
|
339
|
+
options={theme.resolve(seriesOptions(props, undefined, 'radial'))}
|
|
310
340
|
/>
|
|
311
341
|
</div>
|
|
312
342
|
)
|
|
313
343
|
}
|
|
314
344
|
|
|
315
|
-
/**
|
|
345
|
+
/**
|
|
346
|
+
* A polar area chart: radial bars, for a composition.
|
|
347
|
+
*
|
|
348
|
+
* Its scale is left to Chart.js's polar-area defaults (`'none'` rather than
|
|
349
|
+
* `'radial'`): `solid-chartjs` deletes `options.scales.r` for every chart type
|
|
350
|
+
* but radar when it first creates the chart, so radial styling here would apply
|
|
351
|
+
* only after the first theme change — a chart that restyles itself on a toggle.
|
|
352
|
+
*/
|
|
316
353
|
export function PolarAreaChart(props: {
|
|
317
354
|
labels: readonly string[]
|
|
318
355
|
data: readonly number[]
|
|
319
356
|
options?: ChartOptions
|
|
320
357
|
class?: string
|
|
321
358
|
}) {
|
|
359
|
+
const theme = createThemeResolver()
|
|
322
360
|
return (
|
|
323
|
-
<div class={cn('h-64', props.class)}>
|
|
361
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
324
362
|
<PolarArea
|
|
325
|
-
data={{
|
|
363
|
+
data={theme.resolve({
|
|
326
364
|
labels: [...props.labels],
|
|
327
365
|
datasets: [
|
|
328
366
|
{
|
|
@@ -332,8 +370,8 @@ export function PolarAreaChart(props: {
|
|
|
332
370
|
borderWidth: 2,
|
|
333
371
|
},
|
|
334
372
|
],
|
|
335
|
-
}}
|
|
336
|
-
options={chartOptions(props.options)}
|
|
373
|
+
})}
|
|
374
|
+
options={theme.resolve(chartOptions(props.options, 'none'))}
|
|
337
375
|
/>
|
|
338
376
|
</div>
|
|
339
377
|
)
|
|
@@ -356,14 +394,15 @@ export function DonutChart(props: {
|
|
|
356
394
|
options?: ChartOptions
|
|
357
395
|
class?: string
|
|
358
396
|
}) {
|
|
397
|
+
const theme = createThemeResolver()
|
|
359
398
|
// `Dynamic` rather than a local component accessor: Solid's JSX types require
|
|
360
399
|
// a component reference, and an accessor returning one is a call in disguise.
|
|
361
400
|
const component = () => (props.cutout === 0 ? Pie : Doughnut)
|
|
362
401
|
return (
|
|
363
|
-
<div class={cn('h-64', props.class)}>
|
|
402
|
+
<div ref={theme.ref} class={cn('h-64', props.class)}>
|
|
364
403
|
<Dynamic
|
|
365
404
|
component={component()}
|
|
366
|
-
data={{
|
|
405
|
+
data={theme.resolve({
|
|
367
406
|
labels: [...props.labels],
|
|
368
407
|
datasets: [
|
|
369
408
|
{
|
|
@@ -375,11 +414,20 @@ export function DonutChart(props: {
|
|
|
375
414
|
borderWidth: 2,
|
|
376
415
|
},
|
|
377
416
|
],
|
|
378
|
-
}}
|
|
379
|
-
options={chartOptions({
|
|
380
|
-
...props.options,
|
|
381
|
-
...(props.cutout === 0 ? {} : { cutout: `${props.cutout ?? 60}%` }),
|
|
382
417
|
})}
|
|
418
|
+
options={theme.resolve(
|
|
419
|
+
chartOptions(
|
|
420
|
+
mergeChartOptions(
|
|
421
|
+
props.cutout === 0
|
|
422
|
+
? undefined
|
|
423
|
+
: ({
|
|
424
|
+
cutout: `${props.cutout ?? 60}%`,
|
|
425
|
+
} satisfies ChartOptions<'doughnut'> as ChartOptions),
|
|
426
|
+
props.options
|
|
427
|
+
),
|
|
428
|
+
'none'
|
|
429
|
+
)
|
|
430
|
+
)}
|
|
383
431
|
/>
|
|
384
432
|
</div>
|
|
385
433
|
)
|
|
@@ -131,6 +131,14 @@ export function ContextMenuRadioItem(props: ComponentProps<typeof KobalteContext
|
|
|
131
131
|
)
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* The caption of a `ContextMenuGroup`, and that group's accessible name. It must be a
|
|
136
|
+
* child of the group it names: Kobalte reads the group from context, so a label
|
|
137
|
+
* placed directly in the content throws `useMenuGroupContext must be used within
|
|
138
|
+
* a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
|
|
139
|
+
* label and its items in a group anyway — the grouping is what a screen reader
|
|
140
|
+
* announces.
|
|
141
|
+
*/
|
|
134
142
|
export function ContextMenuLabel(props: ComponentProps<typeof KobalteContextMenu.GroupLabel>) {
|
|
135
143
|
const [local, rest] = splitProps(props, ['class'])
|
|
136
144
|
return <KobalteContextMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
|
|
@@ -41,7 +41,14 @@ export function DrawerOverlay(props: ComponentProps<typeof DrawerPrimitive.Overl
|
|
|
41
41
|
)
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export type DrawerContentProps = ComponentProps<typeof DrawerPrimitive.Content> & {
|
|
44
|
+
export type DrawerContentProps = Omit<ComponentProps<typeof DrawerPrimitive.Content>, 'side'> & {
|
|
45
|
+
/**
|
|
46
|
+
* Not accepted here. corvu reads the side from the root `Drawer`, which uses it
|
|
47
|
+
* for the drag axis and publishes it as `data-side`; a `side` on the content
|
|
48
|
+
* would type-check through the polymorphic props and be silently ignored,
|
|
49
|
+
* leaving the drawer on the bottom edge. Set `<Drawer side="right">` instead.
|
|
50
|
+
*/
|
|
51
|
+
side?: never
|
|
45
52
|
/**
|
|
46
53
|
* Draw the grabber at the leading edge of the sheet.
|
|
47
54
|
*
|
|
@@ -187,6 +187,14 @@ export function DropdownMenuRadioItem(props: ComponentProps<typeof KobalteDropdo
|
|
|
187
187
|
)
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
/**
|
|
191
|
+
* The caption of a `DropdownMenuGroup`, and that group's accessible name. It must be a
|
|
192
|
+
* child of the group it names: Kobalte reads the group from context, so a label
|
|
193
|
+
* placed directly in the content throws `useMenuGroupContext must be used within
|
|
194
|
+
* a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
|
|
195
|
+
* label and its items in a group anyway — the grouping is what a screen reader
|
|
196
|
+
* announces.
|
|
197
|
+
*/
|
|
190
198
|
export function DropdownMenuLabel(props: ComponentProps<typeof KobalteDropdownMenu.GroupLabel>) {
|
|
191
199
|
const [local, rest] = splitProps(props, ['class'])
|
|
192
200
|
return <KobalteDropdownMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
|