@adea-ai/ui 0.105.0 → 0.106.0

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 (147) hide show
  1. package/README.md +44 -8
  2. package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
  3. package/dist/components/composites/action-button/action-button.js +6 -8
  4. package/dist/components/composites/action-button/action-button.js.map +1 -1
  5. package/dist/components/composites/appearance-editor/accent-swatch-groups.d.ts +16 -0
  6. package/dist/components/composites/appearance-editor/accent-swatch-groups.d.ts.map +1 -0
  7. package/dist/components/composites/appearance-editor/accent-swatch-groups.js +151 -0
  8. package/dist/components/composites/appearance-editor/accent-swatch-groups.js.map +1 -0
  9. package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
  10. package/dist/components/composites/appearance-editor/appearance-choices.js +24 -84
  11. package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
  12. package/dist/components/composites/appearance-editor/appearance-editor.d.ts +2 -2
  13. package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
  14. package/dist/components/composites/appearance-editor/appearance-editor.js +25 -7
  15. package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
  16. package/dist/components/composites/appearance-editor/appearance-types.d.ts +9 -0
  17. package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
  18. package/dist/components/composites/appearance-editor/font-settings-group.d.ts +10 -0
  19. package/dist/components/composites/appearance-editor/font-settings-group.d.ts.map +1 -0
  20. package/dist/components/composites/appearance-editor/font-settings-group.js +310 -0
  21. package/dist/components/composites/appearance-editor/font-settings-group.js.map +1 -0
  22. package/dist/components/composites/appearance-editor/index.d.ts +2 -0
  23. package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
  24. package/dist/components/composites/appearance-editor/index.js +3 -1
  25. package/dist/components/composites/update-dialog/update-dialog.js +2 -2
  26. package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
  27. package/dist/components/conversation/message-row.d.ts.map +1 -1
  28. package/dist/components/conversation/message-row.js +3 -3
  29. package/dist/components/conversation/message-row.js.map +1 -1
  30. package/dist/components/conversation/paste-token-editor.js +1 -1
  31. package/dist/components/conversation/paste-token-editor.js.map +1 -1
  32. package/dist/components/layout/side-rail/side-rail.js +2 -2
  33. package/dist/components/layout/side-rail/side-rail.js.map +1 -1
  34. package/dist/components/layout/top-bar/top-bar.js +1 -1
  35. package/dist/components/layout/top-bar/top-bar.js.map +1 -1
  36. package/dist/components/theme/appearance-panel.d.ts +1 -1
  37. package/dist/components/theme/appearance-panel.d.ts.map +1 -1
  38. package/dist/components/theme/appearance-panel.js +45 -105
  39. package/dist/components/theme/appearance-panel.js.map +1 -1
  40. package/dist/components/theme/theme-provider.d.ts +5 -2
  41. package/dist/components/theme/theme-provider.d.ts.map +1 -1
  42. package/dist/components/theme/theme-provider.js +38 -19
  43. package/dist/components/theme/theme-provider.js.map +1 -1
  44. package/dist/components/ui/code-block/code-block.d.ts +3 -3
  45. package/dist/components/ui/code-block/code-block.js +6 -6
  46. package/dist/components/ui/code-block/code-block.js.map +1 -1
  47. package/dist/components/ui/command/command.js +1 -1
  48. package/dist/components/ui/command/command.js.map +1 -1
  49. package/dist/components/ui/context-menu/context-menu.js +1 -1
  50. package/dist/components/ui/context-menu/context-menu.js.map +1 -1
  51. package/dist/components/ui/diff-block/diff-block.js +5 -5
  52. package/dist/components/ui/diff-block/diff-block.js.map +1 -1
  53. package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
  54. package/dist/components/ui/dropdown-menu/dropdown-menu.js +101 -8
  55. package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
  56. package/dist/components/ui/kbd/kbd.js +1 -1
  57. package/dist/components/ui/kbd/kbd.js.map +1 -1
  58. package/dist/components/ui/menubar/menubar.js +1 -1
  59. package/dist/components/ui/menubar/menubar.js.map +1 -1
  60. package/dist/components/ui/typography/typography.d.ts +3 -5
  61. package/dist/components/ui/typography/typography.d.ts.map +1 -1
  62. package/dist/components/ui/typography/typography.js +6 -8
  63. package/dist/components/ui/typography/typography.js.map +1 -1
  64. package/dist/index.js +6 -3
  65. package/dist/lib/appearance-font-settings.d.ts +53 -0
  66. package/dist/lib/appearance-font-settings.d.ts.map +1 -0
  67. package/dist/lib/appearance-font-settings.js +160 -0
  68. package/dist/lib/appearance-font-settings.js.map +1 -0
  69. package/dist/lib/font-catalog.d.ts +72 -0
  70. package/dist/lib/font-catalog.d.ts.map +1 -0
  71. package/dist/lib/font-catalog.js +71 -0
  72. package/dist/lib/font-catalog.js.map +1 -0
  73. package/dist/lib/safe-script.d.ts +3 -0
  74. package/dist/lib/safe-script.d.ts.map +1 -0
  75. package/dist/lib/safe-script.js +9 -0
  76. package/dist/lib/safe-script.js.map +1 -0
  77. package/dist/lib/tokens.d.ts +2 -19
  78. package/dist/lib/tokens.d.ts.map +1 -1
  79. package/dist/lib/tokens.js +63 -39
  80. package/dist/lib/tokens.js.map +1 -1
  81. package/dist/r/appearance-editor.json +11 -0
  82. package/dist/r/lib.json +15 -0
  83. package/dist/r/registry.json +33 -0
  84. package/dist/r/src/components/composites/action-button/action-button.tsx +2 -1
  85. package/dist/r/src/components/composites/appearance-editor/accent-swatch-groups.tsx +147 -0
  86. package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +17 -74
  87. package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +19 -6
  88. package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +9 -0
  89. package/dist/r/src/components/composites/appearance-editor/font-settings-group.tsx +350 -0
  90. package/dist/r/src/components/composites/appearance-editor/index.ts +19 -0
  91. package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +2 -2
  92. package/dist/r/src/components/conversation/message-row.tsx +7 -3
  93. package/dist/r/src/components/conversation/paste-token-editor.tsx +1 -1
  94. package/dist/r/src/components/layout/side-rail/side-rail.tsx +3 -3
  95. package/dist/r/src/components/layout/top-bar/top-bar.tsx +1 -1
  96. package/dist/r/src/components/theme/appearance-panel.tsx +34 -83
  97. package/dist/r/src/components/theme/theme-provider.tsx +50 -20
  98. package/dist/r/src/components/ui/code-block/code-block.tsx +6 -6
  99. package/dist/r/src/components/ui/command/command.tsx +1 -1
  100. package/dist/r/src/components/ui/context-menu/context-menu.tsx +1 -1
  101. package/dist/r/src/components/ui/diff-block/diff-block.tsx +7 -7
  102. package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +145 -7
  103. package/dist/r/src/components/ui/kbd/kbd.tsx +1 -1
  104. package/dist/r/src/components/ui/menubar/menubar.tsx +1 -1
  105. package/dist/r/src/components/ui/typography/typography.tsx +6 -8
  106. package/dist/r/src/lib/appearance-font-settings.ts +233 -0
  107. package/dist/r/src/lib/font-catalog.ts +80 -0
  108. package/dist/r/src/lib/safe-script.ts +7 -0
  109. package/dist/r/src/lib/tokens.ts +64 -59
  110. package/dist/r/src/styles/appearance-font-settings.css +110 -0
  111. package/dist/r/src/styles/fonts.css +4 -5
  112. package/dist/r/src/styles/globals.css +4 -1
  113. package/dist/r/src/styles/theme.css +14 -0
  114. package/dist/r/theme.json +5 -0
  115. package/dist/r/theming.json +2 -0
  116. package/package.json +2 -1
  117. package/registry.json +33 -0
  118. package/src/components/composites/action-button/action-button.tsx +2 -1
  119. package/src/components/composites/appearance-editor/accent-swatch-groups.tsx +147 -0
  120. package/src/components/composites/appearance-editor/appearance-choices.tsx +17 -74
  121. package/src/components/composites/appearance-editor/appearance-editor.tsx +19 -6
  122. package/src/components/composites/appearance-editor/appearance-types.ts +9 -0
  123. package/src/components/composites/appearance-editor/font-settings-group.tsx +350 -0
  124. package/src/components/composites/appearance-editor/index.ts +19 -0
  125. package/src/components/composites/update-dialog/update-dialog.tsx +2 -2
  126. package/src/components/conversation/message-row.tsx +7 -3
  127. package/src/components/conversation/paste-token-editor.tsx +1 -1
  128. package/src/components/layout/side-rail/side-rail.tsx +3 -3
  129. package/src/components/layout/top-bar/top-bar.tsx +1 -1
  130. package/src/components/theme/appearance-panel.tsx +34 -83
  131. package/src/components/theme/theme-provider.tsx +50 -20
  132. package/src/components/ui/code-block/code-block.tsx +6 -6
  133. package/src/components/ui/command/command.tsx +1 -1
  134. package/src/components/ui/context-menu/context-menu.tsx +1 -1
  135. package/src/components/ui/diff-block/diff-block.tsx +7 -7
  136. package/src/components/ui/dropdown-menu/dropdown-menu.tsx +145 -7
  137. package/src/components/ui/kbd/kbd.tsx +1 -1
  138. package/src/components/ui/menubar/menubar.tsx +1 -1
  139. package/src/components/ui/typography/typography.tsx +6 -8
  140. package/src/lib/appearance-font-settings.ts +233 -0
  141. package/src/lib/font-catalog.ts +80 -0
  142. package/src/lib/safe-script.ts +7 -0
  143. package/src/lib/tokens.ts +64 -59
  144. package/src/styles/appearance-font-settings.css +110 -0
  145. package/src/styles/fonts.css +4 -5
  146. package/src/styles/globals.css +4 -1
  147. package/src/styles/theme.css +14 -0
@@ -5,10 +5,13 @@
5
5
  * @shadcn/lint discover, so it must stay the file that imports Tailwind.
6
6
  *
7
7
  * @import '@adea-ai/ui/globals.css';
8
+ * @import '@adea-ai/ui/appearance-font-settings.css'; <- optional role preferences
8
9
  * @import '@adea-ai/ui/fonts.css'; <- optional, self-hosted typefaces
9
10
  *
10
11
  * A consumer that already imports Tailwind itself should import `theme.css`
11
- * and `base.css` directly instead, and must not import this file.
12
+ * and `base.css` directly instead, and must not import this file. The font role
13
+ * stylesheet stays opt-in so applications that use only the base theme keep
14
+ * the existing CSS footprint.
12
15
  *
13
16
  * Tokens live in `theme.css` only. This file must never duplicate them.
14
17
  */
@@ -525,6 +525,14 @@
525
525
  --font-family-geist-mono: 'Geist Mono Variable', 'Geist Mono', monospace;
526
526
  --font-family-system: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
527
527
  --font-family-system-mono: ui-monospace, 'SF Mono', Menlo, Consolas, monospace;
528
+ /* Lightweight role defaults keep conversation and code components useful for
529
+ consumers that have not opted into independent Appearance font settings. */
530
+ --font-content: var(--font-sans);
531
+ --font-code: var(--font-mono);
532
+ --text-content: var(--text-sm);
533
+ --text-content--line-height: var(--text-sm--line-height);
534
+ --text-code: 0.75rem;
535
+ --text-code--line-height: 1.5;
528
536
 
529
537
  /* Width compensation for the mono UI fonts. `normal` on the proportional face,
530
538
  tightened by the `[data-font]` blocks above when the UI font is monospace. */
@@ -776,6 +784,8 @@
776
784
 
777
785
  --font-sans: var(--font-sans);
778
786
  --font-mono: var(--font-mono);
787
+ --font-content: var(--font-content);
788
+ --font-code: var(--font-code);
779
789
  --font-family-space-grotesk: var(--font-family-space-grotesk);
780
790
  --font-family-jetbrains-mono: var(--font-family-jetbrains-mono);
781
791
  --font-family-geist: var(--font-family-geist);
@@ -799,6 +809,10 @@
799
809
  --text-2xl--line-height: var(--text-2xl--line-height);
800
810
  --text-3xl: var(--text-3xl);
801
811
  --text-3xl--line-height: var(--text-3xl--line-height);
812
+ --text-content: var(--text-content);
813
+ --text-content--line-height: var(--text-content--line-height);
814
+ --text-code: var(--text-code);
815
+ --text-code--line-height: var(--text-code--line-height);
802
816
 
803
817
  --radius-sm: var(--radius-sm);
804
818
  --radius-md: var(--radius-md);
package/dist/r/theme.json CHANGED
@@ -27,6 +27,11 @@
27
27
  "type": "registry:style",
28
28
  "target": "styles/base.css"
29
29
  },
30
+ {
31
+ "path": "src/styles/appearance-font-settings.css",
32
+ "type": "registry:style",
33
+ "target": "styles/appearance-font-settings.css"
34
+ },
30
35
  {
31
36
  "path": "src/styles/fonts.css",
32
37
  "type": "registry:style",
@@ -5,9 +5,11 @@
5
5
  "description": "",
6
6
  "dependencies": [
7
7
  "@adea-ai/themes@^0.9.6",
8
+ "@kobalte/core@^0.13.14",
8
9
  "lucide-solid@^1.51.0"
9
10
  ],
10
11
  "registryDependencies": [
12
+ "@adea-ai/ui/appearance-editor",
11
13
  "@adea-ai/ui/button",
12
14
  "@adea-ai/ui/radio-group",
13
15
  "@adea-ai/ui/toggle-group",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adea-ai/ui",
3
- "version": "0.105.0",
3
+ "version": "0.106.0",
4
4
  "description": "The Adea design system: Kobalte-backed Solid components, semantic OKLCH tokens, and the app-shell layout primitives shared by Adea and Cortana.",
5
5
  "keywords": [
6
6
  "adea",
@@ -67,6 +67,7 @@
67
67
  "./globals.css": "./src/styles/globals.css",
68
68
  "./theme.css": "./src/styles/theme.css",
69
69
  "./base.css": "./src/styles/base.css",
70
+ "./appearance-font-settings.css": "./src/styles/appearance-font-settings.css",
70
71
  "./fonts.css": "./src/styles/fonts.css",
71
72
  "./registry.json": "./registry.json",
72
73
  "./package.json": "./package.json",
package/registry.json CHANGED
@@ -1920,9 +1920,15 @@
1920
1920
  "@adea-ai/ui/input",
1921
1921
  "@adea-ai/ui/popover",
1922
1922
  "@adea-ai/ui/switch",
1923
+ "@adea-ai/ui/theme",
1923
1924
  "lib"
1924
1925
  ],
1925
1926
  "files": [
1927
+ {
1928
+ "path": "src/components/composites/appearance-editor/accent-swatch-groups.tsx",
1929
+ "type": "registry:ui",
1930
+ "target": "components/composites/appearance-editor/accent-swatch-groups.tsx"
1931
+ },
1926
1932
  {
1927
1933
  "path": "src/components/composites/appearance-editor/appearance-choices.tsx",
1928
1934
  "type": "registry:ui",
@@ -1943,6 +1949,11 @@
1943
1949
  "type": "registry:ui",
1944
1950
  "target": "components/composites/appearance-editor/appearance-types.ts"
1945
1951
  },
1952
+ {
1953
+ "path": "src/components/composites/appearance-editor/font-settings-group.tsx",
1954
+ "type": "registry:ui",
1955
+ "target": "components/composites/appearance-editor/font-settings-group.tsx"
1956
+ },
1946
1957
  {
1947
1958
  "path": "src/components/composites/appearance-editor/index.ts",
1948
1959
  "type": "registry:ui",
@@ -2358,9 +2369,11 @@
2358
2369
  "description": "",
2359
2370
  "dependencies": [
2360
2371
  "@adea-ai/themes@^0.9.6",
2372
+ "@kobalte/core@^0.13.14",
2361
2373
  "lucide-solid@^1.51.0"
2362
2374
  ],
2363
2375
  "registryDependencies": [
2376
+ "@adea-ai/ui/appearance-editor",
2364
2377
  "@adea-ai/ui/button",
2365
2378
  "@adea-ai/ui/radio-group",
2366
2379
  "@adea-ai/ui/toggle-group",
@@ -2452,6 +2465,11 @@
2452
2465
  "type": "registry:style",
2453
2466
  "target": "styles/base.css"
2454
2467
  },
2468
+ {
2469
+ "path": "src/styles/appearance-font-settings.css",
2470
+ "type": "registry:style",
2471
+ "target": "styles/appearance-font-settings.css"
2472
+ },
2455
2473
  {
2456
2474
  "path": "src/styles/fonts.css",
2457
2475
  "type": "registry:style",
@@ -2471,6 +2489,11 @@
2471
2489
  ],
2472
2490
  "registryDependencies": [],
2473
2491
  "files": [
2492
+ {
2493
+ "path": "src/lib/appearance-font-settings.ts",
2494
+ "type": "registry:lib",
2495
+ "target": "lib/appearance-font-settings.ts"
2496
+ },
2474
2497
  {
2475
2498
  "path": "src/lib/clipboard.ts",
2476
2499
  "type": "registry:lib",
@@ -2486,6 +2509,11 @@
2486
2509
  "type": "registry:lib",
2487
2510
  "target": "lib/download.ts"
2488
2511
  },
2512
+ {
2513
+ "path": "src/lib/font-catalog.ts",
2514
+ "type": "registry:lib",
2515
+ "target": "lib/font-catalog.ts"
2516
+ },
2489
2517
  {
2490
2518
  "path": "src/lib/form-field.ts",
2491
2519
  "type": "registry:lib",
@@ -2501,6 +2529,11 @@
2501
2529
  "type": "registry:lib",
2502
2530
  "target": "lib/overlay.ts"
2503
2531
  },
2532
+ {
2533
+ "path": "src/lib/safe-script.ts",
2534
+ "type": "registry:lib",
2535
+ "target": "lib/safe-script.ts"
2536
+ },
2504
2537
  {
2505
2538
  "path": "src/lib/theme-accents.ts",
2506
2539
  "type": "registry:lib",
@@ -161,6 +161,7 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
161
161
  }
162
162
  >
163
163
  <Tooltip
164
+ placement={local.tooltipSide ?? 'bottom'}
164
165
  open={tooltipOpen()}
165
166
  onOpenChange={(open) => {
166
167
  if (open && tooltipSuppression()) return
@@ -174,7 +175,7 @@ export function ActionButton<T extends ValidComponent = 'button'>(props: ActionB
174
175
  >
175
176
  {contents()}
176
177
  </TooltipTrigger>
177
- <TooltipContent side={local.tooltipSide ?? 'bottom'}>{local.tooltip}</TooltipContent>
178
+ <TooltipContent>{local.tooltip}</TooltipContent>
178
179
  </Tooltip>
179
180
  </Show>
180
181
  <Show when={busy()}>
@@ -0,0 +1,147 @@
1
+ /*
2
+ * MIT License
3
+ *
4
+ * Copyright (c) 2026 Wing
5
+ *
6
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ * of this software and associated documentation files (the "Software"), to deal
8
+ * in the Software without restriction, including without limitation the rights
9
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ * copies of the Software, and to permit persons to whom the Software is
11
+ * furnished to do so, subject to the following conditions:
12
+ *
13
+ * The above copyright notice and this permission notice shall be included in all
14
+ * copies or substantial portions of the Software.
15
+ *
16
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ * SOFTWARE.
23
+ */
24
+ import { RadioGroup as Radio } from '@kobalte/core/radio-group'
25
+ import { For, Show } from 'solid-js'
26
+ import { cn } from '#lib/utils'
27
+
28
+ export const PRIMARY_ACCENT_IDS = ['blue', 'green', 'amber', 'cyan', 'pink'] as const
29
+ const primaryAccentIds: ReadonlySet<string> = new Set(PRIMARY_ACCENT_IDS)
30
+
31
+ type AccentOption = Readonly<{
32
+ id: string
33
+ label: string
34
+ light?: string
35
+ dark?: string
36
+ }>
37
+
38
+ type AccentSwatchGroupsProps = {
39
+ accentOptions: readonly AccentOption[]
40
+ themeAccentOptions: readonly AccentOption[]
41
+ resolvedAppearance: 'light' | 'dark'
42
+ themeDefaultColor: string
43
+ }
44
+
45
+ export function AccentSwatchGroups(props: AccentSwatchGroupsProps) {
46
+ const primaryAccents = () =>
47
+ PRIMARY_ACCENT_IDS.flatMap((id) => {
48
+ const option = props.accentOptions.find((candidate) => candidate.id === id)
49
+ return option ? [option] : []
50
+ })
51
+ const additionalAccents = () =>
52
+ props.accentOptions.filter(
53
+ (option) => option.id !== 'theme' && !primaryAccentIds.has(option.id)
54
+ )
55
+
56
+ return (
57
+ <>
58
+ <div class="grid grid-cols-3 gap-2" data-primary-accent-grid>
59
+ <AccentSwatch
60
+ id="theme"
61
+ label="Theme default"
62
+ color={props.themeDefaultColor}
63
+ group="primary"
64
+ />
65
+ <For each={primaryAccents()}>
66
+ {(option) => (
67
+ <AccentSwatch
68
+ id={option.id}
69
+ label={option.label}
70
+ color={(props.resolvedAppearance === 'light' ? option.light : option.dark) ?? ''}
71
+ group="primary"
72
+ />
73
+ )}
74
+ </For>
75
+ </div>
76
+ <Show when={additionalAccents().length > 0}>
77
+ <div class="flex flex-col gap-1">
78
+ <span class="text-xs font-medium text-muted-foreground">Additional colors</span>
79
+ <div class="flex flex-wrap gap-2">
80
+ <For each={additionalAccents()}>
81
+ {(option) => (
82
+ <AccentSwatch
83
+ id={option.id}
84
+ label={option.label}
85
+ color={(props.resolvedAppearance === 'light' ? option.light : option.dark) ?? ''}
86
+ group="additional"
87
+ />
88
+ )}
89
+ </For>
90
+ </div>
91
+ </div>
92
+ </Show>
93
+ <Show when={props.themeAccentOptions.length > 0}>
94
+ <div class="flex flex-col gap-1">
95
+ <span class="text-xs font-medium text-muted-foreground">Theme accents</span>
96
+ <div class="grid grid-cols-3 gap-2">
97
+ <For each={props.themeAccentOptions}>
98
+ {(option) => (
99
+ <AccentSwatch
100
+ id={option.id}
101
+ label={'Theme ' + option.label.toLowerCase()}
102
+ color={(props.resolvedAppearance === 'light' ? option.light : option.dark) ?? ''}
103
+ group="theme"
104
+ dashed
105
+ />
106
+ )}
107
+ </For>
108
+ </div>
109
+ </div>
110
+ </Show>
111
+ </>
112
+ )
113
+ }
114
+
115
+ function AccentSwatch(props: {
116
+ id: string
117
+ label: string
118
+ color: string
119
+ group: 'primary' | 'additional' | 'theme'
120
+ dashed?: boolean
121
+ }) {
122
+ return (
123
+ <Radio.Item
124
+ value={props.id}
125
+ data-accent-option={props.id}
126
+ data-primary-accent={props.group === 'primary' ? props.id : undefined}
127
+ data-additional-accent={props.group === 'additional' ? props.id : undefined}
128
+ data-theme-accent={props.group === 'theme' ? props.id : undefined}
129
+ class="rounded-full focus-within:ring-3 focus-within:ring-ring/50"
130
+ >
131
+ <Radio.ItemInput />
132
+ <Radio.ItemLabel
133
+ class={cn(
134
+ 'block size-7 cursor-pointer rounded-full border p-0.5 data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary',
135
+ { 'border-dashed data-[checked]:border-solid': props.dashed }
136
+ )}
137
+ >
138
+ <span
139
+ class="block size-full rounded-full"
140
+ aria-hidden="true"
141
+ style={{ 'background-color': props.color }}
142
+ />
143
+ <span class="sr-only">{props.label}</span>
144
+ </Radio.ItemLabel>
145
+ </Radio.Item>
146
+ )
147
+ }
@@ -26,6 +26,7 @@
26
26
  import { RadioGroup as Radio } from '@kobalte/core/radio-group'
27
27
  import { For, Show } from 'solid-js'
28
28
  import { isThemeAccentId } from '#lib/theme-accents'
29
+ import { AccentSwatchGroups } from './accent-swatch-groups'
29
30
  import type { AppearanceEditorProps } from './appearance-types'
30
31
  import { ThemeMiniature, ThemeMiniatureSplit } from './theme-preview'
31
32
 
@@ -39,7 +40,6 @@ const SURFACES = [
39
40
  { value: 'frosted', label: 'Frosted' },
40
41
  { value: 'opaque', label: 'Opaque' },
41
42
  ] as const
42
-
43
43
  export function ModeChoices(props: AppearanceEditorProps) {
44
44
  return (
45
45
  <Radio
@@ -113,89 +113,32 @@ export function AccentChoices(props: AppearanceEditorProps) {
113
113
  disabled={props.saving}
114
114
  orientation="horizontal"
115
115
  aria-label="Accent"
116
- // One chip (custom) beside a capped swatch grid: a chip-per-state column
117
- // beside a full-width swatch line overflowed the settings sidebar's
118
- // control column, so the pair stays a narrow chip plus a wrapped grid.
119
- class="flex items-center gap-3"
116
+ class="flex flex-col items-start gap-3"
120
117
  onChange={(accent) =>
121
118
  props.onChange({
122
119
  accent: accent === 'custom' ? (props.customAccentValue ?? '') : accent,
123
120
  })
124
121
  }
125
122
  >
126
- <div class="grid grid-cols-4 gap-2">
127
- {/* The theme's own accent is the first swatch. Picking it is the same
128
- act the old dedicated "Theme default" chip performed, without a
129
- second control for the state the row starts in. */}
130
- <Radio.Item
131
- value="theme"
132
- class="rounded-full focus-within:ring-3 focus-within:ring-ring/50"
133
- >
123
+ <AccentSwatchGroups
124
+ accentOptions={props.accentOptions}
125
+ themeAccentOptions={themeAccents()}
126
+ resolvedAppearance={resolvedAppearance()}
127
+ themeDefaultColor={
128
+ resolvedAppearance() === 'light'
129
+ ? props.lightTheme.colors.accent
130
+ : props.darkTheme.colors.accent
131
+ }
132
+ />
133
+ <div class="flex flex-col gap-1">
134
+ <span class="text-xs font-medium text-muted-foreground">Custom</span>
135
+ <Radio.Item value="custom">
134
136
  <Radio.ItemInput />
135
- <Radio.ItemLabel class="block size-7 cursor-pointer rounded-full border p-0.5 data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
136
- <span
137
- class="block size-full rounded-full"
138
- aria-hidden="true"
139
- style={{
140
- 'background-color':
141
- resolvedAppearance() === 'light'
142
- ? props.lightTheme.colors.accent
143
- : props.darkTheme.colors.accent,
144
- }}
145
- />
146
- <span class="sr-only">Theme default</span>
137
+ <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
138
+ Custom
147
139
  </Radio.ItemLabel>
148
140
  </Radio.Item>
149
- <For each={props.accentOptions}>
150
- {(option) => (
151
- <Radio.Item
152
- value={option.id}
153
- class="rounded-full focus-within:ring-3 focus-within:ring-ring/50"
154
- >
155
- <Radio.ItemInput />
156
- <Radio.ItemLabel class="block size-7 cursor-pointer rounded-full border p-0.5 data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
157
- <span
158
- class="block size-full rounded-full"
159
- aria-hidden="true"
160
- style={{
161
- 'background-color':
162
- resolvedAppearance() === 'light' ? option.light : option.dark,
163
- }}
164
- />
165
- <span class="sr-only">{option.label}</span>
166
- </Radio.ItemLabel>
167
- </Radio.Item>
168
- )}
169
- </For>
170
- <For each={themeAccents()}>
171
- {(option) => (
172
- <Radio.Item
173
- value={option.id}
174
- data-theme-accent={option.id}
175
- class="rounded-full focus-within:ring-3 focus-within:ring-ring/50"
176
- >
177
- <Radio.ItemInput />
178
- <Radio.ItemLabel class="block size-7 cursor-pointer rounded-full border border-dashed p-0.5 data-[checked]:border-solid data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
179
- <span
180
- class="block size-full rounded-full"
181
- aria-hidden="true"
182
- style={{
183
- 'background-color':
184
- resolvedAppearance() === 'light' ? option.light : option.dark,
185
- }}
186
- />
187
- <span class="sr-only">{`Theme ${option.label.toLowerCase()}`}</span>
188
- </Radio.ItemLabel>
189
- </Radio.Item>
190
- )}
191
- </For>
192
141
  </div>
193
- <Radio.Item value="custom">
194
- <Radio.ItemInput />
195
- <Radio.ItemLabel class="flex h-7 cursor-pointer items-center rounded-md border px-2 text-xs whitespace-nowrap data-[checked]:border-primary data-[checked]:ring-1 data-[checked]:ring-primary">
196
- Custom
197
- </Radio.ItemLabel>
198
- </Radio.Item>
199
142
  </Radio>
200
143
  )
201
144
  }
@@ -32,10 +32,12 @@
32
32
  * custom accent/reduced-transparency are accepted Adea divergences (#425).
33
33
  */
34
34
  import { EyeOff, Palette, PanelsTopLeft, SlidersHorizontal } from 'lucide-solid'
35
- import { Show, createUniqueId } from 'solid-js'
35
+ import { Show, createSignal, createUniqueId } from 'solid-js'
36
36
  import { cn } from '#lib/utils'
37
37
  import { Button } from '../../ui/button/button'
38
38
  import { Input } from '../../ui/input/input'
39
+ import { AppearanceFontSettingsGroup } from './font-settings-group'
40
+ import { DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS } from '#lib/appearance-font-settings'
39
41
 
40
42
  import { Switch } from '../../ui/switch/switch'
41
43
  import {
@@ -54,8 +56,8 @@ import { SettingsRow, TerminalRow, ThemeRow } from './appearance-rows'
54
56
  * No ThemeProvider is required, and no catalogue, renderer engine or storage is
55
57
  * imported. Hosts normalize unknown IDs and custom colors, report recovery,
56
58
  * snapshot on open, preview on changes, commit on Save, and restore on dismissal.
57
- * Both theme rows remain mounted when mode changes. Typeface/density axes are
58
- * deliberately absent from Adea #425's accepted surface.
59
+ * Both theme rows remain mounted when mode changes. Density remains outside the
60
+ * accepted appearance surface; typeface settings are shared and host-persisted.
59
61
  */
60
62
  export function AppearanceEditor(props: AppearanceEditorProps) {
61
63
  const errorId = createUniqueId()
@@ -140,6 +142,12 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
140
142
  </Show>
141
143
  </div>
142
144
  </Show>
145
+ <AppearanceFontSettingsGroup
146
+ settings={props.draft.fonts ?? DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS}
147
+ menuPortalMount={props.menuPortalMount}
148
+ disabled={props.saving}
149
+ onChange={(fonts) => props.onChange({ fonts })}
150
+ />
143
151
  <SettingsRow title="Glass" icon={<PanelsTopLeft />} description={glassDescription()}>
144
152
  <GlassChoices {...props} />
145
153
  </SettingsRow>
@@ -188,7 +196,7 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
188
196
  {/* Sticky so Save/Cancel stay reachable in a tall scroll container: the
189
197
  editor scrolls with its host panel, and actions parked at the bottom
190
198
  of a long form were unreachable without scrolling past every row. */}
191
- <div class="sticky bottom-0 z-10 mt-2 flex flex-wrap items-center gap-2 border-t bg-background px-4 py-3">
199
+ <div class="global-appearance-editor-actions sticky bottom-0 z-10 mt-2 flex flex-wrap items-center gap-2 border-t bg-background px-4 py-3">
192
200
  <Button
193
201
  type="button"
194
202
  size="sm"
@@ -228,6 +236,8 @@ export function AppearanceEditor(props: AppearanceEditorProps) {
228
236
  * Its measured available height also bounds scrolling, keeping footer actions
229
237
  * reachable when a narrow popup begins below its trigger. */
230
238
  export function AppearancePopover(props: AppearancePopoverProps) {
239
+ const [menuPortalMount, setMenuPortalMount] = createSignal<HTMLElement>()
240
+
231
241
  return (
232
242
  <Popover
233
243
  open={props.open}
@@ -243,12 +253,15 @@ export function AppearancePopover(props: AppearancePopoverProps) {
243
253
  >
244
254
  <Palette />
245
255
  </PopoverTrigger>
246
- <PopoverContent class="w-lg max-w-[calc(100vw-2rem)] max-h-(--kb-popper-content-available-height) overflow-y-auto">
256
+ <PopoverContent
257
+ ref={setMenuPortalMount}
258
+ class="w-lg max-w-[calc(100vw-2rem)] max-h-(--kb-popper-content-available-height) overflow-y-auto"
259
+ >
247
260
  <PopoverTitle>Appearance</PopoverTitle>
248
261
  <PopoverDescription>
249
262
  Changes preview immediately. Save keeps them; Cancel restores the previous appearance.
250
263
  </PopoverDescription>
251
- <AppearanceEditor {...props} />
264
+ <AppearanceEditor {...props} menuPortalMount={menuPortalMount()} />
252
265
  </PopoverContent>
253
266
  </Popover>
254
267
  )
@@ -1,4 +1,5 @@
1
1
  import type { AccentPreset, AdeaTheme, AdeaThemeRecord } from '@adea-ai/themes'
2
+ import type { AppearanceEditorFontSettings } from '#lib/appearance-font-settings'
2
3
 
3
4
  export type AppearanceDraft = Readonly<{
4
5
  mode: 'system' | 'light' | 'dark'
@@ -13,6 +14,12 @@ export type AppearanceDraft = Readonly<{
13
14
  terminalThemeId?: string
14
15
  /** Theme default, a canonical preset id, or the host's custom accent draft. */
15
16
  accent: string
17
+ /**
18
+ * The UI, content and code typeface axes. Optional for compatibility with
19
+ * hosts upgrading from the earlier appearance contract; omitted means the
20
+ * shared System defaults.
21
+ */
22
+ fonts?: AppearanceEditorFontSettings
16
23
  surface: 'theme' | 'frosted' | 'opaque'
17
24
  reduceTransparency: boolean
18
25
  }>
@@ -45,6 +52,8 @@ export type AppearanceEditorProps = {
45
52
  surfaceCapability: { frosted: boolean; reason?: string; themeDefaultDescription?: string }
46
53
  /** Undefined means theme import is unavailable, never a working-looking action. */
47
54
  onManageThemes?: () => void
55
+ /** Mount nested font menus inside the containing overlay when one exists. */
56
+ menuPortalMount?: HTMLElement
48
57
  class?: string
49
58
  }
50
59