Custom font selection (#226)

This commit is contained in:
Gregory Schier
2025-06-08 22:48:27 -07:00
committed by GitHub
parent e0aaa33ccb
commit 9fab37fb17
25 changed files with 399 additions and 56 deletions

View File

@@ -1,3 +1,5 @@
import { type } from '@tauri-apps/plugin-os';
import { useFonts } from '@yaakapp-internal/fonts';
import type { EditorKeymap } from '@yaakapp-internal/models';
import { patchModel, settingsAtom } from '@yaakapp-internal/models';
import { useAtomValue } from 'jotai';
@@ -14,11 +16,11 @@ import { Editor } from '../core/Editor/Editor';
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import type { SelectProps } from '../core/Select';
import { Select } from '../core/Select';
import { Select, SelectProps } from '../core/Select';
import { Separator } from '../core/Separator';
import { HStack, VStack } from '../core/Stacks';
import { type } from '@tauri-apps/plugin-os';
const NULL_FONT_VALUE = '__NULL_FONT__';
const fontSizeOptions = [
8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30,
@@ -67,6 +69,7 @@ export function SettingsAppearance() {
const settings = useAtomValue(settingsAtom);
const appearance = useResolvedAppearance();
const activeTheme = useResolvedTheme();
const fonts = useFonts();
if (settings == null || workspace == null) {
return null;
@@ -87,46 +90,97 @@ export function SettingsAppearance() {
}));
return (
<VStack space={2} className="mb-4">
<Select
size="sm"
name="interfaceFontSize"
label="Font Size"
labelPosition="left"
defaultValue="15"
value={`${settings.interfaceFontSize}`}
options={fontSizeOptions}
onChange={(v) => patchModel(settings, { interfaceFontSize: parseInt(v) })}
/>
<Select
size="sm"
name="editorFontSize"
label="Editor Font Size"
labelPosition="left"
defaultValue="13"
value={`${settings.editorFontSize}`}
options={fontSizeOptions}
onChange={(v) => patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })}
/>
<VStack space={3} className="mb-4">
<HStack space={2} alignItems="end">
{fonts.data && (
<Select
size="sm"
name="uiFont"
label="Interface Font"
value={settings.interfaceFont ?? NULL_FONT_VALUE}
options={[
{ label: 'System Default', value: NULL_FONT_VALUE },
...(fonts.data.uiFonts.map((f) => ({
label: f,
value: f,
})) ?? []),
// Some people like monospace fonts for the UI
...(fonts.data.editorFonts.map((f) => ({
label: f,
value: f,
})) ?? []),
]}
onChange={async (v) => {
const interfaceFont = v === NULL_FONT_VALUE ? null : v;
await patchModel(settings, { interfaceFont });
}}
/>
)}
<Select
hideLabel
size="sm"
name="interfaceFontSize"
label="Interface Font Size"
defaultValue="15"
value={`${settings.interfaceFontSize}`}
options={fontSizeOptions}
onChange={(v) => patchModel(settings, { interfaceFontSize: parseInt(v) })}
/>
</HStack>
<HStack space={2} alignItems="end">
{fonts.data && (
<Select
size="sm"
name="editorFont"
label="Editor Font"
value={settings.editorFont ?? NULL_FONT_VALUE}
options={[
{ label: 'System Default', value: NULL_FONT_VALUE },
...(fonts.data.editorFonts.map((f) => ({
label: f,
value: f,
})) ?? []),
]}
onChange={async (v) => {
const editorFont = v === NULL_FONT_VALUE ? null : v;
await patchModel(settings, { editorFont });
}}
/>
)}
<Select
hideLabel
size="sm"
name="editorFontSize"
label="Editor Font Size"
defaultValue="13"
value={`${settings.editorFontSize}`}
options={fontSizeOptions}
onChange={(v) =>
patchModel(settings, { editorFontSize: clamp(parseInt(v) || 14, 8, 30) })
}
/>
</HStack>
<Select
leftSlot={<Icon icon="keyboard" color="secondary" />}
size="sm"
name="editorKeymap"
label="Editor Keymap"
labelPosition="left"
value={`${settings.editorKeymap}`}
options={keymaps}
onChange={(v) => patchModel(settings, { editorKeymap: v })}
/>
<Checkbox
checked={settings.editorSoftWrap}
title="Wrap Editor Lines"
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
/>
<Checkbox
checked={settings.coloredMethods}
title="Colorize Request Methods"
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
/>
<div className="grid grid-cols-2">
<Checkbox
checked={settings.editorSoftWrap}
title="Wrap Editor Lines"
onChange={(editorSoftWrap) => patchModel(settings, { editorSoftWrap })}
/>
<Checkbox
checked={settings.coloredMethods}
title="Colorize Request Methods"
onChange={(coloredMethods) => patchModel(settings, { coloredMethods })}
/>
</div>
{type() !== 'macos' && (
<Checkbox
@@ -156,7 +210,7 @@ export function SettingsAppearance() {
{(settings.appearance === 'system' || settings.appearance === 'light') && (
<Select
hideLabel
leftSlot={<Icon icon="sun" />}
leftSlot={<Icon icon="sun" color="secondary" />}
name="lightTheme"
label="Light Theme"
size="sm"
@@ -172,7 +226,7 @@ export function SettingsAppearance() {
name="darkTheme"
className="flex-1"
label="Dark Theme"
leftSlot={<Icon icon="moon" />}
leftSlot={<Icon icon="moon" color="secondary" />}
size="sm"
value={activeTheme.dark.id}
options={darkThemes}

19
src-web/font.ts Normal file
View File

@@ -0,0 +1,19 @@
// Listen for settings changes, the re-compute theme
import { listen } from '@tauri-apps/api/event';
import type { ModelPayload, Settings } from '@yaakapp-internal/models';
import { getSettings } from './lib/settings';
function setFonts(settings: Settings) {
document.documentElement.style.setProperty('--font-family-editor', settings.editorFont ?? '');
document.documentElement.style.setProperty(
'--font-family-interface',
settings.interfaceFont ?? '',
);
}
listen<ModelPayload>('upserted_model', async (event) => {
if (event.payload.model.model !== 'settings') return;
setFonts(event.payload.model);
}).catch(console.error);
getSettings().then((settings) => setFonts(settings));

View File

@@ -28,6 +28,7 @@
<div id="radix-portal" class="cm-portal"></div>
<script type="module" src="/theme.ts"></script>
<script type="module" src="/font-size.ts"></script>
<script type="module" src="/font.ts"></script>
<script type="module" src="/main.tsx"></script>
</body>
</html>

View File

@@ -9,6 +9,12 @@
@apply w-full h-full overflow-hidden text-text bg-surface;
}
:root {
/* Must default these variables or the default will break */
--font-family-interface: '';
--font-family-editor: '';
}
/* Never show ligatures */
:root {
font-variant-ligatures: none;

View File

@@ -1,5 +1,6 @@
import { QueryClientProvider } from '@tanstack/react-query';
import { createRootRoute, Outlet } from '@tanstack/react-router';
import { type } from '@tauri-apps/plugin-os';
import classNames from 'classnames';
import { Provider as JotaiProvider } from 'jotai';
import { domAnimation, LazyMotion, MotionConfig } from 'motion/react';
@@ -13,7 +14,6 @@ import RouteError from '../components/RouteError';
import { Toasts } from '../components/Toasts';
import { jotaiStore } from '../lib/jotai';
import { queryClient } from '../lib/queryClient';
import { type } from '@tauri-apps/plugin-os';
export const Route = createRootRoute({
component: RouteComponent,
@@ -32,14 +32,7 @@ function RouteComponent() {
<GlobalHooks />
<Toasts />
<Dialogs />
<div
className={classNames(
'w-full h-full',
type() === 'linux' && 'border border-border-subtle',
)}
>
<Outlet />
</div>
<Layout />
</Suspense>
</DndProvider>
</HelmetProvider>
@@ -49,3 +42,13 @@ function RouteComponent() {
</JotaiProvider>
);
}
function Layout() {
return (
<div
className={classNames('w-full h-full', type() === 'linux' && 'border border-border-subtle')}
>
<Outlet />
</div>
);
}

View File

@@ -7,7 +7,7 @@ const sizes = {
md: '2.3rem',
};
/** @type {import("tailwindcss").Config} */
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: ['class', '[data-resolved-appearance="dark"]'],
content: [
@@ -43,6 +43,7 @@ module.exports = {
},
fontFamily: {
mono: [
'var(--font-family-editor)',
'JetBrains Mono',
'ui-monospace',
'SFMono-Regular',
@@ -58,6 +59,7 @@ module.exports = {
'monospace',
],
sans: [
'var(--font-family-interface)',
'Inter UI',
'-apple-system',
'BlinkMacSystemFont',