From 13ba7219c0cbf98b1ede11be317cf0dd111e49fb Mon Sep 17 00:00:00 2001 From: oratis Date: Thu, 18 Jun 2026 22:17:47 +0800 Subject: [PATCH] feat(settings): redesign settings panel as a tabbed/sidebar layout Restructure the single overflowing column into a macOS-style dialog with a left category nav (Appearance / Editor / Files / Shortcuts / Advanced) and a scrollable content pane. - Fix the dialog overflowing the viewport: sticky header + scrollable body + sticky footer, capped at max-h-[88vh]. - Group the 12 settings into five labelled categories. - Replace raw checkboxes + "On/Off" text with accessible toggle switches (role="switch"); move each hint to a description subtitle under the label. - Wire the panel through new .mk-settings-* / .mk-toggle classes using --mk-* tokens so it tracks light/dark/sepia (the old dark: utilities silently missed sepia). Primary "Done" is now an accent button. - Refit ShortcutsEditor to live inside its own tab (drop standalone title and top border, token-style the filter, give the list more height). - Add catAppearance/catEditor/catFiles/catAdvanced/close i18n keys (en + zh). Inactive tabs stay mounted (hidden) so drafted state and existing tests hold. 1005/1005 unit tests pass; tsc -b and biome clean. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/SettingsDialog.tsx | 451 +++++++++++++++++++---------- src/components/ShortcutsEditor.tsx | 14 +- src/index.css | 138 +++++++++ src/lib/locales/en.ts | 5 + src/lib/locales/zh.ts | 5 + 5 files changed, 448 insertions(+), 165 deletions(-) diff --git a/src/components/SettingsDialog.tsx b/src/components/SettingsDialog.tsx index adb03ef..81f46b2 100644 --- a/src/components/SettingsDialog.tsx +++ b/src/components/SettingsDialog.tsx @@ -7,9 +7,13 @@ interface Props { onClose: () => void; } +type Category = "appearance" | "editor" | "files" | "shortcuts" | "advanced"; + export function SettingsDialog({ onClose }: Props) { const t = useT(); const [locale, setLocale] = useLocale(); + const [tab, setTab] = useState("appearance"); + const fontSize = useAppStore((s) => s.fontSize); const proseMaxWidth = useAppStore((s) => s.proseMaxWidth); const autosaveMs = useAppStore((s) => s.autosaveMs); @@ -61,6 +65,14 @@ export function SettingsDialog({ onClose }: Props) { setSettings(patch); // live-apply } + const categories: { id: Category; label: string }[] = [ + { id: "appearance", label: t("settings.catAppearance") }, + { id: "editor", label: t("settings.catEditor") }, + { id: "files", label: t("settings.catFiles") }, + { id: "shortcuts", label: t("settings.shortcuts") }, + { id: "advanced", label: t("settings.catAdvanced") }, + ]; + return (
e.stopPropagation()} - className="w-[480px] max-w-[92vw] rounded-lg shadow-2xl bg-canvas-light dark:bg-canvas-dark border border-black/10 dark:border-white/15 p-5" + className="mk-settings w-[760px] max-w-[94vw] h-[560px] max-h-[88vh] rounded-xl overflow-hidden flex flex-col" > -
{t("settings.title")}
- -
- - commit({ fontSize: Number(e.target.value) })} - className="flex-1" - /> - + {/* Header */} +
+
+ {t("settings.title")} +
+ +
- - commit({ proseMaxWidth: Number(e.target.value) })} - className="flex-1" - /> - + {/* Body: category nav + content pane */} +
+ - - commit({ autosaveMs: Number(e.target.value) })} - className="flex-1" - /> - +
+ + + commit({ fontSize: Number(e.target.value) })} + className="w-full" + /> + - - commit({ imagePasteDir: e.target.value })} - placeholder="assets" - className="flex-1 px-2 py-1 rounded border border-black/10 dark:border-white/20 bg-transparent outline-none focus:border-blue-500" - /> - + + commit({ proseMaxWidth: Number(e.target.value) })} + className="w-full" + /> + - - - + + + - - - + + + + - - - - - - - + - - - - - - - - - - + + commit({ imagePasteDir: e.target.value })} + placeholder="assets" + className="mk-settings-input w-[180px] px-2 py-1 text-[12px]" + /> + + - -