@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
@@ -13,6 +13,7 @@ import type { ComponentProps, JSX } from 'solid-js'
13
13
  import { For, Show, createSignal, onCleanup, splitProps } from 'solid-js'
14
14
  import { cn } from '../../../lib/utils'
15
15
  import { Button, type ButtonProps } from '../../ui/button/button'
16
+ import { SideRailButton } from '../../layout/side-rail/side-rail'
16
17
  import {
17
18
  DropdownMenu,
18
19
  DropdownMenuContent,
@@ -70,6 +71,13 @@ export type AccountMenuProps = {
70
71
  hideArrow?: boolean
71
72
  /** The trigger size. Icon-only menus default to the standard icon size. */
72
73
  size?: ButtonProps['size']
74
+ /**
75
+ * Render the trigger as a `SideRailButton` row instead of a bare icon
76
+ * button: same geometry, hover treatment, and flush collapsed-rail tip as
77
+ * every other entry in the surrounding rail. The component's own tooltip
78
+ * is skipped — the rail row owns its help.
79
+ */
80
+ railTrigger?: boolean
73
81
  /** The entries above the separator. */
74
82
  items: readonly AccountMenuItem[]
75
83
  /** Which platform this is rendered on, for items that declare one. */
@@ -104,6 +112,7 @@ export function AccountMenu(props: AccountMenuProps) {
104
112
  'gutter',
105
113
  'hideArrow',
106
114
  'size',
115
+ 'railTrigger',
107
116
  'items',
108
117
  'platform',
109
118
  'authenticated',
@@ -133,83 +142,113 @@ export function AccountMenu(props: AccountMenuProps) {
133
142
  const visible = () =>
134
143
  local.items.filter((item) => !item.platform || item.platform === (local.platform ?? 'desktop'))
135
144
 
136
- return (
137
- <Tooltip open={tooltipOpen()} onOpenChange={(open) => setTooltipOpen(open && !menuOpen())}>
138
- <DropdownMenu
139
- modal={false}
140
- placement={local.placement ?? 'top-start'}
141
- gutter={local.gutter}
142
- onOpenChange={(open) => {
143
- setMenuOpen(open)
144
- if (open) setTooltipOpen(false)
145
- }}
145
+ // Built by reference at each return, not once up front: Solid fixes a node's
146
+ // owner chain when the JSX expression runs, and an eagerly created tree would
147
+ // never see the Tooltip provider below — Kobalte's TooltipTrigger then throws
148
+ // `useTooltipContext must be used within a Tooltip component`.
149
+ const menu = () => (
150
+ <DropdownMenu
151
+ modal={false}
152
+ placement={local.placement ?? 'top-start'}
153
+ gutter={local.gutter}
154
+ onOpenChange={(open) => {
155
+ setMenuOpen(open)
156
+ if (open) setTooltipOpen(false)
157
+ }}
158
+ >
159
+ <Show
160
+ when={local.railTrigger}
161
+ fallback={
162
+ <TooltipTrigger
163
+ as={AccountMenuButton}
164
+ ref={(element: HTMLButtonElement) => (trigger = element)}
165
+ variant="ghost"
166
+ size={local.size ?? 'icon-md'}
167
+ class={cn('text-muted-foreground', local.class)}
168
+ aria-label={local.label ?? 'Account and settings'}
169
+ onFocus={() => local.onIntent?.()}
170
+ onPointerEnter={() => local.onIntent?.()}
171
+ {...rest}
172
+ >
173
+ <UserRound aria-hidden="true" />
174
+ </TooltipTrigger>
175
+ }
146
176
  >
147
- <TooltipTrigger
148
- as={AccountMenuButton}
177
+ {/* Rail parity: the trigger is a SideRailButton row — the same
178
+ geometry and flush collapsed-rail tip as every other rail entry —
179
+ so the composite's own tooltip stays out of the way. */}
180
+ <DropdownMenuTrigger
181
+ as={SideRailButton}
149
182
  ref={(element: HTMLButtonElement) => (trigger = element)}
150
- variant="ghost"
151
- size={local.size ?? 'icon-md'}
152
- class={cn('text-muted-foreground', local.class)}
153
- aria-label={local.label ?? 'Account and settings'}
183
+ label={local.label ?? 'Account and settings'}
184
+ class={local.class}
154
185
  onFocus={() => local.onIntent?.()}
155
186
  onPointerEnter={() => local.onIntent?.()}
156
187
  {...rest}
157
188
  >
158
189
  <UserRound aria-hidden="true" />
159
- </TooltipTrigger>
160
- <DropdownMenuContent
161
- hideArrow={local.hideArrow}
162
- class="min-w-56 max-h-(--kb-popper-available-height) overflow-x-hidden overflow-y-auto"
163
- onCloseAutoFocus={(event) => {
164
- const selection = pendingAfterClose
165
- pendingAfterClose = undefined
166
- if (!selection) return
190
+ </DropdownMenuTrigger>
191
+ </Show>
192
+ <DropdownMenuContent
193
+ hideArrow={local.hideArrow}
194
+ class="min-w-56 max-h-(--kb-popper-available-height) overflow-x-hidden overflow-y-auto"
195
+ onCloseAutoFocus={(event) => {
196
+ const selection = pendingAfterClose
197
+ pendingAfterClose = undefined
198
+ if (!selection) return
167
199
 
168
- event.preventDefault()
169
- scheduledAfterClose = selection
170
- queueMicrotask(() => {
171
- if (scheduledAfterClose !== selection) return
172
- scheduledAfterClose = undefined
173
- selection.callback(trigger)
174
- })
175
- }}
176
- >
177
- <DropdownMenuGroup>
178
- <For each={visible()}>
179
- {(item) => (
180
- <DropdownMenuItem
181
- disabled={item.disabled}
182
- onSelect={() => {
183
- pendingAfterClose = item.onSelectAfterClose
184
- ? { callback: item.onSelectAfterClose }
185
- : undefined
186
- item.onSelect?.()
187
- }}
188
- shortcut={item.shortcut}
189
- keyshortcuts={item.keyshortcuts}
190
- >
191
- {item.icon ?? renderFallbackIcon(item.id)}
192
- <span>{item.label}</span>
193
- </DropdownMenuItem>
194
- )}
195
- </For>
196
- </DropdownMenuGroup>
197
- <Show when={local.showSession !== false}>
198
- <DropdownMenuSeparator />
199
- <DropdownMenuGroup>
200
+ event.preventDefault()
201
+ scheduledAfterClose = selection
202
+ queueMicrotask(() => {
203
+ if (scheduledAfterClose !== selection) return
204
+ scheduledAfterClose = undefined
205
+ selection.callback(trigger)
206
+ })
207
+ }}
208
+ >
209
+ <DropdownMenuGroup>
210
+ <For each={visible()}>
211
+ {(item) => (
200
212
  <DropdownMenuItem
201
- disabled={local.busy}
202
- onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
213
+ disabled={item.disabled}
214
+ onSelect={() => {
215
+ pendingAfterClose = item.onSelectAfterClose
216
+ ? { callback: item.onSelectAfterClose }
217
+ : undefined
218
+ item.onSelect?.()
219
+ }}
220
+ shortcut={item.shortcut}
221
+ keyshortcuts={item.keyshortcuts}
203
222
  >
204
- <Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
205
- <LogOut aria-hidden="true" />
206
- </Show>
207
- <span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
223
+ {item.icon ?? renderFallbackIcon(item.id)}
224
+ <span>{item.label}</span>
208
225
  </DropdownMenuItem>
209
- </DropdownMenuGroup>
210
- </Show>
211
- </DropdownMenuContent>
212
- </DropdownMenu>
226
+ )}
227
+ </For>
228
+ </DropdownMenuGroup>
229
+ <Show when={local.showSession !== false}>
230
+ <DropdownMenuSeparator />
231
+ <DropdownMenuGroup>
232
+ <DropdownMenuItem
233
+ disabled={local.busy}
234
+ onSelect={() => (local.authenticated ? local.onSignOut?.() : local.onSignIn?.())}
235
+ >
236
+ <Show when={local.authenticated} fallback={<LogIn aria-hidden="true" />}>
237
+ <LogOut aria-hidden="true" />
238
+ </Show>
239
+ <span>{local.authenticated ? 'Sign out' : 'Sign in'}</span>
240
+ </DropdownMenuItem>
241
+ </DropdownMenuGroup>
242
+ </Show>
243
+ </DropdownMenuContent>
244
+ </DropdownMenu>
245
+ )
246
+
247
+ if (local.railTrigger) return menu()
248
+
249
+ return (
250
+ <Tooltip open={tooltipOpen()} onOpenChange={(open) => setTooltipOpen(open && !menuOpen())}>
251
+ {menu()}
213
252
  {/* Both overlays own a Popper context; the tooltip must stay outside the menu root. */}
214
253
  <TooltipContent placement="top">{local.label ?? 'Account and settings'}</TooltipContent>
215
254
  </Tooltip>
@@ -5,6 +5,7 @@ import { ActionButton } from '../action-button'
5
5
  import { Alert, AlertDescription } from '../../ui/alert'
6
6
  import { Card, CardContent, CardHeader } from '../../ui/card'
7
7
  import { Kbd, KbdGroup } from '../../ui/kbd'
8
+ import { Heading } from '../../ui/typography'
8
9
 
9
10
  export type HelpShortcut = { label: string; keys: readonly string[] }
10
11
  export type HelpLink = { label: string; description?: string; url: string }
@@ -37,7 +38,7 @@ export function HelpCenter(props: HelpCenterProps) {
37
38
  <div class={cn('flex min-w-0 flex-col gap-5', props.class)}>
38
39
  <Show when={props.showHeader !== false}>
39
40
  <div class="flex flex-col gap-2">
40
- <h1 class="text-xl font-semibold tracking-tight">Help Center</h1>
41
+ <Heading size="page">Help Center</Heading>
41
42
  <p class="text-sm text-muted-foreground">
42
43
  Keyboard shortcuts and resources for {props.appName}.
43
44
  </p>
@@ -2,6 +2,7 @@ import type { ComponentProps, JSX } from 'solid-js'
2
2
  import { createUniqueId, Show, splitProps } from 'solid-js'
3
3
  import { createFormFieldContext, FormFieldContext } from '../../../lib/form-field'
4
4
  import { cn } from '../../../lib/utils'
5
+ import { Heading } from '../../ui/typography'
5
6
 
6
7
  /**
7
8
  * SettingsSection and SettingsRow.
@@ -43,7 +44,9 @@ export function SettingsSection(
43
44
  >
44
45
  <div class="flex items-start justify-between gap-4">
45
46
  <div class="flex min-w-0 flex-col gap-0.5">
46
- <h2 class="text-base font-semibold tracking-tight">{local.title}</h2>
47
+ <Heading as="h2" size="card">
48
+ {local.title}
49
+ </Heading>
47
50
  <Show when={local.description}>
48
51
  <p class="max-w-prose text-sm text-muted-foreground text-pretty">{local.description}</p>
49
52
  </Show>
@@ -3,6 +3,7 @@ import type { ComponentProps, JSX } from 'solid-js'
3
3
  import { Show, splitProps } from 'solid-js'
4
4
  import { Dynamic } from 'solid-js/web'
5
5
  import { cn } from '../../../lib/utils'
6
+ import { Heading, Text } from '../../ui/typography'
6
7
 
7
8
  /**
8
9
  * Stat.
@@ -53,9 +54,13 @@ export function Stat(props: StatProps) {
53
54
  >
54
55
  <div class="text-muted-foreground truncate text-xs font-medium">{local.label}</div>
55
56
  <div class="flex items-baseline gap-1">
56
- <span class="text-xl font-semibold tabular-nums tracking-tight">{local.value}</span>
57
+ <Heading as="span" size="page" numeric>
58
+ {local.value}
59
+ </Heading>
57
60
  <Show when={local.unit}>
58
- <span class="text-muted-foreground text-xs">{local.unit}</span>
61
+ <Text variant="caption" tone="muted">
62
+ {local.unit}
63
+ </Text>
59
64
  </Show>
60
65
  </div>
61
66
  <Show when={local.delta}>
@@ -33,6 +33,7 @@ import {
33
33
  } from '../../ui/dialog'
34
34
  import { createDialogFocusRestoration } from '../../ui/dialog/dialog'
35
35
  import { Progress } from '../../ui/progress'
36
+ import { Heading } from '../../ui/typography'
36
37
  import { cn } from '../../../lib/utils'
37
38
  import { formatBytes, formatReleaseDate, plainTextFromMarkdown } from '../../../lib/version-notes'
38
39
 
@@ -590,9 +591,9 @@ export function UpdateDialog(props: UpdateDialogProps) {
590
591
  <p class="text-2xs font-semibold tracking-widest text-muted-foreground uppercase">
591
592
  Installed version
592
593
  </p>
593
- <p class="text-xl font-semibold tracking-tight text-foreground">
594
+ <Heading as="p" size="page" tone="foreground">
594
595
  v{state()?.currentVersion || local.fallbackVersion || '0.1.0'}
595
- </p>
596
+ </Heading>
596
597
  <p class="text-sm text-muted-foreground">
597
598
  <Show
598
599
  when={isCurrent()}
@@ -618,19 +619,30 @@ export function UpdateDialog(props: UpdateDialogProps) {
618
619
  </Show>
619
620
  </p>
620
621
  </div>
621
- <ActionButton
622
- type="button"
623
- variant="outline"
624
- size="sm"
625
- tooltip="Check the release channel for the latest signed app version."
626
- disabled={!desktop() || working()}
627
- onClick={() => void check()}
628
- >
629
- <Show when={working()} fallback={<RefreshCw aria-hidden="true" />}>
630
- <LoaderCircle class="animate-spin" aria-hidden="true" />
622
+ <div class="flex flex-col items-end gap-2">
623
+ <ActionButton
624
+ type="button"
625
+ variant="outline"
626
+ size="sm"
627
+ tooltip="Check the release channel for the latest signed app version."
628
+ disabled={!desktop() || working()}
629
+ onClick={() => void check()}
630
+ >
631
+ <Show when={working()} fallback={<RefreshCw aria-hidden="true" />}>
632
+ <LoaderCircle class="animate-spin" aria-hidden="true" />
633
+ </Show>
634
+ {checkFailed() ? 'Retry update check' : 'Check latest version'}
635
+ </ActionButton>
636
+ {/* The up-to-date confirmation reads as the button's outcome,
637
+ so it sits directly beneath it rather than floating as a
638
+ full-width row between sections. */}
639
+ <Show when={isCurrent()}>
640
+ <p class="text-foreground flex items-center gap-2 text-sm" role="status">
641
+ <Check class="size-4 text-success" aria-hidden="true" />
642
+ {appName()} is up to date.
643
+ </p>
631
644
  </Show>
632
- {checkFailed() ? 'Retry update check' : 'Check latest version'}
633
- </ActionButton>
645
+ </div>
634
646
  </div>
635
647
  </section>
636
648
 
@@ -729,13 +741,6 @@ export function UpdateDialog(props: UpdateDialogProps) {
729
741
  </section>
730
742
  </Show>
731
743
 
732
- <Show when={isCurrent()}>
733
- <p class="text-foreground flex items-center gap-2 text-sm" role="status">
734
- <Check class="size-4 text-success" aria-hidden="true" />
735
- {appName()} is up to date.
736
- </p>
737
- </Show>
738
-
739
744
  <Show when={state()?.phase === 'installed'}>
740
745
  <p class="text-foreground flex items-center gap-2 text-sm" role="status">
741
746
  <Check class="size-4 text-success" aria-hidden="true" />
@@ -780,9 +785,16 @@ export function UpdateDialog(props: UpdateDialogProps) {
780
785
 
781
786
  <Show when={changelog()}>
782
787
  <div class="flex flex-col gap-2">
783
- <h2 id="update-changelog" class="text-sm font-semibold text-foreground">
784
- Installed changelog
785
- </h2>
788
+ <div class="flex items-center gap-2">
789
+ <h2 id="update-changelog" class="text-sm font-semibold text-foreground">
790
+ Installed changelog
791
+ </h2>
792
+ {/* The version pill identifies which build's history this is —
793
+ the same treatment the available-update heading carries. */}
794
+ <Badge variant="subtle" size="sm">
795
+ v{state()?.currentVersion || local.fallbackVersion || '0.1.0'}
796
+ </Badge>
797
+ </div>
786
798
  <p class="text-xs text-muted-foreground">
787
799
  All versions and updates included with this installation.
788
800
  </p>
@@ -1,6 +1,7 @@
1
1
  import type { ComponentProps, JSX } from 'solid-js'
2
2
  import { Show, splitProps } from 'solid-js'
3
3
  import { cn } from '../../../lib/utils'
4
+ import { Heading, headingVariants } from '../../ui/typography'
4
5
 
5
6
  /**
6
7
  * PageHeader.
@@ -44,7 +45,7 @@ export function PageHeaderTitle(props: ComponentProps<'h1'>) {
44
45
  return (
45
46
  <h1
46
47
  data-slot="page-header-title"
47
- class={cn('text-xl font-semibold tracking-tight', local.class)}
48
+ class={cn(headingVariants({ size: 'page' }), local.class)}
48
49
  {...rest}
49
50
  />
50
51
  )
@@ -92,7 +93,7 @@ export function PageSection(
92
93
  <Show when={local.title}>
93
94
  <div class="flex items-baseline justify-between gap-3">
94
95
  <div class="flex min-w-0 flex-col gap-0.5">
95
- <h2 class="text-base font-semibold tracking-tight">{local.title}</h2>
96
+ <Heading size="section">{local.title}</Heading>
96
97
  <Show when={local.description}>
97
98
  <p class="max-w-prose text-sm text-muted-foreground text-pretty">
98
99
  {local.description}
@@ -7,6 +7,7 @@ import { Button } from '../ui/button/button'
7
7
  import { RadioGroup, RadioGroupItem } from '../ui/radio-group/radio-group'
8
8
  import { ThemePicker } from './theme-picker'
9
9
  import { useTheme } from './theme-provider'
10
+ import { Heading } from '../ui/typography'
10
11
 
11
12
  /**
12
13
  * AppearancePanel.
@@ -43,7 +44,9 @@ export function AppearancePanel(props: AppearancePanelProps) {
43
44
  {/* --- 1. Appearance ------------------------------------------------- */}
44
45
  <section class="flex flex-col gap-3">
45
46
  <header class="flex flex-col gap-0.5">
46
- <h2 class="text-base font-semibold tracking-tight">Appearance</h2>
47
+ <Heading as="h2" size="card">
48
+ Appearance
49
+ </Heading>
47
50
  <p class="max-w-prose text-sm text-muted-foreground">
48
51
  Follow the system, or pin one. The theme below is chosen per appearance, so switching at
49
52
  sunset does not make you choose again.
@@ -87,7 +90,9 @@ export function AppearancePanel(props: AppearancePanelProps) {
87
90
  {/* --- 3. Accent ------------------------------------------------------ */}
88
91
  <section class="flex flex-col gap-3">
89
92
  <header class="flex flex-col gap-0.5">
90
- <h2 class="text-base font-semibold tracking-tight">Accent</h2>
93
+ <Heading as="h2" size="card">
94
+ Accent
95
+ </Heading>
91
96
  <p class="max-w-prose text-sm text-muted-foreground">
92
97
  Colours the interactive roles — the primary button, the focus ring, a selected row — on
93
98
  top of the theme above. Every preset is measured against the surface it lands on, so
@@ -130,7 +135,9 @@ export function AppearancePanel(props: AppearancePanelProps) {
130
135
  {/* --- 4. Font -------------------------------------------------------- */}
131
136
  <section class="flex flex-col gap-3">
132
137
  <header class="flex flex-col gap-0.5">
133
- <h2 class="text-base font-semibold tracking-tight">Typeface</h2>
138
+ <Heading as="h2" size="card">
139
+ Typeface
140
+ </Heading>
134
141
  <p class="max-w-prose text-sm text-muted-foreground">
135
142
  The interface face. Every option is self-hosted, so nothing is fetched at runtime and
136
143
  the choice survives going offline.
@@ -166,7 +173,9 @@ function ThemeSection(props: {
166
173
  return (
167
174
  <section class="flex flex-col gap-3">
168
175
  <header class="flex flex-col gap-0.5">
169
- <h2 class="text-base font-semibold tracking-tight">{props.title}</h2>
176
+ <Heading as="h2" size="card">
177
+ {props.title}
178
+ </Heading>
170
179
  <p class="max-w-prose text-sm text-muted-foreground">{props.description}</p>
171
180
  </header>
172
181
  <ThemePicker
@@ -7,6 +7,7 @@ import type { ComponentProps } from 'solid-js'
7
7
  import { createEffect, createSignal, splitProps } from 'solid-js'
8
8
  import { overlayMotion, overlayPositioner, overlayScrim, overlaySurface } from '../../../lib/overlay'
9
9
  import { cn } from '../../../lib/utils'
10
+ import { headingVariants } from '../typography'
10
11
 
11
12
  /**
12
13
  * AlertDialog.
@@ -117,7 +118,7 @@ export function AlertDialogTitle(props: ComponentProps<typeof KobalteAlertDialog
117
118
  const [local, rest] = splitProps(props, ['class'])
118
119
  return (
119
120
  <KobalteAlertDialog.Title
120
- class={cn('text-base leading-none font-semibold tracking-tight', local.class)}
121
+ class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
121
122
  {...rest}
122
123
  />
123
124
  )
@@ -1,6 +1,7 @@
1
1
  import type { ComponentProps, JSX } from 'solid-js'
2
2
  import { splitProps } from 'solid-js'
3
3
  import { cn } from '../../../lib/utils'
4
+ import { headingVariants } from '../typography'
4
5
 
5
6
  /**
6
7
  * Card.
@@ -41,7 +42,7 @@ export function CardTitle(props: ComponentProps<'div'>) {
41
42
  return (
42
43
  <div
43
44
  data-slot="card-title"
44
- class={cn('text-base leading-none font-semibold tracking-tight', local.class)}
45
+ class={cn(headingVariants({ size: 'card', leading: 'none' }), local.class)}
45
46
  {...rest}
46
47
  />
47
48
  )
@@ -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)) {