Split appearance settings into theme/interface

This commit is contained in:
Gregory Schier
2025-06-09 21:19:44 -07:00
parent be0a8fc27a
commit 383fd05c6c
3 changed files with 148 additions and 128 deletions

View File

@@ -8,22 +8,24 @@ import { capitalize } from '../../lib/capitalize';
import { HStack } from '../core/Stacks';
import { TabContent, Tabs } from '../core/Tabs/Tabs';
import { HeaderSize } from '../HeaderSize';
import { SettingsAppearance } from './SettingsAppearance';
import { SettingsInterface } from './SettingsInterface';
import { SettingsGeneral } from './SettingsGeneral';
import { SettingsLicense } from './SettingsLicense';
import { SettingsPlugins } from './SettingsPlugins';
import { SettingsProxy } from './SettingsProxy';
import { SettingsTheme } from './SettingsTheme';
interface Props {
hide?: () => void;
}
const TAB_GENERAL = 'general';
const TAB_APPEARANCE = 'appearance';
const TAB_INTERFACE = 'interface';
const TAB_THEME = 'theme';
const TAB_PROXY = 'proxy';
const TAB_PLUGINS = 'plugins';
const TAB_LICENSE = 'license';
const tabs = [TAB_GENERAL, TAB_APPEARANCE, TAB_PROXY, TAB_PLUGINS, TAB_LICENSE] as const;
const tabs = [TAB_GENERAL, TAB_THEME, TAB_INTERFACE, TAB_PROXY, TAB_PLUGINS, TAB_LICENSE] as const;
export type SettingsTab = (typeof tabs)[number];
export default function Settings({ hide }: Props) {
@@ -73,8 +75,11 @@ export default function Settings({ hide }: Props) {
<TabContent value={TAB_GENERAL} className="pt-3 overflow-y-auto h-full px-4">
<SettingsGeneral />
</TabContent>
<TabContent value={TAB_APPEARANCE} className="pt-3 overflow-y-auto h-full px-4">
<SettingsAppearance />
<TabContent value={TAB_INTERFACE} className="pt-3 overflow-y-auto h-full px-4">
<SettingsInterface />
</TabContent>
<TabContent value={TAB_THEME} className="pt-3 overflow-y-auto h-full px-4">
<SettingsTheme />
</TabContent>
<TabContent value={TAB_PLUGINS} className="pt-3 overflow-y-auto h-full px-4">
<SettingsPlugins />

View File

@@ -0,0 +1,137 @@
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';
import React from 'react';
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
import { clamp } from '../../lib/clamp';
import { Checkbox } from '../core/Checkbox';
import { Icon } from '../core/Icon';
import { Select } from '../core/Select';
import { HStack, VStack } from '../core/Stacks';
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,
].map((n) => ({ label: `${n}`, value: `${n}` }));
const keymaps: { value: EditorKeymap; label: string }[] = [
{ value: 'default', label: 'Default' },
{ value: 'vim', label: 'Vim' },
{ value: 'vscode', label: 'VSCode' },
{ value: 'emacs', label: 'Emacs' },
];
export function SettingsInterface() {
const workspace = useAtomValue(activeWorkspaceAtom);
const settings = useAtomValue(settingsAtom);
const fonts = useFonts();
if (settings == null || workspace == null) {
return null;
}
return (
<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"
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 })}
/>
{type() !== 'macos' && (
<Checkbox
checked={settings.hideWindowControls}
title="Hide Window Controls"
help="Hide the close/maximize/minimize controls on Windows or Linux"
onChange={(hideWindowControls) => patchModel(settings, { hideWindowControls })}
/>
)}
</VStack>
);
}

View File

@@ -1,38 +1,19 @@
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';
import React from 'react';
import { activeWorkspaceAtom } from '../../hooks/useActiveWorkspace';
import { useResolvedAppearance } from '../../hooks/useResolvedAppearance';
import { useResolvedTheme } from '../../hooks/useResolvedTheme';
import { clamp } from '../../lib/clamp';
import { getThemes } from '../../lib/theme/themes';
import { isThemeDark } from '../../lib/theme/window';
import type { ButtonProps } from '../core/Button';
import { Checkbox } from '../core/Checkbox';
import { Editor } from '../core/Editor/Editor';
import type { IconProps } from '../core/Icon';
import { Icon } from '../core/Icon';
import { IconButton } from '../core/IconButton';
import { Select, SelectProps } from '../core/Select';
import { Separator } from '../core/Separator';
import { HStack, VStack } from '../core/Stacks';
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,
].map((n) => ({ label: `${n}`, value: `${n}` }));
const keymaps: { value: EditorKeymap; label: string }[] = [
{ value: 'default', label: 'Default' },
{ value: 'vim', label: 'Vim' },
{ value: 'vscode', label: 'VSCode' },
{ value: 'emacs', label: 'Emacs' },
];
const buttonColors: ButtonProps['color'][] = [
'primary',
'info',
@@ -64,12 +45,11 @@ const icons: IconProps['icon'][] = [
const { themes } = getThemes();
export function SettingsAppearance() {
export function SettingsTheme() {
const workspace = useAtomValue(activeWorkspaceAtom);
const settings = useAtomValue(settingsAtom);
const appearance = useResolvedAppearance();
const activeTheme = useResolvedTheme();
const fonts = useFonts();
if (settings == null || workspace == null) {
return null;
@@ -91,108 +71,6 @@ export function SettingsAppearance() {
return (
<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"
value={`${settings.editorKeymap}`}
options={keymaps}
onChange={(v) => patchModel(settings, { editorKeymap: v })}
/>
<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
checked={settings.hideWindowControls}
title="Hide Window Controls"
help="Hide the close/maximize/minimize controls on Windows or Linux"
onChange={(hideWindowControls) => patchModel(settings, { hideWindowControls })}
/>
)}
<Separator className="my-4" />
<Select
name="appearance"
label="Appearance"