feat(v3): add global font controls to SettingsTab
Add font family select (9 monospace fonts) and font size +/- stepper (8-24px) to SettingsTab global settings. Both controls apply live preview via CSS custom properties (--ui-font-family, --ui-font-size) and persist to SQLite. Restructure global settings from inline rows to 2-column grid with labels above controls. initTheme() now restores saved font settings on startup.
This commit is contained in:
parent
1279981bf9
commit
47492aa637
4 changed files with 184 additions and 29 deletions
|
|
@ -14,8 +14,8 @@ html, body {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
font-family: var(--ui-font-family);
|
||||||
font-size: 13px;
|
font-size: var(--ui-font-size);
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,23 @@
|
||||||
// Global settings
|
// Global settings
|
||||||
let defaultShell = $state('');
|
let defaultShell = $state('');
|
||||||
let defaultCwd = $state('');
|
let defaultCwd = $state('');
|
||||||
|
let fontFamily = $state('');
|
||||||
|
let fontSize = $state('');
|
||||||
let selectedTheme = $state<ThemeId>(getCurrentTheme());
|
let selectedTheme = $state<ThemeId>(getCurrentTheme());
|
||||||
let themeDropdownOpen = $state(false);
|
let themeDropdownOpen = $state(false);
|
||||||
|
|
||||||
|
const FONT_OPTIONS = [
|
||||||
|
'JetBrains Mono',
|
||||||
|
'Fira Code',
|
||||||
|
'Cascadia Code',
|
||||||
|
'Source Code Pro',
|
||||||
|
'IBM Plex Mono',
|
||||||
|
'Hack',
|
||||||
|
'Inconsolata',
|
||||||
|
'Ubuntu Mono',
|
||||||
|
'monospace',
|
||||||
|
];
|
||||||
|
|
||||||
// Group themes by category
|
// Group themes by category
|
||||||
const themeGroups = $derived(() => {
|
const themeGroups = $derived(() => {
|
||||||
const map = new Map<string, typeof THEME_LIST>();
|
const map = new Map<string, typeof THEME_LIST>();
|
||||||
|
|
@ -47,15 +61,27 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
const [shell, cwd] = await Promise.all([
|
const [shell, cwd, font, size] = await Promise.all([
|
||||||
getSetting('default_shell'),
|
getSetting('default_shell'),
|
||||||
getSetting('default_cwd'),
|
getSetting('default_cwd'),
|
||||||
|
getSetting('font_family'),
|
||||||
|
getSetting('font_size'),
|
||||||
]);
|
]);
|
||||||
defaultShell = shell ?? '';
|
defaultShell = shell ?? '';
|
||||||
defaultCwd = cwd ?? '';
|
defaultCwd = cwd ?? '';
|
||||||
|
fontFamily = font ?? '';
|
||||||
|
fontSize = size ?? '';
|
||||||
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) {
|
||||||
|
document.documentElement.style.setProperty(prop, value);
|
||||||
|
}
|
||||||
|
|
||||||
async function saveGlobalSetting(key: string, value: string) {
|
async function saveGlobalSetting(key: string, value: string) {
|
||||||
try {
|
try {
|
||||||
await setSetting(key, value);
|
await setSetting(key, value);
|
||||||
|
|
@ -64,6 +90,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleFontFamilyChange(family: string) {
|
||||||
|
fontFamily = family;
|
||||||
|
applyFont('--ui-font-family', family ? `'${family}', monospace` : "'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace");
|
||||||
|
await saveGlobalSetting('font_family', family);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleFontSizeChange(size: string) {
|
||||||
|
const num = parseInt(size, 10);
|
||||||
|
if (isNaN(num) || num < 8 || num > 24) return;
|
||||||
|
fontSize = size;
|
||||||
|
applyFont('--ui-font-size', `${num}px`);
|
||||||
|
await saveGlobalSetting('font_size', size);
|
||||||
|
}
|
||||||
|
|
||||||
async function handleThemeChange(themeId: ThemeId) {
|
async function handleThemeChange(themeId: ThemeId) {
|
||||||
selectedTheme = themeId;
|
selectedTheme = themeId;
|
||||||
themeDropdownOpen = false;
|
themeDropdownOpen = false;
|
||||||
|
|
@ -120,8 +160,8 @@
|
||||||
<div class="settings-tab" onclick={handleClickOutside}>
|
<div class="settings-tab" onclick={handleClickOutside}>
|
||||||
<section class="settings-section">
|
<section class="settings-section">
|
||||||
<h2>Global</h2>
|
<h2>Global</h2>
|
||||||
<div class="global-settings">
|
<div class="global-grid">
|
||||||
<div class="setting-row">
|
<div class="setting-field">
|
||||||
<span class="setting-label">Theme</span>
|
<span class="setting-label">Theme</span>
|
||||||
<div class="theme-dropdown" onkeydown={handleDropdownKeydown}>
|
<div class="theme-dropdown" onkeydown={handleDropdownKeydown}>
|
||||||
<button
|
<button
|
||||||
|
|
@ -167,7 +207,48 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-row">
|
|
||||||
|
<div class="setting-field">
|
||||||
|
<label for="font-family" class="setting-label">Font family</label>
|
||||||
|
<select
|
||||||
|
id="font-family"
|
||||||
|
class="setting-select"
|
||||||
|
onchange={e => handleFontFamilyChange((e.target as HTMLSelectElement).value)}
|
||||||
|
>
|
||||||
|
<option value="" selected={!fontFamily}>Default (JetBrains Mono)</option>
|
||||||
|
{#each FONT_OPTIONS as font}
|
||||||
|
<option value={font} selected={fontFamily === font}>{font}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-field">
|
||||||
|
<label for="font-size" class="setting-label">Font size</label>
|
||||||
|
<div class="size-control">
|
||||||
|
<button
|
||||||
|
class="size-btn"
|
||||||
|
onclick={() => handleFontSizeChange(String((parseInt(fontSize, 10) || 13) - 1))}
|
||||||
|
disabled={parseInt(fontSize, 10) <= 8}
|
||||||
|
>−</button>
|
||||||
|
<input
|
||||||
|
id="font-size"
|
||||||
|
type="number"
|
||||||
|
min="8"
|
||||||
|
max="24"
|
||||||
|
value={fontSize || '13'}
|
||||||
|
class="size-input"
|
||||||
|
onchange={e => handleFontSizeChange((e.target as HTMLInputElement).value)}
|
||||||
|
/>
|
||||||
|
<span class="size-unit">px</span>
|
||||||
|
<button
|
||||||
|
class="size-btn"
|
||||||
|
onclick={() => handleFontSizeChange(String((parseInt(fontSize, 10) || 13) + 1))}
|
||||||
|
disabled={parseInt(fontSize, 10) >= 24}
|
||||||
|
>+</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="setting-field">
|
||||||
<label for="default-shell" class="setting-label">Default shell</label>
|
<label for="default-shell" class="setting-label">Default shell</label>
|
||||||
<input
|
<input
|
||||||
id="default-shell"
|
id="default-shell"
|
||||||
|
|
@ -176,7 +257,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-row">
|
|
||||||
|
<div class="setting-field">
|
||||||
<label for="default-cwd" class="setting-label">Default CWD</label>
|
<label for="default-cwd" class="setting-label">Default CWD</label>
|
||||||
<input
|
<input
|
||||||
id="default-cwd"
|
id="default-cwd"
|
||||||
|
|
@ -288,45 +370,98 @@
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.global-settings {
|
.global-grid {
|
||||||
display: flex;
|
display: grid;
|
||||||
flex-direction: column;
|
grid-template-columns: 1fr 1fr;
|
||||||
gap: 8px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-row {
|
.setting-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: column;
|
||||||
gap: 12px;
|
gap: 4px;
|
||||||
padding: 6px 10px;
|
|
||||||
background: var(--ctp-surface0);
|
|
||||||
border-radius: 4px;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-label {
|
.setting-label {
|
||||||
font-size: 0.8rem;
|
font-size: 0.7rem;
|
||||||
color: var(--ctp-subtext0);
|
color: var(--ctp-subtext0);
|
||||||
min-width: 100px;
|
text-transform: uppercase;
|
||||||
flex-shrink: 0;
|
letter-spacing: 0.03em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.setting-row input {
|
.setting-field input:not([type="number"]) {
|
||||||
padding: 4px 8px;
|
padding: 6px 10px;
|
||||||
background: var(--ctp-base);
|
background: var(--ctp-surface0);
|
||||||
border: 1px solid var(--ctp-surface1);
|
border: 1px solid var(--ctp-surface1);
|
||||||
border-radius: 3px;
|
border-radius: 4px;
|
||||||
color: var(--ctp-text);
|
color: var(--ctp-text);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
flex: 1;
|
}
|
||||||
min-width: 0;
|
|
||||||
|
.setting-select {
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-control {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-btn {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-btn:hover {
|
||||||
|
background: var(--ctp-surface1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-btn:disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-input {
|
||||||
|
width: 48px;
|
||||||
|
padding: 4px 6px;
|
||||||
|
background: var(--ctp-surface0);
|
||||||
|
border: 1px solid var(--ctp-surface1);
|
||||||
|
border-radius: 4px;
|
||||||
|
color: var(--ctp-text);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-input::-webkit-inner-spin-button,
|
||||||
|
.size-input::-webkit-outer-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.size-unit {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--ctp-overlay0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom theme dropdown */
|
/* Custom theme dropdown */
|
||||||
.theme-dropdown {
|
.theme-dropdown {
|
||||||
position: relative;
|
position: relative;
|
||||||
flex: 1;
|
|
||||||
min-width: 180px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-trigger {
|
.theme-trigger {
|
||||||
|
|
|
||||||
|
|
@ -78,4 +78,20 @@ export async function initTheme(): Promise<void> {
|
||||||
if (currentTheme !== 'mocha') {
|
if (currentTheme !== 'mocha') {
|
||||||
applyCssVariables(currentTheme);
|
applyCssVariables(currentTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Apply saved font settings
|
||||||
|
try {
|
||||||
|
const [fontFamily, fontSize] = await Promise.all([
|
||||||
|
getSetting('font_family'),
|
||||||
|
getSetting('font_size'),
|
||||||
|
]);
|
||||||
|
if (fontFamily) {
|
||||||
|
document.documentElement.style.setProperty('--ui-font-family', `'${fontFamily}', monospace`);
|
||||||
|
}
|
||||||
|
if (fontSize) {
|
||||||
|
document.documentElement.style.setProperty('--ui-font-size', `${fontSize}px`);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Font settings are optional — defaults from catppuccin.css apply
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@
|
||||||
--warning: var(--ctp-yellow);
|
--warning: var(--ctp-yellow);
|
||||||
--error: var(--ctp-red);
|
--error: var(--ctp-red);
|
||||||
|
|
||||||
|
/* Typography */
|
||||||
|
--ui-font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace;
|
||||||
|
--ui-font-size: 13px;
|
||||||
|
|
||||||
/* Layout */
|
/* Layout */
|
||||||
--sidebar-width: 260px;
|
--sidebar-width: 260px;
|
||||||
--right-panel-width: 380px;
|
--right-panel-width: 380px;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue