@adea-ai/ui 0.100.2 → 0.101.1

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 (132) hide show
  1. package/dist/components/composites/about-dialog/about-dialog.d.ts.map +1 -1
  2. package/dist/components/composites/about-dialog/about-dialog.js +2 -1
  3. package/dist/components/composites/about-dialog/about-dialog.js.map +1 -1
  4. package/dist/components/composites/account-menu/account-menu.d.ts +7 -0
  5. package/dist/components/composites/account-menu/account-menu.d.ts.map +1 -1
  6. package/dist/components/composites/account-menu/account-menu.js +116 -89
  7. package/dist/components/composites/account-menu/account-menu.js.map +1 -1
  8. package/dist/components/composites/help-center/help-center.d.ts.map +1 -1
  9. package/dist/components/composites/help-center/help-center.js +26 -21
  10. package/dist/components/composites/help-center/help-center.js.map +1 -1
  11. package/dist/components/composites/settings/settings.d.ts.map +1 -1
  12. package/dist/components/composites/settings/settings.js +43 -36
  13. package/dist/components/composites/settings/settings.js.map +1 -1
  14. package/dist/components/composites/stat/stat.d.ts.map +1 -1
  15. package/dist/components/composites/stat/stat.js +46 -35
  16. package/dist/components/composites/stat/stat.js.map +1 -1
  17. package/dist/components/composites/update-dialog/update-dialog.d.ts.map +1 -1
  18. package/dist/components/composites/update-dialog/update-dialog.js +73 -58
  19. package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
  20. package/dist/components/layout/page/page.d.ts.map +1 -1
  21. package/dist/components/layout/page/page.js +19 -13
  22. package/dist/components/layout/page/page.js.map +1 -1
  23. package/dist/components/theme/appearance-panel.d.ts.map +1 -1
  24. package/dist/components/theme/appearance-panel.js +37 -18
  25. package/dist/components/theme/appearance-panel.js.map +1 -1
  26. package/dist/components/ui/alert-dialog/alert-dialog.d.ts.map +1 -1
  27. package/dist/components/ui/alert-dialog/alert-dialog.js +5 -1
  28. package/dist/components/ui/alert-dialog/alert-dialog.js.map +1 -1
  29. package/dist/components/ui/card/card.d.ts.map +1 -1
  30. package/dist/components/ui/card/card.js +5 -1
  31. package/dist/components/ui/card/card.js.map +1 -1
  32. package/dist/components/ui/chart/chart-options.d.ts +3 -2
  33. package/dist/components/ui/chart/chart-options.d.ts.map +1 -1
  34. package/dist/components/ui/chart/chart-options.js +30 -9
  35. package/dist/components/ui/chart/chart-options.js.map +1 -1
  36. package/dist/components/ui/chart/chart.d.ts +4 -9
  37. package/dist/components/ui/chart/chart.d.ts.map +1 -1
  38. package/dist/components/ui/chart/chart.js +65 -10
  39. package/dist/components/ui/chart/chart.js.map +1 -1
  40. package/dist/components/ui/dialog/dialog.d.ts.map +1 -1
  41. package/dist/components/ui/dialog/dialog.js +5 -1
  42. package/dist/components/ui/dialog/dialog.js.map +1 -1
  43. package/dist/components/ui/drawer/drawer.d.ts.map +1 -1
  44. package/dist/components/ui/drawer/drawer.js +5 -1
  45. package/dist/components/ui/drawer/drawer.js.map +1 -1
  46. package/dist/components/ui/field/field.d.ts.map +1 -1
  47. package/dist/components/ui/field/field.js +2 -1
  48. package/dist/components/ui/field/field.js.map +1 -1
  49. package/dist/components/ui/item/item.d.ts +1 -1
  50. package/dist/components/ui/navigation-menu/navigation-menu.d.ts.map +1 -1
  51. package/dist/components/ui/navigation-menu/navigation-menu.js +8 -7
  52. package/dist/components/ui/navigation-menu/navigation-menu.js.map +1 -1
  53. package/dist/components/ui/sheet/sheet.d.ts.map +1 -1
  54. package/dist/components/ui/sheet/sheet.js +5 -1
  55. package/dist/components/ui/sheet/sheet.js.map +1 -1
  56. package/dist/components/ui/tabs/tabs.d.ts +13 -0
  57. package/dist/components/ui/tabs/tabs.d.ts.map +1 -1
  58. package/dist/components/ui/tabs/tabs.js +15 -1
  59. package/dist/components/ui/tabs/tabs.js.map +1 -1
  60. package/dist/components/ui/toast/toast.d.ts.map +1 -1
  61. package/dist/components/ui/toast/toast.js +6 -2
  62. package/dist/components/ui/toast/toast.js.map +1 -1
  63. package/dist/components/ui/typography/index.d.ts +3 -0
  64. package/dist/components/ui/typography/index.d.ts.map +1 -0
  65. package/dist/components/ui/typography/index.js +2 -0
  66. package/dist/components/ui/typography/typography.d.ts +84 -0
  67. package/dist/components/ui/typography/typography.d.ts.map +1 -0
  68. package/dist/components/ui/typography/typography.js +179 -0
  69. package/dist/components/ui/typography/typography.js.map +1 -0
  70. package/dist/index.d.ts +1 -0
  71. package/dist/index.d.ts.map +1 -1
  72. package/dist/index.js +2 -1
  73. package/dist/r/account-menu.json +1 -0
  74. package/dist/r/alert-dialog.json +1 -0
  75. package/dist/r/card.json +1 -0
  76. package/dist/r/dialog.json +1 -0
  77. package/dist/r/drawer.json +1 -0
  78. package/dist/r/field.json +1 -0
  79. package/dist/r/help-center.json +1 -0
  80. package/dist/r/page.json +1 -0
  81. package/dist/r/registry.json +37 -0
  82. package/dist/r/settings.json +1 -0
  83. package/dist/r/sheet.json +1 -0
  84. package/dist/r/src/components/composites/about-dialog/about-dialog.tsx +3 -2
  85. package/dist/r/src/components/composites/account-menu/account-menu.tsx +105 -66
  86. package/dist/r/src/components/composites/help-center/help-center.tsx +2 -1
  87. package/dist/r/src/components/composites/settings/settings.tsx +4 -1
  88. package/dist/r/src/components/composites/stat/stat.tsx +7 -2
  89. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +36 -24
  90. package/dist/r/src/components/layout/page/page.tsx +3 -2
  91. package/dist/r/src/components/theme/appearance-panel.tsx +13 -4
  92. package/dist/r/src/components/ui/alert-dialog/alert-dialog.tsx +2 -1
  93. package/dist/r/src/components/ui/card/card.tsx +2 -1
  94. package/dist/r/src/components/ui/chart/chart-options.ts +35 -7
  95. package/dist/r/src/components/ui/chart/chart.tsx +65 -12
  96. package/dist/r/src/components/ui/dialog/dialog.tsx +2 -1
  97. package/dist/r/src/components/ui/drawer/drawer.tsx +2 -1
  98. package/dist/r/src/components/ui/field/field.tsx +2 -3
  99. package/dist/r/src/components/ui/navigation-menu/navigation-menu.tsx +27 -20
  100. package/dist/r/src/components/ui/sheet/sheet.tsx +5 -1
  101. package/dist/r/src/components/ui/tabs/tabs.tsx +18 -1
  102. package/dist/r/src/components/ui/toast/toast.tsx +7 -6
  103. package/dist/r/src/components/ui/typography/index.ts +9 -0
  104. package/dist/r/src/components/ui/typography/typography.tsx +205 -0
  105. package/dist/r/stat.json +1 -0
  106. package/dist/r/theming.json +1 -0
  107. package/dist/r/typography.json +24 -0
  108. package/dist/r/update-dialog.json +1 -0
  109. package/package.json +1 -1
  110. package/registry.json +37 -0
  111. package/src/components/composites/about-dialog/about-dialog.tsx +3 -2
  112. package/src/components/composites/account-menu/account-menu.tsx +105 -66
  113. package/src/components/composites/help-center/help-center.tsx +2 -1
  114. package/src/components/composites/settings/settings.tsx +4 -1
  115. package/src/components/composites/stat/stat.tsx +7 -2
  116. package/src/components/composites/update-dialog/update-dialog.tsx +36 -24
  117. package/src/components/layout/page/page.tsx +3 -2
  118. package/src/components/theme/appearance-panel.tsx +13 -4
  119. package/src/components/ui/alert-dialog/alert-dialog.tsx +2 -1
  120. package/src/components/ui/card/card.tsx +2 -1
  121. package/src/components/ui/chart/chart-options.ts +35 -7
  122. package/src/components/ui/chart/chart.tsx +65 -12
  123. package/src/components/ui/dialog/dialog.tsx +2 -1
  124. package/src/components/ui/drawer/drawer.tsx +2 -1
  125. package/src/components/ui/field/field.tsx +2 -3
  126. package/src/components/ui/navigation-menu/navigation-menu.tsx +27 -20
  127. package/src/components/ui/sheet/sheet.tsx +5 -1
  128. package/src/components/ui/tabs/tabs.tsx +18 -1
  129. package/src/components/ui/toast/toast.tsx +7 -6
  130. package/src/components/ui/typography/index.ts +9 -0
  131. package/src/components/ui/typography/typography.tsx +205 -0
  132. package/src/index.ts +1 -0
@@ -45,7 +45,30 @@ const furniture = {
45
45
  */
46
46
  export type ChartScaleShape = 'cartesian' | 'radial' | 'none'
47
47
 
48
- const tickFont = { size: 10 }
48
+ /**
49
+ * The chart's type: the UI face from the font axis and a rung of the type scale,
50
+ * declared as tokens like the colours.
51
+ *
52
+ * Chart.js does not inherit the page's font — it paints in its own default
53
+ * family — so every text a chart draws names `--font-sans` explicitly, and the
54
+ * chart follows `data-font` the same way it follows `.dark`. Everything is
55
+ * `--text-2xs`, the rung `ChartLegend` and the frame's metadata already use, so
56
+ * a canvas legend and an HTML one beside it are the same size.
57
+ *
58
+ * Chart.js types `size` as a number of pixels. The token is a `rem` length, so
59
+ * it is written as `var(--text-2xs)` here and the component resolves it to
60
+ * pixels with the colours (see `resolveCssVariables` and `chart.tsx`); the cast
61
+ * is that declaration, not a number.
62
+ */
63
+ function chartFont(weight?: number) {
64
+ return {
65
+ family: 'var(--font-sans)',
66
+ size: 'var(--text-2xs)' as unknown as number,
67
+ ...(weight === undefined ? {} : { weight }),
68
+ }
69
+ }
70
+
71
+ const tickFont = chartFont()
49
72
 
50
73
  function scalesFor(shape: ChartScaleShape): ChartOptions['scales'] {
51
74
  if (shape === 'cartesian') {
@@ -140,7 +163,7 @@ export function chartOptions(
140
163
  boxHeight: 8,
141
164
  usePointStyle: true,
142
165
  pointStyle: 'circle',
143
- font: { size: 11 },
166
+ font: chartFont(),
144
167
  },
145
168
  },
146
169
  tooltip: {
@@ -155,8 +178,9 @@ export function chartOptions(
155
178
  boxWidth: 8,
156
179
  boxHeight: 8,
157
180
  usePointStyle: true,
158
- titleFont: { size: 11, weight: 600 },
159
- bodyFont: { size: 11 },
181
+ titleFont: chartFont(600),
182
+ bodyFont: chartFont(),
183
+ footerFont: chartFont(),
160
184
  },
161
185
  },
162
186
  scales: scalesFor(shape),
@@ -169,17 +193,21 @@ const cssVariable = /^\s*var\(\s*(--[\w-]+)\s*(?:,\s*(.*?))?\s*\)\s*$/
169
193
 
170
194
  /**
171
195
  * Replace every `var(--token)` string in `value` — options, datasets, nested
172
- * arrays — with what `read` returns for the token. A token `read` cannot
196
+ * arrays — with what `read` returns for the token, which may be a number (a
197
+ * length token read as pixels). A token `read` cannot
173
198
  * resolve falls back to the `var()`'s own fallback, then to the string as
174
199
  * written. Functions and class instances pass through untouched, so scriptable
175
200
  * options and gradients keep working.
176
201
  */
177
- export function resolveCssVariables<T>(value: T, read: (name: string) => string | undefined): T {
202
+ export function resolveCssVariables<T>(
203
+ value: T,
204
+ read: (name: string) => string | number | undefined
205
+ ): T {
178
206
  if (typeof value === 'string') {
179
207
  const match = cssVariable.exec(value)
180
208
  if (!match) return value
181
209
  const resolved = read(match[1]!)
182
- if (resolved) return resolved as T
210
+ if (resolved !== undefined && resolved !== '') return resolved as T
183
211
  return (match[2] ? resolveCssVariables(match[2], read) : value) as T
184
212
  }
185
213
  if (Array.isArray(value)) {
@@ -15,7 +15,7 @@ import {
15
15
  RadialLinearScale,
16
16
  Tooltip as ChartTooltip,
17
17
  } from 'chart.js'
18
- import type { ChartOptions, ChartType } from 'chart.js'
18
+ import type { ChartOptions, ChartType, Plugin } from 'chart.js'
19
19
  import { cn } from '#lib/utils'
20
20
  import {
21
21
  chartOptions,
@@ -62,7 +62,9 @@ import {
62
62
  * A canvas cannot read a custom property, so the options and datasets carry
63
63
  * `var(--token)` strings and each chart resolves them against its own element
64
64
  * when it renders, and again whenever the root's attributes change — the
65
- * `.dark` class, `data-accent`, an inline override. See `createThemeResolver`.
65
+ * `.dark` class, `data-accent`, `data-font`, an inline override. The type is
66
+ * resolved the same way: `--font-sans` for the family and a type-scale rung for
67
+ * the size, so a chart follows the font axis too. See `createThemeResolver`.
66
68
  */
67
69
 
68
70
  /**
@@ -119,6 +121,23 @@ function toCanvasColor(value: string): string {
119
121
  return `rgba(${r}, ${g}, ${b}, ${Math.round(((a ?? 255) / 255) * 1000) / 1000})`
120
122
  }
121
123
 
124
+ const length = /^(-?\d*\.?\d+)(px|rem)$/
125
+
126
+ /**
127
+ * A token's computed value as a canvas option: a colour as an sRGB colour, a
128
+ * `px`/`rem` length (a type-scale rung) as the number of pixels Chart.js wants,
129
+ * and anything else — a font stack — as written.
130
+ */
131
+ function toCanvasValue(raw: string): string | number {
132
+ const match = length.exec(raw)
133
+ if (match) {
134
+ const value = Number(match[1])
135
+ if (match[2] === 'px') return value
136
+ return value * (Number.parseFloat(getComputedStyle(document.documentElement).fontSize) || 16)
137
+ }
138
+ return toCanvasColor(raw)
139
+ }
140
+
122
141
  /**
123
142
  * Resolves `var(--token)` strings against a chart's element, and re-resolves
124
143
  * them when the theme changes.
@@ -127,16 +146,25 @@ function toCanvasColor(value: string): string {
127
146
  * inline tokens takes those. The observer watches every attribute on `<html>`,
128
147
  * because that is where both applications switch appearance, accent and theme
129
148
  * (`.dark`, `data-accent`, `data-theme`, inline variables); a theme axis added
130
- * later is covered without touching this. It is released with the chart.
149
+ * later is covered without touching this. It also re-resolves when a web font
150
+ * finishes loading: a canvas draws text once, so a chart rendered before the
151
+ * face selected by `data-font` arrived would otherwise keep the fallback face
152
+ * until something else redrew it. Both are released with the chart.
131
153
  */
132
154
  function createThemeResolver() {
133
155
  let element: HTMLElement | undefined
134
156
  const [revision, invalidate] = createSignal(undefined, { equals: false })
157
+ const onFontsLoaded = () => invalidate()
135
158
 
136
159
  onMount(() => {
137
160
  const observer = new MutationObserver(() => invalidate())
138
161
  observer.observe(document.documentElement, { attributes: true })
139
- onCleanup(() => observer.disconnect())
162
+ const fonts = typeof document.fonts === 'undefined' ? undefined : document.fonts
163
+ fonts?.addEventListener('loadingdone', onFontsLoaded)
164
+ onCleanup(() => {
165
+ observer.disconnect()
166
+ fonts?.removeEventListener('loadingdone', onFontsLoaded)
167
+ })
140
168
  })
141
169
 
142
170
  return {
@@ -147,11 +175,11 @@ function createThemeResolver() {
147
175
  revision()
148
176
  if (!element || typeof getComputedStyle === 'undefined') return value
149
177
  const style = getComputedStyle(element)
150
- const resolved = new Map<string, string | undefined>()
178
+ const resolved = new Map<string, string | number | undefined>()
151
179
  return resolveCssVariables(value, (name) => {
152
180
  if (!resolved.has(name)) {
153
181
  const raw = style.getPropertyValue(name).trim()
154
- resolved.set(name, raw ? toCanvasColor(raw) : undefined)
182
+ resolved.set(name, raw ? toCanvasValue(raw) : undefined)
155
183
  }
156
184
  return resolved.get(name)
157
185
  })
@@ -343,13 +371,36 @@ export function RadarChart(props: SeriesChartProps) {
343
371
  }
344
372
 
345
373
  /**
346
- * A polar area chart: radial bars, for a composition.
374
+ * Puts the themed radial scale back on a chart `solid-chartjs` has just created.
347
375
  *
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.
376
+ * `solid-chartjs` deletes `options.scales.r` for every chart type but radar when
377
+ * it first creates a chart. Today that delete lands on a throwaway copy — Solid
378
+ * compiles `options={…}` to a getter, so its next read rebuilds the object with
379
+ * `r` intact — but that is an accident of how the prop is passed: a memoised or
380
+ * static options object would lose the scale, and the polar area would fall back
381
+ * to Chart.js's defaults (grey ticks on a translucent white box, which is a white
382
+ * box on a dark card). Restoring it from this component's own options makes the
383
+ * themed scale independent of both. Later option updates (every theme change)
384
+ * pass `r` through untouched; only creation drops it.
385
+ *
386
+ * `afterInit` runs inside Chart.js's constructor, after the options are read and
387
+ * before the first update builds the scales and draws, so the themed scale is in
388
+ * place for the first frame — no default-styled frame, and no extra update. The
389
+ * plugin is per instance, passed through the chart's `plugins` prop, so nothing
390
+ * is registered globally.
352
391
  */
392
+ function restoreRadialScale(options: () => ChartOptions): Plugin {
393
+ return {
394
+ id: 'adea-radial-scale',
395
+ afterInit(chart) {
396
+ const scale = options().scales?.['r']
397
+ if (!scale) return
398
+ chart.options.scales = { ...chart.options.scales, r: scale }
399
+ },
400
+ }
401
+ }
402
+
403
+ /** A polar area chart: radial bars, for a composition. */
353
404
  export function PolarAreaChart(props: {
354
405
  labels: readonly string[]
355
406
  data: readonly number[]
@@ -357,9 +408,11 @@ export function PolarAreaChart(props: {
357
408
  class?: string
358
409
  }) {
359
410
  const theme = createThemeResolver()
411
+ const options = () => theme.resolve(chartOptions(props.options, 'radial'))
360
412
  return (
361
413
  <div ref={theme.ref} class={cn('h-64', props.class)}>
362
414
  <PolarArea
415
+ plugins={[restoreRadialScale(options)]}
363
416
  data={theme.resolve({
364
417
  labels: [...props.labels],
365
418
  datasets: [
@@ -371,7 +424,7 @@ export function PolarAreaChart(props: {
371
424
  },
372
425
  ],
373
426
  })}
374
- options={theme.resolve(chartOptions(props.options, 'none'))}
427
+ options={options()}
375
428
  />
376
429
  </div>
377
430
  )
@@ -9,6 +9,7 @@ import type { Accessor, ComponentProps, JSX } from 'solid-js'
9
9
  import { createRenderEffect, onCleanup, Show, splitProps } from 'solid-js'
10
10
  import { overlayMotion, overlayPositioner, overlayScrim, overlaySurface } from '#lib/overlay'
11
11
  import { cn } from '#lib/utils'
12
+ import { headingVariants } from '../typography'
12
13
 
13
14
  type FocusCycle = {
14
15
  content: HTMLElement
@@ -327,7 +328,7 @@ export function DialogTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
327
328
  const [local, rest] = splitProps(props, ['class'])
328
329
  return (
329
330
  <KobalteDialog.Title
330
- class={cn('text-base leading-none font-semibold tracking-tight', local.class)}
331
+ class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
331
332
  {...rest}
332
333
  />
333
334
  )
@@ -2,6 +2,7 @@ import DrawerPrimitive from '@corvu/drawer'
2
2
  import type { ComponentProps } from 'solid-js'
3
3
  import { splitProps } from 'solid-js'
4
4
  import { cn } from '#lib/utils'
5
+ import { headingVariants } from '../typography'
5
6
 
6
7
  /**
7
8
  * Drawer.
@@ -96,7 +97,7 @@ export function DrawerTitle(props: ComponentProps<typeof DrawerPrimitive.Label>)
96
97
  const [local, rest] = splitProps(props, ['class'])
97
98
  return (
98
99
  <DrawerPrimitive.Label
99
- class={cn('text-base leading-none font-semibold tracking-tight', local.class)}
100
+ class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
100
101
  {...rest}
101
102
  />
102
103
  )
@@ -4,6 +4,7 @@ import { createUniqueId, Show, splitProps } from 'solid-js'
4
4
  import { Label } from '../label/label'
5
5
  import { createFormFieldContext, FormFieldContext } from '#lib/form-field'
6
6
  import { cn } from '#lib/utils'
7
+ import { headingVariants } from '../typography'
7
8
 
8
9
  /**
9
10
  * Field.
@@ -199,9 +200,7 @@ export function FieldLegend(props: ComponentProps<'legend'> & { variant?: 'legen
199
200
  <legend
200
201
  class={cn(
201
202
  'mb-1',
202
- local.variant === 'label'
203
- ? 'text-sm font-medium'
204
- : 'text-base font-semibold tracking-tight',
203
+ local.variant === 'label' ? 'text-sm font-medium' : headingVariants({ size: 'card' }),
205
204
  local.class
206
205
  )}
207
206
  {...rest}
@@ -2,7 +2,14 @@ import { splitProps, type ComponentProps, type JSX } from 'solid-js'
2
2
  import * as KobalteNavigationMenu from '@kobalte/core/navigation-menu'
3
3
  import { ChevronDown } from 'lucide-solid'
4
4
  import { cn } from '#lib/utils'
5
- import { menuContentPadding, menuItem, overlayMotion, overlaySurface } from '#lib/overlay'
5
+ import {
6
+ menuContentPadding,
7
+ menuItem,
8
+ menuLabel,
9
+ menuSeparator,
10
+ overlayMotion,
11
+ overlaySurface,
12
+ } from '#lib/overlay'
6
13
 
7
14
  /**
8
15
  * NavigationMenu.
@@ -123,7 +130,11 @@ export function NavigationMenuContent(props: ComponentProps<typeof KobalteNaviga
123
130
  class={cn(
124
131
  // Absolute so the viewport sizes to the active panel's measured box and
125
132
  // animates between panels, rather than stacking them.
126
- 'absolute top-0 left-0 grid w-max gap-1 p-2 outline-none',
133
+ 'absolute top-0 left-0 grid w-max gap-1 outline-none',
134
+ // The viewport is the surface and carries no padding of its own (its
135
+ // measured size is the panel's), so the panel supplies the inset every
136
+ // menu surface shares — the one the separator's bleed is cut for.
137
+ menuContentPadding,
127
138
  // The motion is the panel's, not the viewport's. Kobalte keeps an
128
139
  // outgoing panel mounted until the viewport's own animation ends; a
129
140
  // viewport that carries an entrance animation never fires that again
@@ -193,7 +204,11 @@ export function NavigationMenuItem(props: NavigationMenuItemProps) {
193
204
  <KobalteNavigationMenu.Item
194
205
  class={cn(
195
206
  menuItem,
196
- 'grid grid-cols-[auto_1fr] items-center gap-x-2 gap-y-0.5',
207
+ // The description sits under the label, in the label's column. Without
208
+ // an icon there is no leading column, and a two-column grid would set
209
+ // the description beside the label instead of beneath it.
210
+ local.icon ? 'grid grid-cols-[auto_1fr]' : 'grid grid-cols-1',
211
+ 'items-center gap-x-2 gap-y-0.5',
197
212
  local.description && 'grid-rows-[auto_auto] py-2',
198
213
  local.class
199
214
  )}
@@ -202,7 +217,13 @@ export function NavigationMenuItem(props: NavigationMenuItemProps) {
202
217
  {local.icon}
203
218
  <span class="font-medium">{local.children}</span>
204
219
  {local.description && (
205
- <span class="col-start-2 text-xs text-muted-foreground">{local.description}</span>
220
+ <span
221
+ class={cn('text-xs text-muted-foreground', {
222
+ 'col-start-2': !!local.icon,
223
+ })}
224
+ >
225
+ {local.description}
226
+ </span>
206
227
  )}
207
228
  </KobalteNavigationMenu.Item>
208
229
  )
@@ -212,12 +233,7 @@ export function NavigationMenuSeparator(
212
233
  props: ComponentProps<typeof KobalteNavigationMenu.Separator>
213
234
  ) {
214
235
  const [local, rest] = splitProps(props, ['class'])
215
- return (
216
- <KobalteNavigationMenu.Separator
217
- class={cn('-mx-1 my-1 h-px bg-border', local.class)}
218
- {...rest}
219
- />
220
- )
236
+ return <KobalteNavigationMenu.Separator class={cn(menuSeparator, local.class)} {...rest} />
221
237
  }
222
238
 
223
239
  /** A non-interactive caption above a group of items. */
@@ -225,16 +241,7 @@ export function NavigationMenuGroupLabel(
225
241
  props: ComponentProps<typeof KobalteNavigationMenu.GroupLabel>
226
242
  ) {
227
243
  const [local, rest] = splitProps(props, ['class'])
228
- return (
229
- <KobalteNavigationMenu.GroupLabel
230
- class={cn(
231
- menuContentPadding,
232
- 'text-2xs font-semibold text-muted-foreground uppercase',
233
- local.class
234
- )}
235
- {...rest}
236
- />
237
- )
244
+ return <KobalteNavigationMenu.GroupLabel class={cn(menuLabel, local.class)} {...rest} />
238
245
  }
239
246
 
240
247
  export function NavigationMenuGroup(props: ComponentProps<typeof KobalteNavigationMenu.Group>) {
@@ -10,6 +10,7 @@ import { Show, splitProps } from 'solid-js'
10
10
  import { cva, type VariantProps } from '#lib/variants'
11
11
  import { cn } from '#lib/utils'
12
12
  import { createDialogFocusRestoration, DialogOverlay } from '../dialog/dialog'
13
+ import { headingVariants } from '../typography'
13
14
 
14
15
  /**
15
16
  * Sheet.
@@ -152,7 +153,10 @@ export function SheetFooter(props: ComponentProps<'div'>) {
152
153
  export function SheetTitle(props: ComponentProps<typeof KobalteDialog.Title>) {
153
154
  const [local, rest] = splitProps(props, ['class'])
154
155
  return (
155
- <KobalteDialog.Title class={cn('text-sm leading-none font-semibold', local.class)} {...rest} />
156
+ <KobalteDialog.Title
157
+ class={cn(headingVariants({ size: 'subsection', leading: 'none' }), local.class)}
158
+ {...rest}
159
+ />
156
160
  )
157
161
  }
158
162
 
@@ -13,6 +13,7 @@ import { cn } from '#lib/utils'
13
13
 
14
14
  type TabsIdContextValue = {
15
15
  id: Accessor<string>
16
+ orientation: Accessor<'horizontal' | 'vertical'>
16
17
  triggerIds: Accessor<ReadonlyMap<string, string>>
17
18
  registerTriggerId: (value: string, id: string) => void
18
19
  unregisterTriggerId: (value: string, id: string) => void
@@ -43,6 +44,7 @@ export function Tabs(props: ComponentProps<typeof KobalteTabs>) {
43
44
  const [triggerIds, setTriggerIds] = createSignal<ReadonlyMap<string, string>>(new Map())
44
45
  const tabs: TabsIdContextValue = {
45
46
  id,
47
+ orientation: () => rest.orientation ?? 'horizontal',
46
48
  triggerIds,
47
49
  registerTriggerId: (value, triggerId) => {
48
50
  setTriggerIds((current) => new Map(current).set(value, triggerId))
@@ -103,6 +105,19 @@ export type TabsTriggerProps = ComponentProps<typeof KobalteTabs.Trigger> & {
103
105
  appearance?: 'underline' | 'segmented'
104
106
  }
105
107
 
108
+ /**
109
+ * The underline appearance marks the selected trigger on the edge it shares
110
+ * with the list's axis. A horizontal row reads left to right along a baseline,
111
+ * so the mark is a bottom bar sitting on the list's rule. A vertical list stacks
112
+ * its triggers, and a bottom bar there underlines one row of a column — it reads
113
+ * as a divider, not a selection — so the mark moves to the inline-start edge,
114
+ * the leading bar a side navigation uses, and the label aligns to that edge.
115
+ *
116
+ * The orientation is read from the root rather than from a `data-orientation`
117
+ * selector so the vertical classes are unprefixed: a composite that replaces the
118
+ * mark (SettingsNavigation's filled row passes `border-0`) then overrides it
119
+ * through the ordinary class merge instead of fighting a variant-scoped border.
120
+ */
106
121
  export function TabsTrigger(props: TabsTriggerProps) {
107
122
  const [local, rest] = splitProps(props, ['class', 'appearance', 'id'])
108
123
  const tabs = useTabsId()
@@ -128,7 +143,9 @@ export function TabsTrigger(props: TabsTriggerProps) {
128
143
  '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg]:size-4',
129
144
  local.appearance === 'segmented'
130
145
  ? 'h-control-sm rounded-md px-control-sm data-[selected]:bg-card data-[selected]:shadow-xs'
131
- : 'h-control-md -mb-px rounded-none border-b-2 border-transparent px-2 pb-2 data-[selected]:border-primary',
146
+ : tabs.orientation() === 'vertical'
147
+ ? 'h-control-md justify-start rounded-none border-s-2 border-transparent ps-3 pe-2 data-[selected]:border-primary'
148
+ : 'h-control-md -mb-px rounded-none border-b-2 border-transparent px-2 pb-2 data-[selected]:border-primary',
132
149
  local.class
133
150
  )}
134
151
  {...rest}
@@ -180,6 +180,10 @@ export type ToasterProps = ComponentProps<typeof KobalteToast.Region> & {
180
180
  }
181
181
 
182
182
  /**
183
+ * `class` lands on the region too: the props describe the region, and the
184
+ * region is the element a caller lays out (an inset clear of a status bar, a
185
+ * narrower stack). The list inside it is an implementation detail.
186
+ *
183
187
  * The corner is set on the region, not the list. The region is the `fixed`
184
188
  * element: an inset on the list inside it positions nothing, and the region falls
185
189
  * back to wherever it happens to sit in the document flow.
@@ -217,16 +221,13 @@ export function Toaster(props: ToasterProps) {
217
221
  */
218
222
  class={cn(
219
223
  'pointer-events-none fixed z-(--z-toast) flex max-h-screen w-full flex-col gap-2 p-4 sm:max-w-sm',
220
- toasterPosition[local.position ?? 'bottom-right']
224
+ toasterPosition[local.position ?? 'bottom-right'],
225
+ local.class
221
226
  )}
222
227
  {...rest}
223
228
  >
224
229
  <KobalteToast.List
225
- class={cn(
226
- 'flex flex-col gap-2',
227
- toasterAlignment[local.position ?? 'bottom-right'],
228
- local.class
229
- )}
230
+ class={cn('flex flex-col gap-2', toasterAlignment[local.position ?? 'bottom-right'])}
230
231
  />
231
232
  </KobalteToast.Region>
232
233
  )
@@ -0,0 +1,9 @@
1
+ export {
2
+ Heading,
3
+ headingElement,
4
+ headingVariants,
5
+ Text,
6
+ textElement,
7
+ textVariants,
8
+ } from './typography'
9
+ export type { HeadingProps, TextProps } from './typography'