@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
|
@@ -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>
|
|
@@ -119,11 +119,9 @@ export function Heading<T extends ValidComponent = 'h2'>(props: HeadingProps<T>)
|
|
|
119
119
|
/**
|
|
120
120
|
* Text.
|
|
121
121
|
*
|
|
122
|
-
* Everything that is not a heading.
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
* `micro` (11px, 500) is the floor — keyboard keys and the status bar. `code` is
|
|
126
|
-
* the mono face at 12px, for anything a user compares character by character.
|
|
122
|
+
* Everything that is not a heading. Body and strong copy use the content font
|
|
123
|
+
* axis; labels, captions and microcopy inherit the UI face. Code uses its own
|
|
124
|
+
* family and size axis for anything a user compares character by character.
|
|
127
125
|
*
|
|
128
126
|
* `tone` is a separate axis from `variant` because "secondary" is a colour
|
|
129
127
|
* decision, not a size: a muted caption and a muted body line are both common.
|
|
@@ -136,17 +134,17 @@ export const textVariants = cva('', {
|
|
|
136
134
|
variants: {
|
|
137
135
|
variant: {
|
|
138
136
|
/** 14px / 400. Body copy and descriptions. */
|
|
139
|
-
body: 'text-
|
|
137
|
+
body: 'font-content text-content',
|
|
140
138
|
/** 14px / 500. Labels, control text, list-row titles. */
|
|
141
139
|
label: 'text-sm font-medium',
|
|
142
140
|
/** 14px / 600. Emphasis inside body copy. */
|
|
143
|
-
strong: 'text-
|
|
141
|
+
strong: 'font-content text-content font-semibold',
|
|
144
142
|
/** 12px / 400. Metadata, secondary rows, units. */
|
|
145
143
|
caption: 'text-xs',
|
|
146
144
|
/** 11px / 500. The floor: keyboard keys, the status bar. */
|
|
147
145
|
micro: 'text-2xs font-medium',
|
|
148
146
|
/** 12px mono. Ids, paths, hashes, inline code. */
|
|
149
|
-
code: 'font-
|
|
147
|
+
code: 'font-code text-code',
|
|
150
148
|
},
|
|
151
149
|
tone: {
|
|
152
150
|
inherit: '',
|