@adea-ai/ui 0.105.0 → 0.106.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.
- 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 +12 -1
- package/dist/r/lib.json +16 -1
- package/dist/r/registry.json +36 -3
- 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 +3 -1
- package/package.json +3 -2
- package/registry.json +36 -3
- 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
|
@@ -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
|
|
@@ -8,7 +8,15 @@ import {
|
|
|
8
8
|
splitProps,
|
|
9
9
|
useContext,
|
|
10
10
|
} from 'solid-js'
|
|
11
|
-
import { accentPresets
|
|
11
|
+
import { accentPresets } from '../../lib/tokens'
|
|
12
|
+
import { safeScriptStringLiteral } from '../../lib/safe-script'
|
|
13
|
+
import {
|
|
14
|
+
applyAppearanceFontSettings,
|
|
15
|
+
DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,
|
|
16
|
+
fontSettingsBootstrapScript,
|
|
17
|
+
normalizeAppearanceEditorFontSettings,
|
|
18
|
+
type AppearanceEditorFontSettings,
|
|
19
|
+
} from '../../lib/appearance-font-settings'
|
|
12
20
|
import type { AccentPreset } from '@adea-ai/themes'
|
|
13
21
|
import {
|
|
14
22
|
accentVariables,
|
|
@@ -39,7 +47,7 @@ import {
|
|
|
39
47
|
* - **an accent** — adea's six presets, or one of the accents the selected theme
|
|
40
48
|
* pair carries itself (`ansi-blue`…), which re-colour the interactive roles on
|
|
41
49
|
* top of whichever theme is active.
|
|
42
|
-
* - **
|
|
50
|
+
* - **fonts** — separate family and size choices for UI, content and code.
|
|
43
51
|
*
|
|
44
52
|
* Four axes rather than one list of presets, because they compose: a user who wants
|
|
45
53
|
* Nord with a violet accent and the system font should not need someone to author
|
|
@@ -67,8 +75,10 @@ export type ThemeSelection = {
|
|
|
67
75
|
* not offer falls back to the variant's own primary.
|
|
68
76
|
*/
|
|
69
77
|
accent: string
|
|
70
|
-
/**
|
|
78
|
+
/** UI-family alias for consumers of the original single-font preference. */
|
|
71
79
|
font: string
|
|
80
|
+
/** UI, content and code font settings. Omitted legacy records are migrated from `font`. */
|
|
81
|
+
fonts?: AppearanceEditorFontSettings
|
|
72
82
|
/**
|
|
73
83
|
* `comfortable` is the system's default geometry; `compact` moves the control
|
|
74
84
|
* ladder down one rung.
|
|
@@ -84,7 +94,8 @@ export const defaultThemeSelection: ThemeSelection = {
|
|
|
84
94
|
lightThemeId: defaultLightThemeId,
|
|
85
95
|
darkThemeId: defaultDarkThemeId,
|
|
86
96
|
accent: 'theme',
|
|
87
|
-
font: '
|
|
97
|
+
font: 'system',
|
|
98
|
+
fonts: DEFAULT_APPEARANCE_EDITOR_FONT_SETTINGS,
|
|
88
99
|
density: 'comfortable',
|
|
89
100
|
}
|
|
90
101
|
|
|
@@ -153,6 +164,7 @@ function readSelection(storageKey: string): ThemeSelection {
|
|
|
153
164
|
const raw = preferenceStore()?.getItem(storageKey)
|
|
154
165
|
if (!raw) return defaultThemeSelection
|
|
155
166
|
const parsed = JSON.parse(raw) as Partial<ThemeSelection>
|
|
167
|
+
const fonts = normalizeAppearanceEditorFontSettings(parsed.fonts, parsed.font).settings
|
|
156
168
|
return {
|
|
157
169
|
appearance:
|
|
158
170
|
parsed.appearance === 'light' ||
|
|
@@ -179,9 +191,8 @@ function readSelection(storageKey: string): ThemeSelection {
|
|
|
179
191
|
isThemeAccentId(parsed.accent))
|
|
180
192
|
? parsed.accent
|
|
181
193
|
: defaultThemeSelection.accent,
|
|
182
|
-
font:
|
|
183
|
-
|
|
184
|
-
: defaultThemeSelection.font,
|
|
194
|
+
font: fonts.ui.family,
|
|
195
|
+
fonts,
|
|
185
196
|
// Every axis has to be read back here, or it is lost twice: the effect below
|
|
186
197
|
// removes the `data-density` that `themeScript` set for the first paint, and
|
|
187
198
|
// the next `setSelection` rewrites the store without it.
|
|
@@ -192,6 +203,26 @@ function readSelection(storageKey: string): ThemeSelection {
|
|
|
192
203
|
}
|
|
193
204
|
}
|
|
194
205
|
|
|
206
|
+
/** Keep the deprecated single-font view synchronized with the UI axis. */
|
|
207
|
+
function mergeThemeSelection(base: ThemeSelection, patch: Partial<ThemeSelection>): ThemeSelection {
|
|
208
|
+
const merged = { ...base, ...patch }
|
|
209
|
+
let fonts: AppearanceEditorFontSettings
|
|
210
|
+
|
|
211
|
+
if (patch.fonts !== undefined) {
|
|
212
|
+
fonts = normalizeAppearanceEditorFontSettings(patch.fonts).settings
|
|
213
|
+
} else if (patch.font !== undefined) {
|
|
214
|
+
const currentFonts = normalizeAppearanceEditorFontSettings(base.fonts, base.font).settings
|
|
215
|
+
fonts = normalizeAppearanceEditorFontSettings({
|
|
216
|
+
...currentFonts,
|
|
217
|
+
ui: { ...currentFonts.ui, family: patch.font },
|
|
218
|
+
}).settings
|
|
219
|
+
} else {
|
|
220
|
+
fonts = normalizeAppearanceEditorFontSettings(merged.fonts, merged.font).settings
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return { ...merged, fonts, font: fonts.ui.family }
|
|
224
|
+
}
|
|
225
|
+
|
|
195
226
|
export type ThemeProviderProps = {
|
|
196
227
|
children?: JSX.Element
|
|
197
228
|
/** Where the preference is persisted. Distinct per application. */
|
|
@@ -217,10 +248,9 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
217
248
|
const [local] = splitProps(props, ['children', 'storageKey', 'initial', 'selection'])
|
|
218
249
|
const storageKey = () => local.storageKey ?? 'adea-appearance'
|
|
219
250
|
|
|
220
|
-
const [selection, setSelectionState] = createSignal<ThemeSelection>(
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
251
|
+
const [selection, setSelectionState] = createSignal<ThemeSelection>(
|
|
252
|
+
mergeThemeSelection(readSelection(storageKey()), local.initial ?? {})
|
|
253
|
+
)
|
|
224
254
|
|
|
225
255
|
// The controlled half of the contract. A patch from the owner replaces the
|
|
226
256
|
// matching axes; the stored preference is untouched, since the owner is the one
|
|
@@ -228,7 +258,7 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
228
258
|
// value — is what keeps this subscribed for the provider's lifetime.
|
|
229
259
|
createEffect(() => {
|
|
230
260
|
const incoming = local.selection
|
|
231
|
-
if (incoming) setSelectionState((current) => (
|
|
261
|
+
if (incoming) setSelectionState((current) => mergeThemeSelection(current, incoming))
|
|
232
262
|
})
|
|
233
263
|
|
|
234
264
|
const [systemAppearance, setSystemAppearance] = createSignal<ThemeAppearance>('light')
|
|
@@ -284,8 +314,10 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
284
314
|
if (!accent) root.removeAttribute('data-accent')
|
|
285
315
|
else root.dataset['accent'] = selection().accent
|
|
286
316
|
|
|
287
|
-
|
|
288
|
-
|
|
317
|
+
applyAppearanceFontSettings(
|
|
318
|
+
root,
|
|
319
|
+
normalizeAppearanceEditorFontSettings(selection().fonts, selection().font).settings
|
|
320
|
+
)
|
|
289
321
|
|
|
290
322
|
// Density is a pure token shift, so unlike the colour axes there is nothing to
|
|
291
323
|
// compute: the attribute selects a block in `theme.css`. It is applied here for
|
|
@@ -306,7 +338,7 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
306
338
|
themes: builtinThemes,
|
|
307
339
|
themeAccents,
|
|
308
340
|
setSelection(patch) {
|
|
309
|
-
const next =
|
|
341
|
+
const next = mergeThemeSelection(selection(), patch)
|
|
310
342
|
setSelectionState(next)
|
|
311
343
|
try {
|
|
312
344
|
preferenceStore()?.setItem(storageKey(), JSON.stringify(next))
|
|
@@ -339,10 +371,7 @@ export function ThemeProvider(props: ThemeProviderProps) {
|
|
|
339
371
|
* was found; `tests/theme-script.test.ts` pins it so it cannot come back.
|
|
340
372
|
*/
|
|
341
373
|
export function inlineScriptLiteral(value: string): string {
|
|
342
|
-
return
|
|
343
|
-
/[<>\u2028\u2029]/g,
|
|
344
|
-
(character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`
|
|
345
|
-
)
|
|
374
|
+
return safeScriptStringLiteral(value)
|
|
346
375
|
}
|
|
347
376
|
|
|
348
377
|
/**
|
|
@@ -365,5 +394,6 @@ export function inlineScriptLiteral(value: string): string {
|
|
|
365
394
|
* documented there.
|
|
366
395
|
*/
|
|
367
396
|
export function themeScript(storageKey = 'adea-appearance'): string {
|
|
368
|
-
|
|
397
|
+
const script = `(function(){try{var s=localStorage.getItem(${inlineScriptLiteral(storageKey)});var p=s?JSON.parse(s):{};var a=p.appearance||'system';var d=a==='dark'||(a==='system'&&window.matchMedia('(prefers-color-scheme: dark)').matches);var r=document.documentElement;if(d)r.classList.add('dark');r.style.colorScheme=d?'dark':'light';if(p.accent&&p.accent!=='theme')r.dataset.accent=p.accent;if(p.density==='compact')r.dataset.density='compact';}catch(e){}})();`
|
|
398
|
+
return `${script}${fontSettingsBootstrapScript(storageKey)}`
|
|
369
399
|
}
|
|
@@ -156,7 +156,7 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
156
156
|
}}
|
|
157
157
|
>
|
|
158
158
|
<div class="flex items-center justify-between gap-2 px-3 py-1">
|
|
159
|
-
<span class="truncate font-
|
|
159
|
+
<span class="truncate font-code text-code text-muted-foreground">{label()}</span>
|
|
160
160
|
<Actions />
|
|
161
161
|
</div>
|
|
162
162
|
<div
|
|
@@ -170,7 +170,7 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
170
170
|
when={local.highlight}
|
|
171
171
|
fallback={
|
|
172
172
|
<pre class="m-0 px-3 py-2" style={{ 'line-height': local.lineHeight ?? '1.5' }}>
|
|
173
|
-
<code class="font-
|
|
173
|
+
<code class="font-code text-code">
|
|
174
174
|
<Show when={local.showLineNumbers} fallback={local.code.replace(/\n$/, '')}>
|
|
175
175
|
<For each={lines()}>
|
|
176
176
|
{(line, index) => (
|
|
@@ -217,9 +217,9 @@ export function CodeBlock(props: CodeBlockProps) {
|
|
|
217
217
|
* InlineCode.
|
|
218
218
|
*
|
|
219
219
|
* The `code` inside a sentence. It is a component rather than a class because it
|
|
220
|
-
* has to hold three properties at once: it
|
|
221
|
-
*
|
|
222
|
-
*
|
|
220
|
+
* has to hold three properties at once: it uses the shared code family and size
|
|
221
|
+
* preference, and it wraps rather than overflows a narrow column
|
|
222
|
+
* (`overflow-wrap: anywhere` — an identifier with
|
|
223
223
|
* no spaces is one long token), and it must be legible against whatever surface
|
|
224
224
|
* it lands on, which is why the fill is a token and not a fixed grey.
|
|
225
225
|
*/
|
|
@@ -229,7 +229,7 @@ export function InlineCode(props: ComponentProps<'code'>) {
|
|
|
229
229
|
return (
|
|
230
230
|
<code
|
|
231
231
|
class={cn(
|
|
232
|
-
'rounded-sm bg-muted px-[0.4em] py-[0.15em] font-
|
|
232
|
+
'rounded-sm bg-muted px-[0.4em] py-[0.15em] font-code text-code text-foreground [overflow-wrap:anywhere]',
|
|
233
233
|
local.class
|
|
234
234
|
)}
|
|
235
235
|
{...rest}
|
|
@@ -274,7 +274,7 @@ export function CommandShortcut(props: {
|
|
|
274
274
|
return (
|
|
275
275
|
<span
|
|
276
276
|
class={cn(
|
|
277
|
-
'text-muted-foreground ms-auto font-
|
|
277
|
+
'text-muted-foreground ms-auto font-code text-code tracking-widest group-data-[selected=true]/command-item:text-foreground',
|
|
278
278
|
props.class
|
|
279
279
|
)}
|
|
280
280
|
>
|
|
@@ -90,7 +90,7 @@ export function ContextMenuItem(
|
|
|
90
90
|
{local.shortcut ? (
|
|
91
91
|
<span
|
|
92
92
|
aria-hidden="true"
|
|
93
|
-
class="text-muted-foreground ms-auto font-
|
|
93
|
+
class="text-muted-foreground ms-auto font-code text-code tracking-widest"
|
|
94
94
|
>
|
|
95
95
|
{local.shortcut}
|
|
96
96
|
</span>
|
|
@@ -152,7 +152,7 @@ export function diffTargetPath(patch: string): string | null {
|
|
|
152
152
|
return null
|
|
153
153
|
}
|
|
154
154
|
|
|
155
|
-
export const diffLineVariants = cva('flex items-baseline font-
|
|
155
|
+
export const diffLineVariants = cva('flex items-baseline font-code text-code', {
|
|
156
156
|
variants: {
|
|
157
157
|
kind: {
|
|
158
158
|
add: 'bg-success-subtle text-foreground',
|
|
@@ -201,10 +201,10 @@ export function DiffBlock(props: DiffBlockProps) {
|
|
|
201
201
|
>
|
|
202
202
|
<div class="flex items-center justify-between gap-2 border-b border-border px-3 py-1">
|
|
203
203
|
<span class="flex min-w-0 items-baseline gap-2">
|
|
204
|
-
<span class="truncate font-
|
|
204
|
+
<span class="truncate font-code text-code text-foreground">{name()}</span>
|
|
205
205
|
{/* The counts are text, so the summary survives greyscale and a reader
|
|
206
206
|
who cannot separate the two line tones still gets the shape of it. */}
|
|
207
|
-
<span class="shrink-0 font-
|
|
207
|
+
<span class="shrink-0 font-code text-code text-muted-foreground">
|
|
208
208
|
<span class="text-foreground">+{stats().added}</span>{' '}
|
|
209
209
|
<span class="text-foreground">−{stats().removed}</span>
|
|
210
210
|
</span>
|
|
@@ -223,13 +223,13 @@ export function DiffBlock(props: DiffBlockProps) {
|
|
|
223
223
|
<div class={diffLineVariants({ kind: line.kind })}>
|
|
224
224
|
<Show when={numbered(line, local.showLineNumbers)}>
|
|
225
225
|
<span
|
|
226
|
-
class="w-10 shrink-0 pr-2 text-right text-
|
|
226
|
+
class="w-10 shrink-0 pr-2 text-right text-code text-muted-foreground tabular-nums select-none"
|
|
227
227
|
aria-hidden="true"
|
|
228
228
|
>
|
|
229
229
|
{line.oldLine ?? ''}
|
|
230
230
|
</span>
|
|
231
231
|
<span
|
|
232
|
-
class="w-10 shrink-0 pr-2 text-right text-
|
|
232
|
+
class="w-10 shrink-0 pr-2 text-right text-code text-muted-foreground tabular-nums select-none"
|
|
233
233
|
aria-hidden="true"
|
|
234
234
|
>
|
|
235
235
|
{line.newLine ?? ''}
|
|
@@ -271,13 +271,13 @@ export function DiffSummary(props: { patch: string; class?: string; onSelect?: (
|
|
|
271
271
|
<button
|
|
272
272
|
type="button"
|
|
273
273
|
class={cn(
|
|
274
|
-
'flex w-full items-baseline justify-between gap-3 rounded-md px-2 py-1 text-left font-
|
|
274
|
+
'flex w-full items-baseline justify-between gap-3 rounded-md px-2 py-1 text-left font-code text-code hover:bg-muted',
|
|
275
275
|
props.class
|
|
276
276
|
)}
|
|
277
277
|
onClick={() => props.onSelect?.()}
|
|
278
278
|
>
|
|
279
279
|
<span class="truncate text-foreground">{name()}</span>
|
|
280
|
-
<span class="shrink-0 text-
|
|
280
|
+
<span class="shrink-0 text-code">
|
|
281
281
|
<span class="text-foreground">+{stats().added}</span>{' '}
|
|
282
282
|
<span class="text-foreground">−{stats().removed}</span>
|
|
283
283
|
</span>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DropdownMenu as KobalteDropdownMenu } from '@kobalte/core/dropdown-menu'
|
|
2
2
|
import { Check, ChevronRight, Circle } from 'lucide-solid'
|
|
3
|
-
import type { ComponentProps } from 'solid-js'
|
|
4
|
-
import { splitProps } from 'solid-js'
|
|
3
|
+
import type { Accessor, ComponentProps } from 'solid-js'
|
|
4
|
+
import { createContext, splitProps, useContext } from 'solid-js'
|
|
5
5
|
import {
|
|
6
6
|
menuContentPadding,
|
|
7
7
|
destructiveMenuItem,
|
|
@@ -14,6 +14,18 @@ import {
|
|
|
14
14
|
} from '../../../lib/overlay'
|
|
15
15
|
import { cn } from '../../../lib/utils'
|
|
16
16
|
|
|
17
|
+
type MenuOrientation = 'horizontal' | 'vertical'
|
|
18
|
+
const DropdownMenuOrientationContext = createContext<Accessor<MenuOrientation | undefined>>()
|
|
19
|
+
type HandlerEvent<Handler> = Handler extends (...args: infer Arguments) => void
|
|
20
|
+
? Arguments[0]
|
|
21
|
+
: never
|
|
22
|
+
type DropdownMenuTriggerKeyEvent = HandlerEvent<
|
|
23
|
+
NonNullable<ComponentProps<typeof KobalteDropdownMenu.Trigger>['onKeyDown']>
|
|
24
|
+
>
|
|
25
|
+
type DropdownMenuTriggerPointerEvent = HandlerEvent<
|
|
26
|
+
NonNullable<ComponentProps<typeof KobalteDropdownMenu.Trigger>['onPointerDown']>
|
|
27
|
+
>
|
|
28
|
+
|
|
17
29
|
/**
|
|
18
30
|
* DropdownMenu.
|
|
19
31
|
*
|
|
@@ -34,12 +46,138 @@ import { cn } from '../../../lib/utils'
|
|
|
34
46
|
* with an AlertDialog is the caller's job.
|
|
35
47
|
*/
|
|
36
48
|
export function DropdownMenu(props: ComponentProps<typeof KobalteDropdownMenu>) {
|
|
37
|
-
const [local, rest] = splitProps(props, ['modal'])
|
|
38
|
-
|
|
49
|
+
const [local, rest] = splitProps(props, ['modal', 'orientation'])
|
|
50
|
+
const orientation = () => local.orientation
|
|
51
|
+
return (
|
|
52
|
+
<DropdownMenuOrientationContext.Provider value={orientation}>
|
|
53
|
+
<KobalteDropdownMenu modal={local.modal ?? false} orientation={local.orientation} {...rest} />
|
|
54
|
+
</DropdownMenuOrientationContext.Provider>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const SCROLLABLE_OVERFLOW = /(auto|scroll)/
|
|
59
|
+
const OVERFLOW_PROPERTIES = ['overflow', 'overflow-x', 'overflow-y'] as const
|
|
60
|
+
|
|
61
|
+
type OverflowProperty = (typeof OVERFLOW_PROPERTIES)[number]
|
|
62
|
+
type OverflowDeclaration = Readonly<{
|
|
63
|
+
property: OverflowProperty
|
|
64
|
+
value: string
|
|
65
|
+
priority: string
|
|
66
|
+
}>
|
|
67
|
+
|
|
68
|
+
function readInlineOverflow(element: HTMLElement): OverflowDeclaration[] {
|
|
69
|
+
return OVERFLOW_PROPERTIES.map((property) => ({
|
|
70
|
+
property,
|
|
71
|
+
value: element.style.getPropertyValue(property),
|
|
72
|
+
priority: element.style.getPropertyPriority(property),
|
|
73
|
+
}))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function declarationsMatch(element: HTMLElement, declarations: OverflowDeclaration[]) {
|
|
77
|
+
return declarations.every(
|
|
78
|
+
({ property, value, priority }) =>
|
|
79
|
+
element.style.getPropertyValue(property) === value &&
|
|
80
|
+
element.style.getPropertyPriority(property) === priority
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function bypassUnboundedScrollLookup(trigger: HTMLElement) {
|
|
85
|
+
const scrollRoot = document.scrollingElement ?? document.documentElement
|
|
86
|
+
if (window.getComputedStyle(scrollRoot).overflow !== 'hidden') return
|
|
87
|
+
|
|
88
|
+
const temporaryStyles: Array<{
|
|
89
|
+
element: HTMLElement
|
|
90
|
+
original: OverflowDeclaration[]
|
|
91
|
+
applied: OverflowDeclaration[]
|
|
92
|
+
}> = []
|
|
93
|
+
|
|
94
|
+
for (let element: HTMLElement | null = trigger; element;) {
|
|
95
|
+
const parent: HTMLElement | null = element.parentElement
|
|
96
|
+
if (element !== scrollRoot) {
|
|
97
|
+
const computed = window.getComputedStyle(element)
|
|
98
|
+
const overflow = `${computed.overflow} ${computed.overflowX} ${computed.overflowY}`
|
|
99
|
+
if (SCROLLABLE_OVERFLOW.test(overflow)) {
|
|
100
|
+
const original = readInlineOverflow(element)
|
|
101
|
+
element.style.setProperty('overflow', 'clip', 'important')
|
|
102
|
+
temporaryStyles.push({ element, original, applied: readInlineOverflow(element) })
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
element = parent
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (temporaryStyles.length === 0) return
|
|
109
|
+
|
|
110
|
+
// Kobalte's keyboard path synchronously walks scroll parents before it opens
|
|
111
|
+
// the menu. Clipping only non-root scroll ancestors makes that walk stop at
|
|
112
|
+
// the locked viewport. Restore those declarations once the event handler
|
|
113
|
+
// finishes, and leave any newer owner write untouched.
|
|
114
|
+
queueMicrotask(() => {
|
|
115
|
+
for (const { element, original, applied } of temporaryStyles) {
|
|
116
|
+
if (!element.isConnected || !declarationsMatch(element, applied)) continue
|
|
117
|
+
for (const property of OVERFLOW_PROPERTIES) element.style.removeProperty(property)
|
|
118
|
+
for (const { property, value, priority } of original) {
|
|
119
|
+
if (value) element.style.setProperty(property, value, priority)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
})
|
|
39
123
|
}
|
|
40
124
|
|
|
41
125
|
export function DropdownMenuTrigger(props: ComponentProps<typeof KobalteDropdownMenu.Trigger>) {
|
|
42
|
-
|
|
126
|
+
const inheritedOrientation = useContext(DropdownMenuOrientationContext)
|
|
127
|
+
const [local, rest] = splitProps(props, ['disabled', 'onKeyDown', 'onPointerDown'])
|
|
128
|
+
const onPointerDown = (event: DropdownMenuTriggerPointerEvent) => {
|
|
129
|
+
if (typeof local.onPointerDown === 'function') local.onPointerDown(event)
|
|
130
|
+
else if (local.onPointerDown) local.onPointerDown[0](local.onPointerDown[1], event)
|
|
131
|
+
|
|
132
|
+
// WebKit can leave focus on a previous control when a native button gets
|
|
133
|
+
// a pointer click. Focus the trigger before Kobalte opens the menu so an
|
|
134
|
+
// enclosing modal focus scope does not restore focus and dismiss the menu.
|
|
135
|
+
if (
|
|
136
|
+
local.disabled ||
|
|
137
|
+
event.defaultPrevented ||
|
|
138
|
+
!event.isPrimary ||
|
|
139
|
+
event.button !== 0 ||
|
|
140
|
+
event.altKey ||
|
|
141
|
+
event.ctrlKey ||
|
|
142
|
+
event.metaKey ||
|
|
143
|
+
event.shiftKey
|
|
144
|
+
) {
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
const trigger = event.currentTarget
|
|
148
|
+
const isNativeLink = trigger instanceof HTMLAnchorElement && trigger.hasAttribute('href')
|
|
149
|
+
if (trigger instanceof HTMLElement && !isNativeLink) {
|
|
150
|
+
trigger.focus({ preventScroll: true })
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const onKeyDown = (event: DropdownMenuTriggerKeyEvent) => {
|
|
154
|
+
// Kobalte's MenuTrigger does not consult defaultPrevented before its own
|
|
155
|
+
// open-key handling. Preserve its existing callback behavior; this wrapper
|
|
156
|
+
// only makes the pre-open scroll-parent lookup terminate.
|
|
157
|
+
if (typeof local.onKeyDown === 'function') local.onKeyDown(event)
|
|
158
|
+
else if (local.onKeyDown) local.onKeyDown[0](local.onKeyDown[1], event)
|
|
159
|
+
|
|
160
|
+
if (local.disabled) return
|
|
161
|
+
const trigger = event.currentTarget
|
|
162
|
+
if (!(trigger instanceof HTMLElement)) return
|
|
163
|
+
const menubarOrientation = trigger.closest('[role="menubar"]')?.getAttribute('aria-orientation')
|
|
164
|
+
const orientation =
|
|
165
|
+
inheritedOrientation?.() ?? (menubarOrientation === 'vertical' ? 'vertical' : 'horizontal')
|
|
166
|
+
const openingArrow = orientation === 'horizontal' ? 'ArrowDown' : 'ArrowRight'
|
|
167
|
+
if (!['Enter', ' ', openingArrow].includes(event.key)) return
|
|
168
|
+
const isNativeLink = trigger instanceof HTMLAnchorElement && trigger.hasAttribute('href')
|
|
169
|
+
if (isNativeLink && (event.key === 'Enter' || event.key === ' ')) return
|
|
170
|
+
bypassUnboundedScrollLookup(trigger)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return (
|
|
174
|
+
<KobalteDropdownMenu.Trigger
|
|
175
|
+
{...rest}
|
|
176
|
+
disabled={local.disabled}
|
|
177
|
+
onPointerDown={onPointerDown}
|
|
178
|
+
onKeyDown={onKeyDown}
|
|
179
|
+
/>
|
|
180
|
+
)
|
|
43
181
|
}
|
|
44
182
|
|
|
45
183
|
export function DropdownMenuPortal(props: ComponentProps<typeof KobalteDropdownMenu.Portal>) {
|
|
@@ -113,7 +251,7 @@ export function DropdownMenuItem(
|
|
|
113
251
|
{local.shortcut ? (
|
|
114
252
|
<span
|
|
115
253
|
aria-hidden="true"
|
|
116
|
-
class="text-muted-foreground ms-auto font-
|
|
254
|
+
class="text-muted-foreground ms-auto font-code text-code tracking-widest"
|
|
117
255
|
>
|
|
118
256
|
{local.shortcut}
|
|
119
257
|
</span>
|
|
@@ -139,7 +277,7 @@ export function DropdownMenuShortcut(props: ComponentProps<'span'>) {
|
|
|
139
277
|
<span
|
|
140
278
|
data-slot="dropdown-menu-shortcut"
|
|
141
279
|
aria-hidden="true"
|
|
142
|
-
class={cn('text-muted-foreground ms-auto font-
|
|
280
|
+
class={cn('text-muted-foreground ms-auto font-code text-code tracking-widest', local.class)}
|
|
143
281
|
{...rest}
|
|
144
282
|
/>
|
|
145
283
|
)
|
|
@@ -20,7 +20,7 @@ import { cn } from '../../../lib/utils'
|
|
|
20
20
|
* needs a gap between them that is a layout concern, not punctuation.
|
|
21
21
|
*/
|
|
22
22
|
export const kbdVariants = cva(
|
|
23
|
-
'bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 items-center justify-center gap-1 rounded-sm border border-border px-1 font-
|
|
23
|
+
'bg-muted text-muted-foreground pointer-events-none inline-flex h-5 min-w-5 items-center justify-center gap-1 rounded-sm border border-border px-1 font-code text-code font-medium select-none',
|
|
24
24
|
{
|
|
25
25
|
variants: {
|
|
26
26
|
size: {
|
|
@@ -123,7 +123,7 @@ export function MenubarItem(
|
|
|
123
123
|
{local.shortcut ? (
|
|
124
124
|
<span
|
|
125
125
|
aria-hidden="true"
|
|
126
|
-
class="text-muted-foreground ms-auto font-
|
|
126
|
+
class="text-muted-foreground ms-auto font-code text-code tracking-widest"
|
|
127
127
|
>
|
|
128
128
|
{local.shortcut}
|
|
129
129
|
</span>
|