@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.
- package/README.md +44 -8
- package/dist/components/composites/action-button/action-button.d.ts.map +1 -1
- package/dist/components/composites/action-button/action-button.js +6 -8
- package/dist/components/composites/action-button/action-button.js.map +1 -1
- package/dist/components/composites/appearance-editor/accent-swatch-groups.d.ts +16 -0
- package/dist/components/composites/appearance-editor/accent-swatch-groups.d.ts.map +1 -0
- package/dist/components/composites/appearance-editor/accent-swatch-groups.js +151 -0
- package/dist/components/composites/appearance-editor/accent-swatch-groups.js.map +1 -0
- package/dist/components/composites/appearance-editor/appearance-choices.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-choices.js +24 -84
- package/dist/components/composites/appearance-editor/appearance-choices.js.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-editor.d.ts +2 -2
- package/dist/components/composites/appearance-editor/appearance-editor.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-editor.js +25 -7
- package/dist/components/composites/appearance-editor/appearance-editor.js.map +1 -1
- package/dist/components/composites/appearance-editor/appearance-types.d.ts +9 -0
- package/dist/components/composites/appearance-editor/appearance-types.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/font-settings-group.d.ts +10 -0
- package/dist/components/composites/appearance-editor/font-settings-group.d.ts.map +1 -0
- package/dist/components/composites/appearance-editor/font-settings-group.js +310 -0
- package/dist/components/composites/appearance-editor/font-settings-group.js.map +1 -0
- package/dist/components/composites/appearance-editor/index.d.ts +2 -0
- package/dist/components/composites/appearance-editor/index.d.ts.map +1 -1
- package/dist/components/composites/appearance-editor/index.js +3 -1
- package/dist/components/composites/update-dialog/update-dialog.js +2 -2
- package/dist/components/composites/update-dialog/update-dialog.js.map +1 -1
- package/dist/components/conversation/message-row.d.ts.map +1 -1
- package/dist/components/conversation/message-row.js +3 -3
- package/dist/components/conversation/message-row.js.map +1 -1
- package/dist/components/conversation/paste-token-editor.js +1 -1
- package/dist/components/conversation/paste-token-editor.js.map +1 -1
- package/dist/components/layout/side-rail/side-rail.js +2 -2
- package/dist/components/layout/side-rail/side-rail.js.map +1 -1
- package/dist/components/layout/top-bar/top-bar.js +1 -1
- package/dist/components/layout/top-bar/top-bar.js.map +1 -1
- package/dist/components/theme/appearance-panel.d.ts +1 -1
- package/dist/components/theme/appearance-panel.d.ts.map +1 -1
- package/dist/components/theme/appearance-panel.js +45 -105
- package/dist/components/theme/appearance-panel.js.map +1 -1
- package/dist/components/theme/theme-provider.d.ts +5 -2
- package/dist/components/theme/theme-provider.d.ts.map +1 -1
- package/dist/components/theme/theme-provider.js +38 -19
- package/dist/components/theme/theme-provider.js.map +1 -1
- package/dist/components/ui/code-block/code-block.d.ts +3 -3
- package/dist/components/ui/code-block/code-block.js +6 -6
- package/dist/components/ui/code-block/code-block.js.map +1 -1
- package/dist/components/ui/command/command.js +1 -1
- package/dist/components/ui/command/command.js.map +1 -1
- package/dist/components/ui/context-menu/context-menu.js +1 -1
- package/dist/components/ui/context-menu/context-menu.js.map +1 -1
- package/dist/components/ui/diff-block/diff-block.js +5 -5
- package/dist/components/ui/diff-block/diff-block.js.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.d.ts.map +1 -1
- package/dist/components/ui/dropdown-menu/dropdown-menu.js +101 -8
- package/dist/components/ui/dropdown-menu/dropdown-menu.js.map +1 -1
- package/dist/components/ui/kbd/kbd.js +1 -1
- package/dist/components/ui/kbd/kbd.js.map +1 -1
- package/dist/components/ui/menubar/menubar.js +1 -1
- package/dist/components/ui/menubar/menubar.js.map +1 -1
- package/dist/components/ui/typography/typography.d.ts +3 -5
- package/dist/components/ui/typography/typography.d.ts.map +1 -1
- package/dist/components/ui/typography/typography.js +6 -8
- package/dist/components/ui/typography/typography.js.map +1 -1
- package/dist/index.js +6 -3
- package/dist/lib/appearance-font-settings.d.ts +53 -0
- package/dist/lib/appearance-font-settings.d.ts.map +1 -0
- package/dist/lib/appearance-font-settings.js +160 -0
- package/dist/lib/appearance-font-settings.js.map +1 -0
- package/dist/lib/font-catalog.d.ts +72 -0
- package/dist/lib/font-catalog.d.ts.map +1 -0
- package/dist/lib/font-catalog.js +71 -0
- package/dist/lib/font-catalog.js.map +1 -0
- package/dist/lib/safe-script.d.ts +3 -0
- package/dist/lib/safe-script.d.ts.map +1 -0
- package/dist/lib/safe-script.js +9 -0
- package/dist/lib/safe-script.js.map +1 -0
- package/dist/lib/tokens.d.ts +2 -19
- package/dist/lib/tokens.d.ts.map +1 -1
- package/dist/lib/tokens.js +63 -39
- package/dist/lib/tokens.js.map +1 -1
- package/dist/r/appearance-editor.json +11 -0
- package/dist/r/lib.json +15 -0
- package/dist/r/registry.json +33 -0
- package/dist/r/src/components/composites/action-button/action-button.tsx +2 -1
- package/dist/r/src/components/composites/appearance-editor/accent-swatch-groups.tsx +147 -0
- package/dist/r/src/components/composites/appearance-editor/appearance-choices.tsx +17 -74
- package/dist/r/src/components/composites/appearance-editor/appearance-editor.tsx +19 -6
- package/dist/r/src/components/composites/appearance-editor/appearance-types.ts +9 -0
- package/dist/r/src/components/composites/appearance-editor/font-settings-group.tsx +350 -0
- package/dist/r/src/components/composites/appearance-editor/index.ts +19 -0
- package/dist/r/src/components/composites/update-dialog/update-dialog.tsx +2 -2
- package/dist/r/src/components/conversation/message-row.tsx +7 -3
- package/dist/r/src/components/conversation/paste-token-editor.tsx +1 -1
- package/dist/r/src/components/layout/side-rail/side-rail.tsx +3 -3
- package/dist/r/src/components/layout/top-bar/top-bar.tsx +1 -1
- package/dist/r/src/components/theme/appearance-panel.tsx +34 -83
- package/dist/r/src/components/theme/theme-provider.tsx +50 -20
- package/dist/r/src/components/ui/code-block/code-block.tsx +6 -6
- package/dist/r/src/components/ui/command/command.tsx +1 -1
- package/dist/r/src/components/ui/context-menu/context-menu.tsx +1 -1
- package/dist/r/src/components/ui/diff-block/diff-block.tsx +7 -7
- package/dist/r/src/components/ui/dropdown-menu/dropdown-menu.tsx +145 -7
- package/dist/r/src/components/ui/kbd/kbd.tsx +1 -1
- package/dist/r/src/components/ui/menubar/menubar.tsx +1 -1
- package/dist/r/src/components/ui/typography/typography.tsx +6 -8
- package/dist/r/src/lib/appearance-font-settings.ts +233 -0
- package/dist/r/src/lib/font-catalog.ts +80 -0
- package/dist/r/src/lib/safe-script.ts +7 -0
- package/dist/r/src/lib/tokens.ts +64 -59
- package/dist/r/src/styles/appearance-font-settings.css +110 -0
- package/dist/r/src/styles/fonts.css +4 -5
- package/dist/r/src/styles/globals.css +4 -1
- package/dist/r/src/styles/theme.css +14 -0
- package/dist/r/theme.json +5 -0
- package/dist/r/theming.json +2 -0
- package/package.json +2 -1
- package/registry.json +33 -0
- package/src/components/composites/action-button/action-button.tsx +2 -1
- package/src/components/composites/appearance-editor/accent-swatch-groups.tsx +147 -0
- package/src/components/composites/appearance-editor/appearance-choices.tsx +17 -74
- package/src/components/composites/appearance-editor/appearance-editor.tsx +19 -6
- package/src/components/composites/appearance-editor/appearance-types.ts +9 -0
- package/src/components/composites/appearance-editor/font-settings-group.tsx +350 -0
- package/src/components/composites/appearance-editor/index.ts +19 -0
- package/src/components/composites/update-dialog/update-dialog.tsx +2 -2
- package/src/components/conversation/message-row.tsx +7 -3
- package/src/components/conversation/paste-token-editor.tsx +1 -1
- package/src/components/layout/side-rail/side-rail.tsx +3 -3
- package/src/components/layout/top-bar/top-bar.tsx +1 -1
- package/src/components/theme/appearance-panel.tsx +34 -83
- package/src/components/theme/theme-provider.tsx +50 -20
- package/src/components/ui/code-block/code-block.tsx +6 -6
- package/src/components/ui/command/command.tsx +1 -1
- package/src/components/ui/context-menu/context-menu.tsx +1 -1
- package/src/components/ui/diff-block/diff-block.tsx +7 -7
- package/src/components/ui/dropdown-menu/dropdown-menu.tsx +145 -7
- package/src/components/ui/kbd/kbd.tsx +1 -1
- package/src/components/ui/menubar/menubar.tsx +1 -1
- package/src/components/ui/typography/typography.tsx +6 -8
- package/src/lib/appearance-font-settings.ts +233 -0
- package/src/lib/font-catalog.ts +80 -0
- package/src/lib/safe-script.ts +7 -0
- package/src/lib/tokens.ts +64 -59
- package/src/styles/appearance-font-settings.css +110 -0
- package/src/styles/fonts.css +4 -5
- package/src/styles/globals.css +4 -1
- package/src/styles/theme.css +14 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { BookOpen, ChevronDown, Code2, PanelsTopLeft } from 'lucide-solid'
|
|
2
|
+
import { For, createMemo, createSignal, createUniqueId } from 'solid-js'
|
|
3
|
+
import { fontOptions } from '#lib/font-catalog'
|
|
4
|
+
import { Button } from '../../ui/button/button'
|
|
5
|
+
import {
|
|
6
|
+
DropdownMenu,
|
|
7
|
+
DropdownMenuContent,
|
|
8
|
+
DropdownMenuRadioGroup,
|
|
9
|
+
DropdownMenuRadioItem,
|
|
10
|
+
DropdownMenuSeparator,
|
|
11
|
+
DropdownMenuTrigger,
|
|
12
|
+
} from '../../ui/dropdown-menu/dropdown-menu'
|
|
13
|
+
import { InputControl } from '../../ui/input/input-control'
|
|
14
|
+
import { SettingsRow } from './appearance-rows'
|
|
15
|
+
import {
|
|
16
|
+
APPEARANCE_EDITOR_FONT_SIZE_MAX,
|
|
17
|
+
APPEARANCE_EDITOR_FONT_SIZE_MIN,
|
|
18
|
+
normalizeAppearanceEditorFontSettings,
|
|
19
|
+
normalizeAppearanceEditorFontSize,
|
|
20
|
+
type AppearanceEditorFontAxis,
|
|
21
|
+
type AppearanceEditorFontFamilyId,
|
|
22
|
+
type AppearanceEditorFontSettings,
|
|
23
|
+
} from '#lib/appearance-font-settings'
|
|
24
|
+
|
|
25
|
+
export type AppearanceFontSettingsGroupProps = {
|
|
26
|
+
settings: AppearanceEditorFontSettings
|
|
27
|
+
menuPortalMount?: HTMLElement
|
|
28
|
+
onChange: (settings: AppearanceEditorFontSettings) => void
|
|
29
|
+
disabled?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const FONT_ROWS = [
|
|
33
|
+
{
|
|
34
|
+
axis: 'ui',
|
|
35
|
+
label: 'UI',
|
|
36
|
+
description: 'Controls and interface labels.',
|
|
37
|
+
icon: PanelsTopLeft,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
axis: 'content',
|
|
41
|
+
label: 'Content',
|
|
42
|
+
description: 'Reading and long-form text.',
|
|
43
|
+
icon: BookOpen,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
axis: 'code',
|
|
47
|
+
label: 'Code',
|
|
48
|
+
description: 'Code, terminals, and file paths.',
|
|
49
|
+
icon: Code2,
|
|
50
|
+
},
|
|
51
|
+
] as const satisfies readonly {
|
|
52
|
+
axis: AppearanceEditorFontAxis
|
|
53
|
+
label: string
|
|
54
|
+
description: string
|
|
55
|
+
icon: typeof PanelsTopLeft
|
|
56
|
+
}[]
|
|
57
|
+
|
|
58
|
+
const catalogFontOptions = fontOptions.filter((option) => option.id !== 'system')
|
|
59
|
+
const MENU_FOCUS_TARGET_SELECTOR =
|
|
60
|
+
'a[href], area[href], button, input, select, textarea, iframe, [tabindex], [contenteditable], details > summary:first-of-type'
|
|
61
|
+
|
|
62
|
+
function isMenuTabStop(element: HTMLElement) {
|
|
63
|
+
return (
|
|
64
|
+
(element.tabIndex >= 0 ||
|
|
65
|
+
(element.matches('details > summary:first-of-type') && !element.hasAttribute('tabindex'))) &&
|
|
66
|
+
!element.matches(':disabled') &&
|
|
67
|
+
element.getAttribute('aria-disabled') !== 'true' &&
|
|
68
|
+
element.closest('[hidden], [inert], [aria-hidden="true"]') === null &&
|
|
69
|
+
element.getClientRects().length > 0
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function FontFamilyMenu(props: {
|
|
74
|
+
label: string
|
|
75
|
+
family: AppearanceEditorFontFamilyId
|
|
76
|
+
menuPortalMount?: HTMLElement
|
|
77
|
+
disabled?: boolean
|
|
78
|
+
onSelect: (family: AppearanceEditorFontFamilyId) => void
|
|
79
|
+
}) {
|
|
80
|
+
const [menuOpen, setMenuOpen] = createSignal(false)
|
|
81
|
+
const [restoreTriggerFocus, setRestoreTriggerFocus] = createSignal(false)
|
|
82
|
+
const [triggerElement, setTriggerElement] = createSignal<HTMLButtonElement>()
|
|
83
|
+
let focusTargetAfterTab: HTMLElement | undefined
|
|
84
|
+
|
|
85
|
+
const moveFocusOnTab = (event: KeyboardEvent) => {
|
|
86
|
+
if (event.key !== 'Tab' || event.isComposing) return
|
|
87
|
+
|
|
88
|
+
const trigger = triggerElement()
|
|
89
|
+
const owner = props.menuPortalMount
|
|
90
|
+
const parentDialog = owner?.closest<HTMLElement>('[role="dialog"]')
|
|
91
|
+
const focusScope = parentDialog ?? owner?.ownerDocument.body
|
|
92
|
+
if (!trigger || !parentDialog || !focusScope) return
|
|
93
|
+
|
|
94
|
+
const focusStops = Array.from(focusScope.querySelectorAll<HTMLElement>('*')).filter(
|
|
95
|
+
(element) =>
|
|
96
|
+
isMenuTabStop(element) &&
|
|
97
|
+
element.closest('[role="menu"], [hidden], [inert], [aria-hidden="true"]') === null
|
|
98
|
+
)
|
|
99
|
+
const orderedFocusStops = [
|
|
100
|
+
...focusStops
|
|
101
|
+
.filter((element) => element.tabIndex > 0)
|
|
102
|
+
.toSorted((a, b) => a.tabIndex - b.tabIndex),
|
|
103
|
+
...focusStops.filter((element) => element.tabIndex <= 0),
|
|
104
|
+
]
|
|
105
|
+
const triggerIndex = orderedFocusStops.indexOf(trigger)
|
|
106
|
+
if (triggerIndex < 0) return
|
|
107
|
+
const direction = event.shiftKey ? -1 : 1
|
|
108
|
+
const adjacent =
|
|
109
|
+
orderedFocusStops[triggerIndex + direction] ??
|
|
110
|
+
(direction > 0 ? orderedFocusStops[0] : orderedFocusStops.at(-1))
|
|
111
|
+
|
|
112
|
+
event.stopPropagation()
|
|
113
|
+
setRestoreTriggerFocus(false)
|
|
114
|
+
if (adjacent) {
|
|
115
|
+
event.preventDefault()
|
|
116
|
+
focusTargetAfterTab = adjacent
|
|
117
|
+
setMenuOpen(false)
|
|
118
|
+
adjacent.focus({ preventScroll: true })
|
|
119
|
+
} else {
|
|
120
|
+
focusTargetAfterTab = undefined
|
|
121
|
+
setMenuOpen(false)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const selectedLabel = () =>
|
|
125
|
+
fontOptions.find((option) => option.id === props.family)?.label ??
|
|
126
|
+
fontOptions.find((option) => option.id === 'system')?.label ??
|
|
127
|
+
'System'
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<DropdownMenu
|
|
131
|
+
modal={false}
|
|
132
|
+
open={menuOpen()}
|
|
133
|
+
onOpenChange={(open) => {
|
|
134
|
+
setMenuOpen(open)
|
|
135
|
+
if (open) setRestoreTriggerFocus(false)
|
|
136
|
+
}}
|
|
137
|
+
>
|
|
138
|
+
<DropdownMenuTrigger
|
|
139
|
+
ref={setTriggerElement}
|
|
140
|
+
as={Button}
|
|
141
|
+
variant="outline"
|
|
142
|
+
size="sm"
|
|
143
|
+
aria-label={`${props.label} font family`}
|
|
144
|
+
disabled={props.disabled}
|
|
145
|
+
class="w-40 max-w-full justify-between"
|
|
146
|
+
>
|
|
147
|
+
<span class="truncate">{selectedLabel()}</span>
|
|
148
|
+
<ChevronDown aria-hidden="true" class="ms-2 size-4 shrink-0 text-muted-foreground" />
|
|
149
|
+
</DropdownMenuTrigger>
|
|
150
|
+
<DropdownMenuContent
|
|
151
|
+
hideArrow
|
|
152
|
+
portalMount={props.menuPortalMount}
|
|
153
|
+
class="min-w-48"
|
|
154
|
+
onFocusOutside={(event) => {
|
|
155
|
+
// WebKit may briefly focus the owning Popover root while the menu's
|
|
156
|
+
// focus scope starts. Keep the menu open for that transient target;
|
|
157
|
+
// genuine focus moves to another control still dismiss it.
|
|
158
|
+
if (
|
|
159
|
+
!event.defaultPrevented &&
|
|
160
|
+
props.menuPortalMount &&
|
|
161
|
+
event.detail.originalEvent.target === props.menuPortalMount
|
|
162
|
+
) {
|
|
163
|
+
setRestoreTriggerFocus(true)
|
|
164
|
+
event.preventDefault()
|
|
165
|
+
} else if (
|
|
166
|
+
event.detail.originalEvent.target instanceof HTMLElement &&
|
|
167
|
+
props.menuPortalMount?.contains(event.detail.originalEvent.target)
|
|
168
|
+
) {
|
|
169
|
+
setRestoreTriggerFocus(false)
|
|
170
|
+
}
|
|
171
|
+
}}
|
|
172
|
+
onInteractOutside={(event) => {
|
|
173
|
+
if (event.detail.originalEvent.type !== 'pointerdown') return
|
|
174
|
+
const owner = props.menuPortalMount
|
|
175
|
+
const target = event.detail.originalEvent.target
|
|
176
|
+
if (!(target instanceof Element) || !owner?.contains(target)) return
|
|
177
|
+
|
|
178
|
+
const focusTarget = target.closest<HTMLElement>(MENU_FOCUS_TARGET_SELECTOR)
|
|
179
|
+
setRestoreTriggerFocus(!focusTarget || !isMenuTabStop(focusTarget))
|
|
180
|
+
}}
|
|
181
|
+
onKeyDown={moveFocusOnTab}
|
|
182
|
+
onEscapeKeyDown={() => setRestoreTriggerFocus(true)}
|
|
183
|
+
onCloseAutoFocus={(event) => {
|
|
184
|
+
if (menuOpen()) {
|
|
185
|
+
event.preventDefault()
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
if (focusTargetAfterTab) {
|
|
189
|
+
const focusTarget = focusTargetAfterTab
|
|
190
|
+
focusTargetAfterTab = undefined
|
|
191
|
+
event.preventDefault()
|
|
192
|
+
queueMicrotask(() => {
|
|
193
|
+
if (!menuOpen() && focusTarget.isConnected) {
|
|
194
|
+
focusTarget.focus({ preventScroll: true })
|
|
195
|
+
}
|
|
196
|
+
})
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
if (!restoreTriggerFocus()) return
|
|
200
|
+
|
|
201
|
+
event.preventDefault()
|
|
202
|
+
setRestoreTriggerFocus(false)
|
|
203
|
+
const trigger = triggerElement()
|
|
204
|
+
if (
|
|
205
|
+
trigger?.isConnected &&
|
|
206
|
+
!trigger.disabled &&
|
|
207
|
+
trigger.closest('[hidden], [inert], [aria-hidden="true"]') === null &&
|
|
208
|
+
trigger.getClientRects().length > 0
|
|
209
|
+
) {
|
|
210
|
+
trigger.focus({ preventScroll: true })
|
|
211
|
+
}
|
|
212
|
+
}}
|
|
213
|
+
>
|
|
214
|
+
<DropdownMenuRadioGroup
|
|
215
|
+
value={props.family}
|
|
216
|
+
aria-label={`${props.label} system font`}
|
|
217
|
+
onChange={(family) => {
|
|
218
|
+
if (family === 'system') props.onSelect('system')
|
|
219
|
+
}}
|
|
220
|
+
>
|
|
221
|
+
<DropdownMenuRadioItem value="system" closeOnSelect>
|
|
222
|
+
System
|
|
223
|
+
</DropdownMenuRadioItem>
|
|
224
|
+
</DropdownMenuRadioGroup>
|
|
225
|
+
<DropdownMenuSeparator />
|
|
226
|
+
<DropdownMenuRadioGroup
|
|
227
|
+
value={props.family}
|
|
228
|
+
aria-label={`${props.label} available fonts`}
|
|
229
|
+
onChange={(family) => {
|
|
230
|
+
const option = fontOptions.find((candidate) => candidate.id === family)
|
|
231
|
+
if (option && option.id !== 'system') props.onSelect(option.id)
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
<For each={catalogFontOptions}>
|
|
235
|
+
{(option) => (
|
|
236
|
+
<DropdownMenuRadioItem value={option.id} closeOnSelect>
|
|
237
|
+
{option.label}
|
|
238
|
+
</DropdownMenuRadioItem>
|
|
239
|
+
)}
|
|
240
|
+
</For>
|
|
241
|
+
</DropdownMenuRadioGroup>
|
|
242
|
+
</DropdownMenuContent>
|
|
243
|
+
</DropdownMenu>
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Controlled font controls shared by AppearanceEditor and host-owned settings. */
|
|
248
|
+
export function AppearanceFontSettingsGroup(props: AppearanceFontSettingsGroupProps) {
|
|
249
|
+
const [sizeDrafts, setSizeDrafts] = createSignal<
|
|
250
|
+
Partial<Record<AppearanceEditorFontAxis, string>>
|
|
251
|
+
>({})
|
|
252
|
+
const sizeHintPrefix = createUniqueId()
|
|
253
|
+
const settings = createMemo(() => normalizeAppearanceEditorFontSettings(props.settings).settings)
|
|
254
|
+
|
|
255
|
+
const updateAxis = (
|
|
256
|
+
axis: AppearanceEditorFontAxis,
|
|
257
|
+
patch: { family?: AppearanceEditorFontFamilyId; size?: number }
|
|
258
|
+
) => {
|
|
259
|
+
const current = settings()
|
|
260
|
+
props.onChange({
|
|
261
|
+
...current,
|
|
262
|
+
[axis]: {
|
|
263
|
+
...current[axis],
|
|
264
|
+
...patch,
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const commitSize = (axis: AppearanceEditorFontAxis) => {
|
|
270
|
+
const input = sizeDrafts()[axis]
|
|
271
|
+
if (input === undefined) return
|
|
272
|
+
const size = normalizeAppearanceEditorFontSize(input, settings()[axis].size)
|
|
273
|
+
setSizeDrafts((current) => {
|
|
274
|
+
const next = { ...current }
|
|
275
|
+
delete next[axis]
|
|
276
|
+
return next
|
|
277
|
+
})
|
|
278
|
+
if (size !== settings()[axis].size) updateAxis(axis, { size })
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return (
|
|
282
|
+
<div class="global-appearance-font-settings">
|
|
283
|
+
<For each={FONT_ROWS}>
|
|
284
|
+
{(row) => {
|
|
285
|
+
const Icon = row.icon
|
|
286
|
+
const value = () => settings()[row.axis]
|
|
287
|
+
const inputValue = () => sizeDrafts()[row.axis] ?? String(value().size)
|
|
288
|
+
const sizeHintId = `${sizeHintPrefix}-${row.axis}-font-size-help`
|
|
289
|
+
const sizeIsInvalid = () => {
|
|
290
|
+
const draft = sizeDrafts()[row.axis]
|
|
291
|
+
if (draft === undefined || draft.trim() === '') return false
|
|
292
|
+
const parsed = Number(draft)
|
|
293
|
+
return (
|
|
294
|
+
!Number.isFinite(parsed) ||
|
|
295
|
+
!Number.isInteger(parsed) ||
|
|
296
|
+
parsed < APPEARANCE_EDITOR_FONT_SIZE_MIN ||
|
|
297
|
+
parsed > APPEARANCE_EDITOR_FONT_SIZE_MAX
|
|
298
|
+
)
|
|
299
|
+
}
|
|
300
|
+
return (
|
|
301
|
+
<SettingsRow title={row.label} icon={<Icon />} description={row.description}>
|
|
302
|
+
<div class="flex min-w-0 max-w-full flex-wrap items-center gap-2">
|
|
303
|
+
<FontFamilyMenu
|
|
304
|
+
label={row.label}
|
|
305
|
+
family={value().family}
|
|
306
|
+
menuPortalMount={props.menuPortalMount}
|
|
307
|
+
disabled={props.disabled}
|
|
308
|
+
onSelect={(family) => updateAxis(row.axis, { family })}
|
|
309
|
+
/>
|
|
310
|
+
<div class="flex shrink-0 items-center gap-1">
|
|
311
|
+
<InputControl
|
|
312
|
+
type="number"
|
|
313
|
+
min={APPEARANCE_EDITOR_FONT_SIZE_MIN}
|
|
314
|
+
max={APPEARANCE_EDITOR_FONT_SIZE_MAX}
|
|
315
|
+
step={1}
|
|
316
|
+
value={inputValue()}
|
|
317
|
+
disabled={props.disabled}
|
|
318
|
+
aria-label={`${row.label} font size in pixels`}
|
|
319
|
+
aria-describedby={sizeHintId}
|
|
320
|
+
aria-invalid={sizeIsInvalid()}
|
|
321
|
+
class="w-16"
|
|
322
|
+
onInput={(event) =>
|
|
323
|
+
setSizeDrafts((current) => ({
|
|
324
|
+
...current,
|
|
325
|
+
[row.axis]: event.currentTarget.value,
|
|
326
|
+
}))
|
|
327
|
+
}
|
|
328
|
+
onBlur={() => commitSize(row.axis)}
|
|
329
|
+
onKeyDown={(event) => {
|
|
330
|
+
if (event.key !== 'Enter') return
|
|
331
|
+
event.preventDefault()
|
|
332
|
+
commitSize(row.axis)
|
|
333
|
+
}}
|
|
334
|
+
/>
|
|
335
|
+
<span aria-hidden="true" class="text-xs text-muted-foreground">
|
|
336
|
+
px
|
|
337
|
+
</span>
|
|
338
|
+
</div>
|
|
339
|
+
<span id={sizeHintId} class="sr-only">
|
|
340
|
+
Enter a whole number from {APPEARANCE_EDITOR_FONT_SIZE_MIN} to{' '}
|
|
341
|
+
{APPEARANCE_EDITOR_FONT_SIZE_MAX} pixels.
|
|
342
|
+
</span>
|
|
343
|
+
</div>
|
|
344
|
+
</SettingsRow>
|
|
345
|
+
)
|
|
346
|
+
}}
|
|
347
|
+
</For>
|
|
348
|
+
</div>
|
|
349
|
+
)
|
|
350
|
+
}
|
|
@@ -1,4 +1,23 @@
|
|
|
1
1
|
export { AppearanceEditor, AppearancePopover } from './appearance-editor'
|
|
2
|
+
export {
|
|
3
|
+
AppearanceFontSettingsGroup,
|
|
4
|
+
type AppearanceFontSettingsGroupProps,
|
|
5
|
+
} from './font-settings-group'
|
|
6
|
+
export {
|
|
7
|
+
APPEARANCE_EDITOR_FONT_AXES,
|
|
8
|
+
APPEARANCE_EDITOR_FONT_SIZE_MAX,
|
|
9
|
+
APPEARANCE_EDITOR_FONT_SIZE_MIN,
|
|
10
|
+
DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,
|
|
11
|
+
applyAppearanceFontSettings,
|
|
12
|
+
fontSettingsBootstrapScript,
|
|
13
|
+
normalizeAppearanceEditorFontSettings,
|
|
14
|
+
normalizeAppearanceEditorFontSize,
|
|
15
|
+
type AppearanceEditorFontAxis,
|
|
16
|
+
type AppearanceEditorFontFamilyId,
|
|
17
|
+
type AppearanceEditorFontSetting,
|
|
18
|
+
type AppearanceEditorFontSettings,
|
|
19
|
+
type NormalizedAppearanceEditorFontSettings,
|
|
20
|
+
} from '#lib/appearance-font-settings'
|
|
2
21
|
export type {
|
|
3
22
|
AppearanceDraft,
|
|
4
23
|
AppearanceEditorProps,
|
|
@@ -857,7 +857,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
|
|
|
857
857
|
stop, named by the heading above it, or a keyboard user cannot
|
|
858
858
|
scroll it (`scrollable-region-focusable`). */}
|
|
859
859
|
<div
|
|
860
|
-
class="max-h-52 overflow-y-auto rounded-xl border border-border bg-background/45 p-4 font-
|
|
860
|
+
class="max-h-52 overflow-y-auto rounded-xl border border-border bg-background/45 p-4 font-code text-code leading-5 whitespace-pre-wrap text-muted-foreground"
|
|
861
861
|
role="region"
|
|
862
862
|
aria-labelledby="update-release-notes"
|
|
863
863
|
tabindex="0"
|
|
@@ -886,7 +886,7 @@ export function UpdateDialog(props: UpdateDialogProps) {
|
|
|
886
886
|
stop, named by the heading above it, or a keyboard user cannot
|
|
887
887
|
scroll it (`scrollable-region-focusable`). */}
|
|
888
888
|
<div
|
|
889
|
-
class="max-h-72 overflow-y-auto rounded-xl border border-border bg-background/45 p-4 font-
|
|
889
|
+
class="max-h-72 overflow-y-auto rounded-xl border border-border bg-background/45 p-4 font-code text-code leading-5 whitespace-pre-wrap text-muted-foreground"
|
|
890
890
|
role="region"
|
|
891
891
|
aria-labelledby="update-changelog"
|
|
892
892
|
tabindex="0"
|
|
@@ -141,10 +141,14 @@ export function MessageRow(props: MessageRowProps) {
|
|
|
141
141
|
that repeats every speaker's name in every bubble is one nobody reads. */}
|
|
142
142
|
<span class="visually-hidden">{local.senderName}</span>
|
|
143
143
|
|
|
144
|
-
<div data-slot="message-body" class="text-
|
|
144
|
+
<div data-slot="message-body" class="font-content text-content">
|
|
145
145
|
<Show
|
|
146
146
|
when={!local.deleted}
|
|
147
|
-
fallback={
|
|
147
|
+
fallback={
|
|
148
|
+
<p class="font-content text-content text-muted-foreground italic">
|
|
149
|
+
Message deleted
|
|
150
|
+
</p>
|
|
151
|
+
}
|
|
148
152
|
>
|
|
149
153
|
{local.children}
|
|
150
154
|
</Show>
|
|
@@ -376,7 +380,7 @@ function Prose(props: { text: string }) {
|
|
|
376
380
|
const parts = () => props.text.split(/(`[^`\n]+`)/g).filter(Boolean)
|
|
377
381
|
|
|
378
382
|
return (
|
|
379
|
-
<p class="whitespace-pre-wrap">
|
|
383
|
+
<p class="font-content text-content whitespace-pre-wrap">
|
|
380
384
|
<For each={parts()}>
|
|
381
385
|
{(part) => (
|
|
382
386
|
<Show
|
|
@@ -668,7 +668,7 @@ export function PasteTokenEditor(props: PasteTokenEditorProps) {
|
|
|
668
668
|
data-slot="paste-token-preview"
|
|
669
669
|
class="bg-scrim text-scrim-foreground z-(--z-tooltip) w-fit max-w-64 rounded-md px-2 py-1 text-xs origin-(--kb-tooltip-content-transform-origin) text-balance data-expanded:animate-in data-expanded:fade-in-0 data-expanded:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-expanded:duration-150 data-closed:duration-100"
|
|
670
670
|
>
|
|
671
|
-
<pre class="m-0 max-h-48 max-w-64 overflow-y-auto whitespace-pre-wrap break-words font-
|
|
671
|
+
<pre class="m-0 max-h-48 max-w-64 overflow-y-auto whitespace-pre-wrap break-words font-code text-code">
|
|
672
672
|
{summary().text}
|
|
673
673
|
</pre>
|
|
674
674
|
<Show when={summary().hiddenLines > 0}>
|
|
@@ -301,7 +301,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
|
|
|
301
301
|
<Show when={local.shortcut && local.trailing === undefined}>
|
|
302
302
|
<span
|
|
303
303
|
aria-hidden="true"
|
|
304
|
-
class="text-sidebar-muted-foreground shrink-0 font-
|
|
304
|
+
class="text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden"
|
|
305
305
|
>
|
|
306
306
|
{local.shortcut}
|
|
307
307
|
</span>
|
|
@@ -337,7 +337,7 @@ export function SideRailItem<T extends ValidComponent = 'a'>(props: SideRailItem
|
|
|
337
337
|
</span>
|
|
338
338
|
{local.label}
|
|
339
339
|
<Show when={local.shortcut}>
|
|
340
|
-
<span class="text-muted-foreground shrink-0 font-
|
|
340
|
+
<span class="text-muted-foreground shrink-0 font-code text-code leading-none">
|
|
341
341
|
{local.shortcut}
|
|
342
342
|
</span>
|
|
343
343
|
</Show>
|
|
@@ -399,7 +399,7 @@ export function SideRailButton(
|
|
|
399
399
|
<Show when={local.shortcut}>
|
|
400
400
|
<span
|
|
401
401
|
aria-hidden="true"
|
|
402
|
-
class="text-sidebar-muted-foreground shrink-0 font-
|
|
402
|
+
class="text-sidebar-muted-foreground shrink-0 font-code text-code leading-none opacity-0 transition-opacity group-hover/rail-item:opacity-100 group-focus-visible/rail-item:opacity-100 group-data-[collapsed=true]/rail:hidden"
|
|
403
403
|
>
|
|
404
404
|
{local.shortcut}
|
|
405
405
|
</span>
|
|
@@ -166,7 +166,7 @@ export function TopBarSearch(props: TopBarSearchProps) {
|
|
|
166
166
|
<Show when={local.shortcut}>
|
|
167
167
|
<kbd
|
|
168
168
|
aria-hidden="true"
|
|
169
|
-
class="bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-
|
|
169
|
+
class="bg-muted ms-auto hidden h-5 shrink-0 items-center rounded-sm border border-border px-1.5 font-code text-code sm:flex"
|
|
170
170
|
>
|
|
171
171
|
{local.shortcut}
|
|
172
172
|
</kbd>
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import type { ComponentProps } from 'solid-js'
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { Show, splitProps } from 'solid-js'
|
|
3
|
+
import { RadioGroup as Radio } from '@kobalte/core/radio-group'
|
|
4
|
+
import { accentPresets } from '#lib/tokens'
|
|
4
5
|
import { themesForAppearance } from '#lib/themes'
|
|
6
|
+
import { DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS } from '#lib/appearance-font-settings'
|
|
5
7
|
import { cn } from '#lib/utils'
|
|
6
|
-
import {
|
|
8
|
+
import { AccentSwatchGroups } from '../composites/appearance-editor/accent-swatch-groups'
|
|
9
|
+
import { AppearanceFontSettingsGroup } from '../composites/appearance-editor/font-settings-group'
|
|
7
10
|
import { RadioGroup, RadioGroupItem } from '../ui/radio-group/radio-group'
|
|
8
11
|
import { ThemePicker } from './theme-picker'
|
|
9
12
|
import { useTheme } from './theme-provider'
|
|
@@ -26,7 +29,7 @@ import { Heading } from '../ui/typography'
|
|
|
26
29
|
* setting that keeps changing.
|
|
27
30
|
* 3. **Accent** — adea's presets, then the accents the selected theme pair
|
|
28
31
|
* carries itself, layered on whichever theme is active.
|
|
29
|
-
* 4. **
|
|
32
|
+
* 4. **Fonts** — separate families and sizes for interface, content and code.
|
|
30
33
|
*
|
|
31
34
|
* The panel reads and writes the provider's preference and keeps no state of its
|
|
32
35
|
* own, so it cannot disagree with what is applied.
|
|
@@ -39,6 +42,11 @@ const DARK_THEMES = themesForAppearance('dark')
|
|
|
39
42
|
export function AppearancePanel(props: AppearancePanelProps) {
|
|
40
43
|
const [local, rest] = splitProps(props, ['class'])
|
|
41
44
|
const { selection, setSelection, resolvedAppearance, themeAccents } = useTheme()
|
|
45
|
+
const selectedAccent = () =>
|
|
46
|
+
accentPresets.some((option) => option.id === selection().accent) ||
|
|
47
|
+
themeAccents().some((option) => option.id === selection().accent)
|
|
48
|
+
? selection().accent
|
|
49
|
+
: 'theme'
|
|
42
50
|
|
|
43
51
|
return (
|
|
44
52
|
<div data-slot="appearance-panel" class={cn('flex flex-col gap-8', local.class)} {...rest}>
|
|
@@ -101,99 +109,42 @@ export function AppearancePanel(props: AppearancePanelProps) {
|
|
|
101
109
|
unreadable.
|
|
102
110
|
</p>
|
|
103
111
|
</header>
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
)}
|
|
119
|
-
</For>
|
|
120
|
-
{/* The theme's own accents follow the presets. Their values are the
|
|
121
|
-
theme pair's, so the swatch shows the side that is active now. */}
|
|
122
|
-
<For each={themeAccents()}>
|
|
123
|
-
{(accent) => (
|
|
124
|
-
<AccentOption
|
|
125
|
-
id={accent.id}
|
|
126
|
-
label={`Theme ${accent.label.toLowerCase()}`}
|
|
127
|
-
checked={selection().accent === accent.id}
|
|
128
|
-
swatch={resolvedAppearance() === 'dark' ? accent.dark : accent.light}
|
|
129
|
-
onSelect={() => setSelection({ accent: accent.id })}
|
|
130
|
-
/>
|
|
131
|
-
)}
|
|
132
|
-
</For>
|
|
133
|
-
</div>
|
|
112
|
+
<Radio
|
|
113
|
+
value={selectedAccent()}
|
|
114
|
+
onChange={(accent) => setSelection({ accent })}
|
|
115
|
+
orientation="horizontal"
|
|
116
|
+
aria-label="Accent"
|
|
117
|
+
class="flex flex-col items-start gap-3"
|
|
118
|
+
>
|
|
119
|
+
<AccentSwatchGroups
|
|
120
|
+
accentOptions={accentPresets}
|
|
121
|
+
themeAccentOptions={themeAccents()}
|
|
122
|
+
resolvedAppearance={resolvedAppearance()}
|
|
123
|
+
themeDefaultColor="var(--primary)"
|
|
124
|
+
/>
|
|
125
|
+
</Radio>
|
|
134
126
|
</section>
|
|
135
127
|
|
|
136
|
-
{/* --- 4.
|
|
128
|
+
{/* --- 4. Fonts ------------------------------------------------------- */}
|
|
137
129
|
<section class="flex flex-col gap-3">
|
|
138
130
|
<header class="flex flex-col gap-0.5">
|
|
139
131
|
<Heading as="h2" size="card">
|
|
140
|
-
|
|
132
|
+
Fonts
|
|
141
133
|
</Heading>
|
|
142
134
|
<p class="max-w-prose text-sm text-muted-foreground">
|
|
143
|
-
|
|
144
|
-
the
|
|
135
|
+
Choose separate families and sizes for the interface, reading, and code. Font families
|
|
136
|
+
come from the shared catalogue and are self-hosted.
|
|
145
137
|
</p>
|
|
146
138
|
</header>
|
|
147
|
-
<
|
|
148
|
-
|
|
149
|
-
onChange={(
|
|
150
|
-
|
|
151
|
-
>
|
|
152
|
-
<For each={fontOptions}>
|
|
153
|
-
{(option) => (
|
|
154
|
-
<RadioGroupItem
|
|
155
|
-
value={option.id}
|
|
156
|
-
label={option.label}
|
|
157
|
-
description={option.description}
|
|
158
|
-
/>
|
|
159
|
-
)}
|
|
160
|
-
</For>
|
|
161
|
-
</RadioGroup>
|
|
139
|
+
<AppearanceFontSettingsGroup
|
|
140
|
+
settings={selection().fonts ?? DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS}
|
|
141
|
+
onChange={(fonts) => setSelection({ fonts })}
|
|
142
|
+
/>
|
|
162
143
|
</section>
|
|
163
144
|
</div>
|
|
164
145
|
)
|
|
165
146
|
}
|
|
166
147
|
|
|
167
|
-
function AccentOption(props: {
|
|
168
|
-
id: string
|
|
169
|
-
label: string
|
|
170
|
-
checked: boolean
|
|
171
|
-
swatch: string
|
|
172
|
-
onSelect: () => void
|
|
173
|
-
}) {
|
|
174
|
-
return (
|
|
175
|
-
<Button
|
|
176
|
-
as="button"
|
|
177
|
-
type="button"
|
|
178
|
-
variant="outline"
|
|
179
|
-
size="sm"
|
|
180
|
-
role="radio"
|
|
181
|
-
data-accent-option={props.id}
|
|
182
|
-
aria-checked={props.checked}
|
|
183
|
-
aria-label={props.label}
|
|
184
|
-
class={cn({ 'border-ring ring-3 ring-primary-subtle': props.checked })}
|
|
185
|
-
onClick={() => props.onSelect()}
|
|
186
|
-
>
|
|
187
|
-
<span
|
|
188
|
-
aria-hidden="true"
|
|
189
|
-
class="size-3 rounded-full border border-border"
|
|
190
|
-
style={{ 'background-color': props.swatch }}
|
|
191
|
-
/>
|
|
192
|
-
{props.label}
|
|
193
|
-
</Button>
|
|
194
|
-
)
|
|
195
|
-
}
|
|
196
|
-
|
|
197
148
|
function ThemeSection(props: {
|
|
198
149
|
title: string
|
|
199
150
|
description: string
|