@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
@@ -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} />
@@ -38,7 +38,17 @@ export function InputOtp(props: InputOtpProps) {
38
38
  return (
39
39
  <OtpField
40
40
  maxLength={length()}
41
- class={cn('flex items-center gap-2', local.class)}
41
+ class={cn(
42
+ 'flex items-center gap-2',
43
+ // corvu positions the field itself, but its hidden input is 40px wider
44
+ // than the field so a password manager's badge lands outside the boxes
45
+ // (`shiftPWManagers`). `clip-path` only hides that overhang; it still
46
+ // counts as overflow, and a full-width field scrolled the page sideways.
47
+ // `overflow-hidden` contains it, and `p-1` keeps the edge boxes' focus
48
+ // ring (`ring-3`) inside the clip.
49
+ 'overflow-hidden p-1',
50
+ local.class
51
+ )}
42
52
  {...rest}
43
53
  children={(field) => (
44
54
  <>
@@ -72,7 +82,9 @@ export function InputOtp(props: InputOtpProps) {
72
82
  'data-[disabled]:cursor-not-allowed data-[disabled]:opacity-50'
73
83
  )}
74
84
  data-active={field.activeSlots.includes(slot)}
75
- data-disabled={props.disabled}
85
+ // Present-or-absent, never `"false"`: `data-[disabled]`
86
+ // matches any value, so `disabled={false}` dimmed the boxes.
87
+ data-disabled={props.disabled ? '' : undefined}
76
88
  >
77
89
  {field.value[slot] ?? ''}
78
90
  </span>
@@ -160,6 +160,14 @@ export function MenubarRadioItem(props: ComponentProps<typeof KobalteMenubar.Rad
160
160
  )
161
161
  }
162
162
 
163
+ /**
164
+ * The caption of a `MenubarGroup`, and that group's accessible name. It must be a
165
+ * child of the group it names: Kobalte reads the group from context, so a label
166
+ * placed directly in the content throws `useMenuGroupContext must be used within
167
+ * a Menu.Group` the moment the menu opens. For an ungrouped caption, wrap the
168
+ * label and its items in a group anyway — the grouping is what a screen reader
169
+ * announces.
170
+ */
163
171
  export function MenubarLabel(props: ComponentProps<typeof KobalteMenubar.GroupLabel>) {
164
172
  const [local, rest] = splitProps(props, ['class'])
165
173
  return <KobalteMenubar.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
@@ -41,9 +41,15 @@ import { menuContentPadding, menuItem, overlayMotion, overlaySurface } from '../
41
41
  * - `Content` must be a direct child of `Menu`, and the `Viewport` is a **sibling
42
42
  * of the menus** — not inside them. All the panels share one viewport, which is
43
43
  * what lets the bar animate between them instead of each panel popping in place.
44
+ * - The viewport is an empty surface. Each `Content` reaches it through Kobalte's
45
+ * `Portal`, which mounts the panel inside the viewport while keeping it in its
46
+ * own `Menu`'s context. A `Content` rendered anywhere else — the viewport's own
47
+ * children included — has no `Menu` above it, and Kobalte throws
48
+ * `useMenuRootContext must be used within a MenuRoot` the moment a panel opens.
44
49
  *
45
- * The wrapper enforces the last of those by rendering the `Viewport` itself, so a
46
- * caller cannot forget it and get a menu whose panels never appear.
50
+ * The wrapper enforces the last two by rendering the `Viewport` itself and by
51
+ * portalling every `NavigationMenuContent`, so a caller cannot forget either and
52
+ * get a menu whose panels never appear.
47
53
  */
48
54
 
49
55
  export type NavigationMenuProps = ComponentProps<typeof KobalteNavigationMenu.Root> & {
@@ -112,25 +118,41 @@ export function NavigationMenuContent(props: ComponentProps<typeof KobalteNaviga
112
118
  const [local, rest] = splitProps(props, ['class', 'children'])
113
119
 
114
120
  return (
115
- <KobalteNavigationMenu.Content
116
- class={cn(
117
- 'grid w-max gap-1 p-2',
118
- // A two-column panel is the shape most menu bars want, and it is opt-in
119
- // through this hook rather than a prop because the column count is a
120
- // layout decision the caller's content makes.
121
- 'data-[wide]:grid-cols-2',
122
- local.class
123
- )}
124
- {...rest}
125
- >
126
- {local.children}
127
- </KobalteNavigationMenu.Content>
121
+ <KobalteNavigationMenu.Portal>
122
+ <KobalteNavigationMenu.Content
123
+ class={cn(
124
+ // Absolute so the viewport sizes to the active panel's measured box and
125
+ // animates between panels, rather than stacking them.
126
+ 'absolute top-0 left-0 grid w-max gap-1 p-2 outline-none',
127
+ // The motion is the panel's, not the viewport's. Kobalte keeps an
128
+ // outgoing panel mounted until the viewport's own animation ends; a
129
+ // viewport that carries an entrance animation never fires that again
130
+ // after it opens, so the previous panel would stay painted under the
131
+ // next one.
132
+ overlayMotion,
133
+ // A two-column panel is the shape most menu bars want, and it is opt-in
134
+ // through this hook rather than a prop because the column count is a
135
+ // layout decision the caller's content makes.
136
+ 'data-[wide]:grid-cols-2',
137
+ local.class
138
+ )}
139
+ {...rest}
140
+ >
141
+ {local.children}
142
+ </KobalteNavigationMenu.Content>
143
+ </KobalteNavigationMenu.Portal>
128
144
  )
129
145
  }
130
146
 
131
147
  /**
132
148
  * The shared panel surface. One per menu bar, and the wrapper supplies it — but
133
149
  * exported so a caller with an unusual layout can place it themselves.
150
+ *
151
+ * It renders no children: the panels arrive through each `NavigationMenuContent`'s
152
+ * portal. Kobalte positions it under the bar and publishes the active panel's size
153
+ * as `--kb-navigation-menu-viewport-*`, which the surface animates between.
154
+ * `box-content` keeps the border outside that measured size so a panel is never
155
+ * clipped by its own frame.
134
156
  */
135
157
  export function NavigationMenuViewport(
136
158
  props: ComponentProps<typeof KobalteNavigationMenu.Viewport>
@@ -139,24 +161,16 @@ export function NavigationMenuViewport(
139
161
 
140
162
  return (
141
163
  <KobalteNavigationMenu.Viewport
164
+ data-slot="navigation-menu-viewport"
142
165
  class={cn(
143
- 'absolute top-full left-0 isolate z-(--z-menu) mt-1.5 flex justify-center overflow-hidden',
166
+ overlaySurface,
167
+ 'isolate z-(--z-menu) mt-1.5 box-content list-none overflow-hidden',
144
168
  'h-(--kb-navigation-menu-viewport-height) w-(--kb-navigation-menu-viewport-width)',
145
- 'origin-top transition-[width,height] duration-200 ease-out',
169
+ 'origin-(--kb-menu-content-transform-origin) transition-[width,height] duration-200 ease-out',
146
170
  local.class
147
171
  )}
148
172
  {...rest}
149
- >
150
- <div
151
- class={cn(
152
- overlaySurface,
153
- overlayMotion,
154
- 'w-full rounded-lg border border-border bg-popover p-1 shadow-lg'
155
- )}
156
- >
157
- <KobalteNavigationMenu.Content />
158
- </div>
159
- </KobalteNavigationMenu.Viewport>
173
+ />
160
174
  )
161
175
  }
162
176
 
@@ -55,7 +55,14 @@ export function Pagination(props: PaginationProps) {
55
55
  ((itemProps) => <PaginationItem page={itemProps.page}>{itemProps.page}</PaginationItem>)
56
56
  }
57
57
  ellipsisComponent={local.ellipsisComponent ?? (() => <PaginationEllipsis />)}
58
- class={cn('flex w-full items-center justify-center gap-1', local.class)}
58
+ // Kobalte renders the root as a `<nav>` wrapping a `<ul>`, and every control —
59
+ // previous, the page run, next — is an `<li>` inside that list. The row has to be
60
+ // declared on the list: on the `<nav>` alone it lays out one child and the
61
+ // controls stack vertically.
62
+ class={cn(
63
+ 'flex w-full items-center justify-center [&>ul]:m-0 [&>ul]:flex [&>ul]:list-none [&>ul]:items-center [&>ul]:gap-1 [&>ul]:p-0',
64
+ local.class
65
+ )}
59
66
  {...rest}
60
67
  >
61
68
  <Show when={local.children} fallback={<DefaultPaginationControls />}>