feat(v3): redesign SettingsTab global settings with split font controls
Split single font setting into separate UI font (sans-serif options) and Terminal font (monospace options), each with custom themed dropdown and size stepper (8-24px). Single-column layout with Appearance and Defaults subsections. All native <select> replaced with custom themed dropdowns. Font previews render in their own typeface. New CSS vars: --term-font-family, --term-font-size. Setting keys changed from font_family/font_size to ui_font_family/ui_font_size + term_font_family/term_font_size.
This commit is contained in:
parent
fa7d0bd915
commit
36af9dd1d2
3 changed files with 360 additions and 223 deletions
|
|
@ -29,21 +29,40 @@
|
||||||
// Global settings
|
// Global settings
|
||||||
let defaultShell = $state('');
|
let defaultShell = $state('');
|
||||||
let defaultCwd = $state('');
|
let defaultCwd = $state('');
|
||||||
let fontFamily = $state('');
|
let uiFont = $state('');
|
||||||
let fontSize = $state('');
|
let uiFontSize = $state('');
|
||||||
|
let termFont = $state('');
|
||||||
|
let termFontSize = $state('');
|
||||||
let selectedTheme = $state<ThemeId>(getCurrentTheme());
|
let selectedTheme = $state<ThemeId>(getCurrentTheme());
|
||||||
let themeDropdownOpen = $state(false);
|
|
||||||
|
|
||||||
const FONT_OPTIONS = [
|
// Dropdown open states
|
||||||
'JetBrains Mono',
|
let themeDropdownOpen = $state(false);
|
||||||
'Fira Code',
|
let uiFontDropdownOpen = $state(false);
|
||||||
'Cascadia Code',
|
let termFontDropdownOpen = $state(false);
|
||||||
'Source Code Pro',
|
|
||||||
'IBM Plex Mono',
|
const UI_FONTS = [
|
||||||
'Hack',
|
{ value: '', label: 'System Default' },
|
||||||
'Inconsolata',
|
{ value: 'Inter', label: 'Inter' },
|
||||||
'Ubuntu Mono',
|
{ value: 'IBM Plex Sans', label: 'IBM Plex Sans' },
|
||||||
'monospace',
|
{ value: 'Noto Sans', label: 'Noto Sans' },
|
||||||
|
{ value: 'Roboto', label: 'Roboto' },
|
||||||
|
{ value: 'Source Sans 3', label: 'Source Sans 3' },
|
||||||
|
{ value: 'Ubuntu', label: 'Ubuntu' },
|
||||||
|
{ value: 'JetBrains Mono', label: 'JetBrains Mono' },
|
||||||
|
{ value: 'Fira Code', label: 'Fira Code' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const TERM_FONTS = [
|
||||||
|
{ value: '', label: 'Default (JetBrains Mono)' },
|
||||||
|
{ value: 'JetBrains Mono', label: 'JetBrains Mono' },
|
||||||
|
{ value: 'Fira Code', label: 'Fira Code' },
|
||||||
|
{ value: 'Cascadia Code', label: 'Cascadia Code' },
|
||||||
|
{ value: 'Source Code Pro', label: 'Source Code Pro' },
|
||||||
|
{ value: 'IBM Plex Mono', label: 'IBM Plex Mono' },
|
||||||
|
{ value: 'Hack', label: 'Hack' },
|
||||||
|
{ value: 'Inconsolata', label: 'Inconsolata' },
|
||||||
|
{ value: 'Ubuntu Mono', label: 'Ubuntu Mono' },
|
||||||
|
{ value: 'monospace', label: 'monospace' },
|
||||||
];
|
];
|
||||||
|
|
||||||
// Group themes by category
|
// Group themes by category
|
||||||
|
|
@ -60,25 +79,33 @@
|
||||||
THEME_LIST.find(t => t.id === selectedTheme)?.label ?? selectedTheme,
|
THEME_LIST.find(t => t.id === selectedTheme)?.label ?? selectedTheme,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let uiFontLabel = $derived(
|
||||||
|
UI_FONTS.find(f => f.value === uiFont)?.label ?? 'System Default',
|
||||||
|
);
|
||||||
|
|
||||||
|
let termFontLabel = $derived(
|
||||||
|
TERM_FONTS.find(f => f.value === termFont)?.label ?? 'Default (JetBrains Mono)',
|
||||||
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const [shell, cwd, font, size] = await Promise.all([
|
const [shell, cwd, font, size, tfont, tsize] = await Promise.all([
|
||||||
getSetting('default_shell'),
|
getSetting('default_shell'),
|
||||||
getSetting('default_cwd'),
|
getSetting('default_cwd'),
|
||||||
getSetting('font_family'),
|
getSetting('ui_font_family'),
|
||||||
getSetting('font_size'),
|
getSetting('ui_font_size'),
|
||||||
|
getSetting('term_font_family'),
|
||||||
|
getSetting('term_font_size'),
|
||||||
]);
|
]);
|
||||||
defaultShell = shell ?? '';
|
defaultShell = shell ?? '';
|
||||||
defaultCwd = cwd ?? '';
|
defaultCwd = cwd ?? '';
|
||||||
fontFamily = font ?? '';
|
uiFont = font ?? '';
|
||||||
fontSize = size ?? '';
|
uiFontSize = size ?? '';
|
||||||
|
termFont = tfont ?? '';
|
||||||
|
termFontSize = tsize ?? '';
|
||||||
selectedTheme = getCurrentTheme();
|
selectedTheme = getCurrentTheme();
|
||||||
|
|
||||||
// Apply saved font settings
|
|
||||||
if (font) applyFont('--ui-font-family', `'${font}', monospace`);
|
|
||||||
if (size) applyFont('--ui-font-size', `${size}px`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function applyFont(prop: string, value: string) {
|
function applyCssProp(prop: string, value: string) {
|
||||||
document.documentElement.style.setProperty(prop, value);
|
document.documentElement.style.setProperty(prop, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,18 +117,40 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFontFamilyChange(family: string) {
|
async function handleUiFontChange(family: string) {
|
||||||
fontFamily = family;
|
uiFont = family;
|
||||||
applyFont('--ui-font-family', family ? `'${family}', monospace` : "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace");
|
uiFontDropdownOpen = false;
|
||||||
await saveGlobalSetting('font_family', family);
|
const val = family
|
||||||
|
? `'${family}', sans-serif`
|
||||||
|
: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace";
|
||||||
|
applyCssProp('--ui-font-family', val);
|
||||||
|
await saveGlobalSetting('ui_font_family', family);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleFontSizeChange(size: string) {
|
async function handleUiFontSizeChange(size: string) {
|
||||||
const num = parseInt(size, 10);
|
const num = parseInt(size, 10);
|
||||||
if (isNaN(num) || num < 8 || num > 24) return;
|
if (isNaN(num) || num < 8 || num > 24) return;
|
||||||
fontSize = size;
|
uiFontSize = size;
|
||||||
applyFont('--ui-font-size', `${num}px`);
|
applyCssProp('--ui-font-size', `${num}px`);
|
||||||
await saveGlobalSetting('font_size', size);
|
await saveGlobalSetting('ui_font_size', size);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTermFontChange(family: string) {
|
||||||
|
termFont = family;
|
||||||
|
termFontDropdownOpen = false;
|
||||||
|
const val = family
|
||||||
|
? `'${family}', monospace`
|
||||||
|
: "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace";
|
||||||
|
applyCssProp('--term-font-family', val);
|
||||||
|
await saveGlobalSetting('term_font_family', family);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleTermFontSizeChange(size: string) {
|
||||||
|
const num = parseInt(size, 10);
|
||||||
|
if (isNaN(num) || num < 8 || num > 24) return;
|
||||||
|
termFontSize = size;
|
||||||
|
applyCssProp('--term-font-size', `${num}px`);
|
||||||
|
await saveGlobalSetting('term_font_size', size);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleThemeChange(themeId: ThemeId) {
|
async function handleThemeChange(themeId: ThemeId) {
|
||||||
|
|
@ -110,16 +159,20 @@
|
||||||
await setTheme(themeId);
|
await setTheme(themeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDropdownKeydown(e: KeyboardEvent) {
|
function handleClickOutside(e: MouseEvent) {
|
||||||
if (e.key === 'Escape') {
|
const target = e.target as HTMLElement;
|
||||||
|
if (!target.closest('.custom-dropdown')) {
|
||||||
themeDropdownOpen = false;
|
themeDropdownOpen = false;
|
||||||
|
uiFontDropdownOpen = false;
|
||||||
|
termFontDropdownOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClickOutside(e: MouseEvent) {
|
function handleKeydown(e: KeyboardEvent) {
|
||||||
const target = e.target as HTMLElement;
|
if (e.key === 'Escape') {
|
||||||
if (!target.closest('.theme-dropdown')) {
|
|
||||||
themeDropdownOpen = false;
|
themeDropdownOpen = false;
|
||||||
|
uiFontDropdownOpen = false;
|
||||||
|
termFontDropdownOpen = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -157,16 +210,16 @@
|
||||||
|
|
||||||
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
||||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||||
<div class="settings-tab" onclick={handleClickOutside}>
|
<div class="settings-tab" onclick={handleClickOutside} onkeydown={handleKeydown}>
|
||||||
<section class="settings-section">
|
<section class="settings-section">
|
||||||
<h2>Global</h2>
|
<h2>Appearance</h2>
|
||||||
<div class="global-grid">
|
<div class="settings-list">
|
||||||
<div class="setting-field">
|
<div class="setting-field">
|
||||||
<span class="setting-label">Theme</span>
|
<span class="setting-label">Theme</span>
|
||||||
<div class="theme-dropdown" onkeydown={handleDropdownKeydown}>
|
<div class="custom-dropdown">
|
||||||
<button
|
<button
|
||||||
class="theme-trigger"
|
class="dropdown-trigger"
|
||||||
onclick={() => (themeDropdownOpen = !themeDropdownOpen)}
|
onclick={() => { themeDropdownOpen = !themeDropdownOpen; uiFontDropdownOpen = false; termFontDropdownOpen = false; }}
|
||||||
aria-haspopup="listbox"
|
aria-haspopup="listbox"
|
||||||
aria-expanded={themeDropdownOpen}
|
aria-expanded={themeDropdownOpen}
|
||||||
>
|
>
|
||||||
|
|
@ -174,16 +227,16 @@
|
||||||
class="theme-swatch"
|
class="theme-swatch"
|
||||||
style="background: {getPalette(selectedTheme).base}; border-color: {getPalette(selectedTheme).surface1};"
|
style="background: {getPalette(selectedTheme).base}; border-color: {getPalette(selectedTheme).surface1};"
|
||||||
></span>
|
></span>
|
||||||
<span class="theme-trigger-label">{selectedThemeLabel}</span>
|
<span class="dropdown-label">{selectedThemeLabel}</span>
|
||||||
<span class="theme-arrow">{themeDropdownOpen ? '\u25B4' : '\u25BE'}</span>
|
<span class="dropdown-arrow">{themeDropdownOpen ? '\u25B4' : '\u25BE'}</span>
|
||||||
</button>
|
</button>
|
||||||
{#if themeDropdownOpen}
|
{#if themeDropdownOpen}
|
||||||
<div class="theme-menu" role="listbox">
|
<div class="dropdown-menu" role="listbox">
|
||||||
{#each themeGroups() as [groupName, themes]}
|
{#each themeGroups() as [groupName, themes]}
|
||||||
<div class="theme-group-label">{groupName}</div>
|
<div class="dropdown-group-label">{groupName}</div>
|
||||||
{#each themes as t}
|
{#each themes as t}
|
||||||
<button
|
<button
|
||||||
class="theme-option"
|
class="dropdown-option"
|
||||||
class:active={t.id === selectedTheme}
|
class:active={t.id === selectedTheme}
|
||||||
role="option"
|
role="option"
|
||||||
aria-selected={t.id === selectedTheme}
|
aria-selected={t.id === selectedTheme}
|
||||||
|
|
@ -193,7 +246,7 @@
|
||||||
class="theme-swatch"
|
class="theme-swatch"
|
||||||
style="background: {getPalette(t.id).base}; border-color: {getPalette(t.id).surface1};"
|
style="background: {getPalette(t.id).base}; border-color: {getPalette(t.id).surface1};"
|
||||||
></span>
|
></span>
|
||||||
<span class="theme-option-label">{t.label}</span>
|
<span class="dropdown-option-label">{t.label}</span>
|
||||||
<span class="theme-colors">
|
<span class="theme-colors">
|
||||||
<span class="color-dot" style="background: {getPalette(t.id).red};"></span>
|
<span class="color-dot" style="background: {getPalette(t.id).red};"></span>
|
||||||
<span class="color-dot" style="background: {getPalette(t.id).green};"></span>
|
<span class="color-dot" style="background: {getPalette(t.id).green};"></span>
|
||||||
|
|
@ -209,47 +262,120 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-field">
|
<div class="setting-field">
|
||||||
<label for="font-family" class="setting-label">Font family</label>
|
<span class="setting-label">UI Font</span>
|
||||||
<select
|
<div class="setting-row">
|
||||||
id="font-family"
|
<div class="custom-dropdown dropdown-grow">
|
||||||
class="setting-select"
|
<button
|
||||||
onchange={e => handleFontFamilyChange((e.target as HTMLSelectElement).value)}
|
class="dropdown-trigger"
|
||||||
>
|
onclick={() => { uiFontDropdownOpen = !uiFontDropdownOpen; themeDropdownOpen = false; termFontDropdownOpen = false; }}
|
||||||
<option value="" selected={!fontFamily}>Default (JetBrains Mono)</option>
|
aria-haspopup="listbox"
|
||||||
{#each FONT_OPTIONS as font}
|
aria-expanded={uiFontDropdownOpen}
|
||||||
<option value={font} selected={fontFamily === font}>{font}</option>
|
>
|
||||||
{/each}
|
<span class="dropdown-label" style={uiFont ? `font-family: '${uiFont}', sans-serif` : ''}>{uiFontLabel}</span>
|
||||||
</select>
|
<span class="dropdown-arrow">{uiFontDropdownOpen ? '\u25B4' : '\u25BE'}</span>
|
||||||
</div>
|
</button>
|
||||||
|
{#if uiFontDropdownOpen}
|
||||||
<div class="setting-field">
|
<div class="dropdown-menu" role="listbox">
|
||||||
<label for="font-size" class="setting-label">Font size</label>
|
{#each UI_FONTS as f}
|
||||||
<div class="size-control">
|
<button
|
||||||
<button
|
class="dropdown-option"
|
||||||
class="size-btn"
|
class:active={f.value === uiFont}
|
||||||
onclick={() => handleFontSizeChange(String((parseInt(fontSize, 10) || 13) - 1))}
|
role="option"
|
||||||
disabled={parseInt(fontSize, 10) <= 8}
|
aria-selected={f.value === uiFont}
|
||||||
>−</button>
|
style={f.value ? `font-family: '${f.value}', sans-serif` : ''}
|
||||||
<input
|
onclick={() => handleUiFontChange(f.value)}
|
||||||
id="font-size"
|
>
|
||||||
type="number"
|
<span class="dropdown-option-label">{f.label}</span>
|
||||||
min="8"
|
</button>
|
||||||
max="24"
|
{/each}
|
||||||
value={fontSize || '13'}
|
</div>
|
||||||
class="size-input"
|
{/if}
|
||||||
onchange={e => handleFontSizeChange((e.target as HTMLInputElement).value)}
|
</div>
|
||||||
/>
|
<div class="size-control">
|
||||||
<span class="size-unit">px</span>
|
<button
|
||||||
<button
|
class="size-btn"
|
||||||
class="size-btn"
|
onclick={() => handleUiFontSizeChange(String((parseInt(uiFontSize, 10) || 13) - 1))}
|
||||||
onclick={() => handleFontSizeChange(String((parseInt(fontSize, 10) || 13) + 1))}
|
disabled={(parseInt(uiFontSize, 10) || 13) <= 8}
|
||||||
disabled={parseInt(fontSize, 10) >= 24}
|
>−</button>
|
||||||
>+</button>
|
<input
|
||||||
|
type="number"
|
||||||
|
min="8"
|
||||||
|
max="24"
|
||||||
|
value={uiFontSize || '13'}
|
||||||
|
class="size-input"
|
||||||
|
onchange={e => handleUiFontSizeChange((e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<span class="size-unit">px</span>
|
||||||
|
<button
|
||||||
|
class="size-btn"
|
||||||
|
onclick={() => handleUiFontSizeChange(String((parseInt(uiFontSize, 10) || 13) + 1))}
|
||||||
|
disabled={(parseInt(uiFontSize, 10) || 13) >= 24}
|
||||||
|
>+</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-field">
|
<div class="setting-field">
|
||||||
<label for="default-shell" class="setting-label">Default shell</label>
|
<span class="setting-label">Terminal Font</span>
|
||||||
|
<div class="setting-row">
|
||||||
|
<div class="custom-dropdown dropdown-grow">
|
||||||
|
<button
|
||||||
|
class="dropdown-trigger"
|
||||||
|
onclick={() => { termFontDropdownOpen = !termFontDropdownOpen; themeDropdownOpen = false; uiFontDropdownOpen = false; }}
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
aria-expanded={termFontDropdownOpen}
|
||||||
|
>
|
||||||
|
<span class="dropdown-label" style={termFont ? `font-family: '${termFont}', monospace` : ''}>{termFontLabel}</span>
|
||||||
|
<span class="dropdown-arrow">{termFontDropdownOpen ? '\u25B4' : '\u25BE'}</span>
|
||||||
|
</button>
|
||||||
|
{#if termFontDropdownOpen}
|
||||||
|
<div class="dropdown-menu" role="listbox">
|
||||||
|
{#each TERM_FONTS as f}
|
||||||
|
<button
|
||||||
|
class="dropdown-option"
|
||||||
|
class:active={f.value === termFont}
|
||||||
|
role="option"
|
||||||
|
aria-selected={f.value === termFont}
|
||||||
|
style={f.value ? `font-family: '${f.value}', monospace` : ''}
|
||||||
|
onclick={() => handleTermFontChange(f.value)}
|
||||||
|
>
|
||||||
|
<span class="dropdown-option-label">{f.label}</span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<div class="size-control">
|
||||||
|
<button
|
||||||
|
class="size-btn"
|
||||||
|
onclick={() => handleTermFontSizeChange(String((parseInt(termFontSize, 10) || 13) - 1))}
|
||||||
|
disabled={(parseInt(termFontSize, 10) || 13) <= 8}
|
||||||
|
>−</button>
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
min="8"
|
||||||
|
max="24"
|
||||||
|
value={termFontSize || '13'}
|
||||||
|
class="size-input"
|
||||||
|
onchange={e => handleTermFontSizeChange((e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<span class="size-unit">px</span>
|
||||||
|
<button
|
||||||
|
class="size-btn"
|
||||||
|
onclick={() => handleTermFontSizeChange(String((parseInt(termFontSize, 10) || 13) + 1))}
|
||||||
|
disabled={(parseInt(termFontSize, 10) || 13) >= 24}
|
||||||
|
>+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="settings-section">
|
||||||
|
<h2>Defaults</h2>
|
||||||
|
<div class="settings-list">
|
||||||
|
<div class="setting-field">
|
||||||
|
<label for="default-shell" class="setting-label">Shell</label>
|
||||||
<input
|
<input
|
||||||
id="default-shell"
|
id="default-shell"
|
||||||
value={defaultShell}
|
value={defaultShell}
|
||||||
|
|
@ -257,9 +383,8 @@
|
||||||
onchange={e => { defaultShell = (e.target as HTMLInputElement).value; saveGlobalSetting('default_shell', defaultShell); }}
|
onchange={e => { defaultShell = (e.target as HTMLInputElement).value; saveGlobalSetting('default_shell', defaultShell); }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="setting-field">
|
<div class="setting-field">
|
||||||
<label for="default-cwd" class="setting-label">Default CWD</label>
|
<label for="default-cwd" class="setting-label">Working directory</label>
|
||||||
<input
|
<input
|
||||||
id="default-cwd"
|
id="default-cwd"
|
||||||
value={defaultCwd}
|
value={defaultCwd}
|
||||||
|
|
@ -356,24 +481,26 @@
|
||||||
padding: 16px 24px;
|
padding: 16px 24px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
max-width: 900px;
|
max-width: 560px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
font-size: 1rem;
|
font-size: 0.85rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--ctp-text);
|
color: var(--ctp-text);
|
||||||
margin: 0 0 12px;
|
margin: 0 0 10px;
|
||||||
|
padding-bottom: 6px;
|
||||||
|
border-bottom: 1px solid var(--ctp-surface0);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section {
|
.settings-section {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-grid {
|
.settings-list {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: 1fr 1fr;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-field {
|
.setting-field {
|
||||||
|
|
@ -389,7 +516,7 @@
|
||||||
letter-spacing: 0.03em;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-field input:not([type="number"]) {
|
.setting-field > input {
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: var(--ctp-surface0);
|
background: var(--ctp-surface0);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
|
@ -398,7 +525,27 @@
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-select {
|
.setting-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reusable custom dropdown */
|
||||||
|
.custom-dropdown {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-grow {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background: var(--ctp-surface0);
|
background: var(--ctp-surface0);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
|
@ -406,12 +553,112 @@
|
||||||
color: var(--ctp-text);
|
color: var(--ctp-text);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-trigger:hover {
|
||||||
|
border-color: var(--ctp-surface2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-label {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-arrow {
|
||||||
|
color: var(--ctp-overlay0);
|
||||||
|
font-size: 0.7rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-menu {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(100% + 4px);
|
||||||
|
left: 0;
|
||||||
|
min-width: 100%;
|
||||||
|
width: max-content;
|
||||||
|
max-height: 360px;
|
||||||
|
overflow-y: auto;
|
||||||
|
background: var(--ctp-mantle);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
||||||
|
z-index: 100;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-group-label {
|
||||||
|
padding: 6px 10px 2px;
|
||||||
|
font-size: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--ctp-overlay0);
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-option {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 5px 10px;
|
||||||
|
background: transparent;
|
||||||
|
border: none;
|
||||||
|
color: var(--ctp-subtext1);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
text-align: left;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-option:hover {
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
color: var(--ctp-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-option.active {
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-option-label {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme-specific dropdown extras */
|
||||||
|
.theme-swatch {
|
||||||
|
display: inline-block;
|
||||||
|
width: 14px;
|
||||||
|
height: 14px;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px solid;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-colors {
|
||||||
|
display: flex;
|
||||||
|
gap: 3px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.color-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Size control (shared by UI and Terminal font) */
|
||||||
.size-control {
|
.size-control {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
gap: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.size-btn {
|
.size-btn {
|
||||||
|
|
@ -438,8 +685,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.size-input {
|
.size-input {
|
||||||
width: 48px;
|
width: 40px;
|
||||||
padding: 4px 6px;
|
padding: 4px 2px;
|
||||||
background: var(--ctp-surface0);
|
background: var(--ctp-surface0);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
@ -455,122 +702,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.size-unit {
|
.size-unit {
|
||||||
font-size: 0.75rem;
|
|
||||||
color: var(--ctp-overlay0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Custom theme dropdown */
|
|
||||||
.theme-dropdown {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-trigger {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 4px 8px;
|
|
||||||
background: var(--ctp-base);
|
|
||||||
border: 1px solid var(--ctp-surface1);
|
|
||||||
border-radius: 3px;
|
|
||||||
color: var(--ctp-text);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-trigger:hover {
|
|
||||||
border-color: var(--ctp-surface2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-trigger-label {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-arrow {
|
|
||||||
color: var(--ctp-overlay0);
|
|
||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-swatch {
|
|
||||||
display: inline-block;
|
|
||||||
width: 14px;
|
|
||||||
height: 14px;
|
|
||||||
border-radius: 3px;
|
|
||||||
border: 1px solid;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-menu {
|
|
||||||
position: absolute;
|
|
||||||
top: calc(100% + 4px);
|
|
||||||
left: 0;
|
|
||||||
min-width: 280px;
|
|
||||||
max-height: 400px;
|
|
||||||
overflow-y: auto;
|
|
||||||
background: var(--ctp-mantle);
|
|
||||||
border: 1px solid var(--ctp-surface1);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
||||||
z-index: 100;
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-group-label {
|
|
||||||
padding: 6px 10px 2px;
|
|
||||||
font-size: 0.65rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--ctp-overlay0);
|
color: var(--ctp-overlay0);
|
||||||
text-transform: uppercase;
|
margin-right: 2px;
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 5px 10px;
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
color: var(--ctp-subtext1);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option:hover {
|
|
||||||
background: var(--ctp-surface0);
|
|
||||||
color: var(--ctp-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option.active {
|
|
||||||
background: var(--ctp-surface0);
|
|
||||||
color: var(--ctp-text);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-option-label {
|
|
||||||
flex: 1;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-colors {
|
|
||||||
display: flex;
|
|
||||||
gap: 3px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.color-dot {
|
|
||||||
display: inline-block;
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Groups & Projects */
|
/* Groups & Projects */
|
||||||
|
|
|
||||||
|
|
@ -81,16 +81,17 @@ export async function initTheme(): Promise<void> {
|
||||||
|
|
||||||
// Apply saved font settings
|
// Apply saved font settings
|
||||||
try {
|
try {
|
||||||
const [fontFamily, fontSize] = await Promise.all([
|
const [uiFont, uiSize, termFont, termSize] = await Promise.all([
|
||||||
getSetting('font_family'),
|
getSetting('ui_font_family'),
|
||||||
getSetting('font_size'),
|
getSetting('ui_font_size'),
|
||||||
|
getSetting('term_font_family'),
|
||||||
|
getSetting('term_font_size'),
|
||||||
]);
|
]);
|
||||||
if (fontFamily) {
|
const root = document.documentElement.style;
|
||||||
document.documentElement.style.setProperty('--ui-font-family', `'${fontFamily}', monospace`);
|
if (uiFont) root.setProperty('--ui-font-family', `'${uiFont}', sans-serif`);
|
||||||
}
|
if (uiSize) root.setProperty('--ui-font-size', `${uiSize}px`);
|
||||||
if (fontSize) {
|
if (termFont) root.setProperty('--term-font-family', `'${termFont}', monospace`);
|
||||||
document.documentElement.style.setProperty('--ui-font-size', `${fontSize}px`);
|
if (termSize) root.setProperty('--term-font-size', `${termSize}px`);
|
||||||
}
|
|
||||||
} catch {
|
} catch {
|
||||||
// Font settings are optional — defaults from catppuccin.css apply
|
// Font settings are optional — defaults from catppuccin.css apply
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,8 @@
|
||||||
/* Typography */
|
/* Typography */
|
||||||
--ui-font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
--ui-font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||||
--ui-font-size: 13px;
|
--ui-font-size: 13px;
|
||||||
|
--term-font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||||
|
--term-font-size: 13px;
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
--sidebar-width: 260px;
|
--sidebar-width: 260px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue