@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.
Files changed (123) hide show
  1. package/dist/components/layout/orbit-layout/orbit-layout.d.ts +5 -1
  2. package/dist/components/layout/orbit-layout/orbit-layout.d.ts.map +1 -1
  3. package/dist/components/layout/orbit-layout/orbit-layout.js.map +1 -1
  4. package/dist/components/layout/side-rail/side-rail.d.ts +4 -1
  5. package/dist/components/layout/side-rail/side-rail.d.ts.map +1 -1
  6. package/dist/components/layout/side-rail/side-rail.js +58 -64
  7. package/dist/components/layout/side-rail/side-rail.js.map +1 -1
  8. package/dist/components/layout/sidebar-nav/sidebar-nav.js +2 -2
  9. package/dist/components/layout/sidebar-nav/sidebar-nav.js.map +1 -1
  10. package/dist/components/motion/presence.d.ts +17 -7
  11. package/dist/components/motion/presence.d.ts.map +1 -1
  12. package/dist/components/motion/presence.js.map +1 -1
  13. package/dist/components/theme/theme-picker.d.ts.map +1 -1
  14. package/dist/components/theme/theme-picker.js +1 -1
  15. package/dist/components/theme/theme-picker.js.map +1 -1
  16. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  17. package/dist/components/theme/theme-provider.js +15 -4
  18. package/dist/components/theme/theme-provider.js.map +1 -1
  19. package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts +2 -2
  20. package/dist/components/ui/aspect-ratio/aspect-ratio.d.ts.map +1 -1
  21. package/dist/components/ui/aspect-ratio/aspect-ratio.js +15 -1
  22. package/dist/components/ui/aspect-ratio/aspect-ratio.js.map +1 -1
  23. package/dist/components/ui/badge/badge.d.ts +1 -1
  24. package/dist/components/ui/button/button.d.ts +1 -1
  25. package/dist/components/ui/calendar/calendar.d.ts.map +1 -1
  26. package/dist/components/ui/calendar/calendar.js +1 -1
  27. package/dist/components/ui/calendar/calendar.js.map +1 -1
  28. package/dist/components/ui/card/card.d.ts +2 -2
  29. package/dist/components/ui/card/card.js +2 -2
  30. package/dist/components/ui/card/card.js.map +1 -1
  31. package/dist/components/ui/chart/chart-options.d.ts +50 -0
  32. package/dist/components/ui/chart/chart-options.d.ts.map +1 -0
  33. package/dist/components/ui/chart/chart-options.js +178 -0
  34. package/dist/components/ui/chart/chart-options.js.map +1 -0
  35. package/dist/components/ui/chart/chart.d.ts +21 -18
  36. package/dist/components/ui/chart/chart.d.ts.map +1 -1
  37. package/dist/components/ui/chart/chart.js +175 -155
  38. package/dist/components/ui/chart/chart.js.map +1 -1
  39. package/dist/components/ui/chart/index.js +2 -1
  40. package/dist/components/ui/context-menu/context-menu.d.ts +8 -0
  41. package/dist/components/ui/context-menu/context-menu.d.ts.map +1 -1
  42. package/dist/components/ui/context-menu/context-menu.js +8 -0
  43. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  44. package/dist/components/ui/drawer/drawer.d.ts +8 -1
  45. package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
  46. package/dist/components/ui/drawer/drawer.js.map +1 -1
  47. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts +8 -0
  48. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  49. package/dist/components/ui/dropdown-menu/dropdown-menu.js +8 -0
  50. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  51. package/dist/components/ui/input-otp/input-otp.d.ts.map +1 -1
  52. package/dist/components/ui/input-otp/input-otp.js +2 -2
  53. package/dist/components/ui/input-otp/input-otp.js.map +1 -1
  54. package/dist/components/ui/menubar/menubar.d.ts +8 -0
  55. package/dist/components/ui/menubar/menubar.d.ts.map +1 -1
  56. package/dist/components/ui/menubar/menubar.js +8 -0
  57. package/dist/components/ui/menubar/menubar.js.map +1 -1
  58. package/dist/components/ui/navigation-menu/navigation-menu.d.ts +14 -2
  59. package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
  60. package/dist/components/ui/navigation-menu/navigation-menu.js +28 -23
  61. package/dist/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  62. package/dist/components/ui/pagination/pagination.d.ts.map +1 -1
  63. package/dist/components/ui/pagination/pagination.js +1 -1
  64. package/dist/components/ui/pagination/pagination.js.map +1 -1
  65. package/dist/components/ui/resizable/resizable.d.ts +0 -12
  66. package/dist/components/ui/resizable/resizable.d.ts.map +1 -1
  67. package/dist/components/ui/resizable/resizable.js +43 -9
  68. package/dist/components/ui/resizable/resizable.js.map +1 -1
  69. package/dist/components/ui/toast/toast.d.ts.map +1 -1
  70. package/dist/components/ui/toast/toast.js +19 -6
  71. package/dist/components/ui/toast/toast.js.map +1 -1
  72. package/dist/lib/cn-tables.generated.js +10 -10
  73. package/dist/lib/cn-tables.generated.js.map +1 -1
  74. package/dist/r/chart.json +5 -0
  75. package/dist/r/registry.json +5 -0
  76. package/dist/r/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
  77. package/dist/r/src/components/layout/side-rail/side-rail.tsx +29 -16
  78. package/dist/r/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
  79. package/dist/r/src/components/motion/presence.tsx +17 -7
  80. package/dist/r/src/components/theme/theme-picker.tsx +5 -1
  81. package/dist/r/src/components/theme/theme-provider.tsx +19 -9
  82. package/dist/r/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
  83. package/dist/r/src/components/ui/calendar/calendar.tsx +6 -1
  84. package/dist/r/src/components/ui/card/card.tsx +2 -2
  85. package/dist/r/src/components/ui/chart/chart-options.ts +196 -0
  86. package/dist/r/src/components/ui/chart/chart.tsx +149 -101
  87. package/dist/r/src/components/ui/context-menu/context-menu.tsx +8 -0
  88. package/dist/r/src/components/ui/drawer/drawer.tsx +8 -1
  89. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  90. package/dist/r/src/components/ui/input-otp/input-otp.tsx +14 -2
  91. package/dist/r/src/components/ui/menubar/menubar.tsx +8 -0
  92. package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  93. package/dist/r/src/components/ui/pagination/pagination.tsx +8 -1
  94. package/dist/r/src/components/ui/resizable/resizable.tsx +46 -8
  95. package/dist/r/src/components/ui/toast/toast.tsx +21 -6
  96. package/dist/r/src/lib/cn-tables.generated.ts +10 -10
  97. package/dist/r/src/styles/base.css +4 -1
  98. package/dist/r/src/styles/theme.css +23 -43
  99. package/package.json +1 -1
  100. package/registry.json +5 -0
  101. package/src/components/layout/orbit-layout/orbit-layout.tsx +5 -1
  102. package/src/components/layout/side-rail/side-rail.tsx +29 -16
  103. package/src/components/layout/sidebar-nav/sidebar-nav.tsx +2 -2
  104. package/src/components/motion/presence.tsx +17 -7
  105. package/src/components/theme/theme-picker.tsx +5 -1
  106. package/src/components/theme/theme-provider.tsx +19 -9
  107. package/src/components/ui/aspect-ratio/aspect-ratio.tsx +14 -3
  108. package/src/components/ui/calendar/calendar.tsx +6 -1
  109. package/src/components/ui/card/card.tsx +2 -2
  110. package/src/components/ui/chart/chart-options.ts +196 -0
  111. package/src/components/ui/chart/chart.tsx +149 -101
  112. package/src/components/ui/context-menu/context-menu.tsx +8 -0
  113. package/src/components/ui/drawer/drawer.tsx +8 -1
  114. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +8 -0
  115. package/src/components/ui/input-otp/input-otp.tsx +14 -2
  116. package/src/components/ui/menubar/menubar.tsx +8 -0
  117. package/src/components/ui/navigation-menu/navigation-menu.tsx +42 -28
  118. package/src/components/ui/pagination/pagination.tsx +8 -1
  119. package/src/components/ui/resizable/resizable.tsx +46 -8
  120. package/src/components/ui/toast/toast.tsx +21 -6
  121. package/src/lib/cn-tables.generated.ts +10 -10
  122. package/src/styles/base.css +4 -1
  123. 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` and `--background` are both
10
- * white, and a borderless card there is invisible. Making the border
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 resolve from `--chart-1` through `--chart-5`, which every theme
50
- * defines, plus `--chart-grid`, `--chart-axis` and `--chart-label`. That means a
51
- * chart follows the accent axis, the appearance, and the font axis with no
52
- * per-chart configuration — and a caller that wants a specific series colour
53
- * passes it explicitly rather than reaching into the theme.
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
- /** The five series colours, in the order a multi-series chart should use them. */
82
- export const chartSeriesColors = [
83
- 'var(--chart-1)',
84
- 'var(--chart-2)',
85
- 'var(--chart-3)',
86
- 'var(--chart-4)',
87
- 'var(--chart-5)',
88
- ] as const
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
- * The theme's defaults as Chart.js options.
123
+ * Resolves `var(--token)` strings against a chart's element, and re-resolves
124
+ * them when the theme changes.
92
125
  *
93
- * Exported so a caller can spread it and change one thing, rather than
94
- * restating every colour. Everything a chart draws that is not data — grid,
95
- * ticks, legend, tooltip — is decided here once, which is what makes two charts
96
- * in one application look like they came from the same place.
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
- export function chartOptions(overrides?: ChartOptions): ChartOptions {
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
- responsive: true,
101
- maintainAspectRatio: false,
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
- scales: {
131
- x: {
132
- border: { color: 'var(--chart-grid)' },
133
- grid: { color: 'var(--chart-grid)', drawTicks: false },
134
- ticks: { color: 'var(--chart-axis)', font: { size: 10 } },
135
- },
136
- y: {
137
- border: { display: false },
138
- grid: { color: 'var(--chart-grid)', drawTicks: false },
139
- ticks: { color: 'var(--chart-axis)', font: { size: 10 } },
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(props: SeriesChartProps, extra?: ChartOptions): ChartOptions {
210
- return chartOptions({
211
- ...extra,
212
- ...(props.stacked
213
- ? { scales: { ...extra?.scales, x: { stacked: true }, y: { stacked: true } } }
214
- : {}),
215
- ...props.options,
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={{ labels: [...props.labels], datasets: datasetsFor(props.series, true) }}
309
- options={seriesOptions(props, { scales: { r: { grid: { color: 'var(--chart-grid)' } } } })}
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
- /** A polar area chart: radial bars, for a composition. */
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} />