diff options
| author | Fuwn <[email protected]> | 2026-02-09 21:24:24 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-09 21:24:24 -0800 |
| commit | 047444beb0663d099717c9c392496fc662f79501 (patch) | |
| tree | 8b878381ec5912c0275c4008bc6af470ab965cc7 /apps/web/app/reader/settings | |
| parent | ui: reorder sidebar footer, import/export tab, and subscription actions (diff) | |
| download | asa.news-047444beb0663d099717c9c392496fc662f79501.tar.xz asa.news-047444beb0663d099717c9c392496fc662f79501.zip | |
ui: group appearance settings into collapsible sections
Diffstat (limited to 'apps/web/app/reader/settings')
| -rw-r--r-- | apps/web/app/reader/settings/_components/appearance-settings.tsx | 425 |
1 files changed, 228 insertions, 197 deletions
diff --git a/apps/web/app/reader/settings/_components/appearance-settings.tsx b/apps/web/app/reader/settings/_components/appearance-settings.tsx index 0f0d793..babf1c0 100644 --- a/apps/web/app/reader/settings/_components/appearance-settings.tsx +++ b/apps/web/app/reader/settings/_components/appearance-settings.tsx @@ -3,6 +3,25 @@ import { useTheme } from "next-themes" import { useUserInterfaceStore } from "@/lib/stores/user-interface-store" +function SettingsSection({ + title, + defaultOpen, + children, +}: { + title: string + defaultOpen?: boolean + children: React.ReactNode +}) { + return ( + <details open={defaultOpen} className="group border-b border-border last:border-b-0"> + <summary className="cursor-pointer select-none px-4 py-3 text-text-primary transition-colors hover:bg-background-tertiary"> + {title} + </summary> + <div className="space-y-6 px-4 pb-4">{children}</div> + </details> + ) +} + export function AppearanceSettings() { const { theme, setTheme } = useTheme() const entryListViewMode = useUserInterfaceStore( @@ -67,206 +86,218 @@ export function AppearanceSettings() { const setToolbarPosition = useUserInterfaceStore( (state) => state.setToolbarPosition ) + return ( - <div className="px-4 py-3"> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">theme</h3> - <p className="mb-3 text-text-dim"> - controls the colour scheme of the application - </p> - <select - value={theme ?? "system"} - onChange={(event) => setTheme(event.target.value)} - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="system">system</option> - <option value="light">light</option> - <option value="dark">dark</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">toolbar position</h3> - <p className="mb-3 text-text-dim"> - place the toolbar at the top or bottom of the reader - </p> - <select - value={toolbarPosition} - onChange={(event) => - setToolbarPosition(event.target.value as "top" | "bottom") - } - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="top">top</option> - <option value="bottom">bottom</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">display density</h3> - <p className="mb-3 text-text-dim"> - controls the overall text size and spacing - </p> - <select - value={displayDensity} - onChange={(event) => - setDisplayDensity( - event.target.value as "compact" | "default" | "spacious" - ) - } - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="compact">compact</option> - <option value="default">default</option> - <option value="spacious">spacious</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">entry list view</h3> - <p className="mb-3 text-text-dim"> - controls how entries are displayed in the list - </p> - <select - value={entryListViewMode} - onChange={(event) => - setEntryListViewMode( - event.target.value as "compact" | "comfortable" | "expanded" - ) - } - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="compact">compact</option> - <option value="comfortable">comfortable</option> - <option value="expanded">expanded</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">feed favicons</h3> - <p className="mb-3 text-text-dim"> - show website icons next to feed names in the sidebar - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={showFeedFavicons} - onChange={(event) => setShowFeedFavicons(event.target.checked)} - className="accent-text-primary" - /> - <span>show favicons</span> - </label> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">entry favicons</h3> - <p className="mb-3 text-text-dim"> - show website icons next to feed names in the entry list - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={showEntryFavicons} - onChange={(event) => setShowEntryFavicons(event.target.checked)} - className="accent-text-primary" - /> - <span>show entry favicons</span> - </label> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">focus follows interaction</h3> - <p className="mb-3 text-text-dim"> - automatically move keyboard panel focus to the last pane you - interacted with (clicked or scrolled) - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={focusFollowsInteraction} + <div className="py-1"> + <SettingsSection title="general" defaultOpen> + <div> + <h3 className="mb-2 text-text-primary">theme</h3> + <p className="mb-3 text-text-dim"> + controls the colour scheme of the application + </p> + <select + value={theme ?? "system"} + onChange={(event) => setTheme(event.target.value)} + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="system">system</option> + <option value="light">light</option> + <option value="dark">dark</option> + </select> + </div> + <div> + <h3 className="mb-2 text-text-primary">font size</h3> + <p className="mb-3 text-text-dim"> + controls the base text size in the reader + </p> + <select + value={fontSize} + onChange={(event) => + setFontSize(event.target.value as "small" | "default" | "large") + } + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="small">small</option> + <option value="default">default</option> + <option value="large">large</option> + </select> + </div> + <div> + <h3 className="mb-2 text-text-primary">display density</h3> + <p className="mb-3 text-text-dim"> + controls the overall text size and spacing + </p> + <select + value={displayDensity} + onChange={(event) => + setDisplayDensity( + event.target.value as "compact" | "default" | "spacious" + ) + } + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="compact">compact</option> + <option value="default">default</option> + <option value="spacious">spacious</option> + </select> + </div> + <div> + <h3 className="mb-2 text-text-primary">toolbar position</h3> + <p className="mb-3 text-text-dim"> + place the toolbar at the top or bottom of the reader + </p> + <select + value={toolbarPosition} + onChange={(event) => + setToolbarPosition(event.target.value as "top" | "bottom") + } + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="top">top</option> + <option value="bottom">bottom</option> + </select> + </div> + </SettingsSection> + + <SettingsSection title="entry list"> + <div> + <h3 className="mb-2 text-text-primary">entry list view</h3> + <p className="mb-3 text-text-dim"> + controls how entries are displayed in the list + </p> + <select + value={entryListViewMode} onChange={(event) => - setFocusFollowsInteraction(event.target.checked) + setEntryListViewMode( + event.target.value as "compact" | "comfortable" | "expanded" + ) } - className="accent-text-primary" - /> - <span>enable focus follows interaction</span> - </label> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">font size</h3> - <p className="mb-3 text-text-dim"> - controls the base text size in the reader - </p> - <select - value={fontSize} - onChange={(event) => - setFontSize(event.target.value as "small" | "default" | "large") - } - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="small">small</option> - <option value="default">default</option> - <option value="large">large</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">time display</h3> - <p className="mb-3 text-text-dim"> - choose between relative timestamps (e.g. “2h ago”) or - absolute dates - </p> - <select - value={timeDisplayFormat} - onChange={(event) => - setTimeDisplayFormat( - event.target.value as "relative" | "absolute" - ) - } - className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" - > - <option value="relative">relative</option> - <option value="absolute">absolute</option> - </select> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">entry images</h3> - <p className="mb-3 text-text-dim"> - show thumbnail images next to entries in the expanded view - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={showEntryImages} - onChange={(event) => setShowEntryImages(event.target.checked)} - className="accent-text-primary" - /> - <span>show entry images</span> - </label> - </div> - <div className="mb-6"> - <h3 className="mb-2 text-text-primary">sidebar ordering</h3> - <p className="mb-3 text-text-dim"> - display folders above ungrouped feeds in the sidebar - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={showFoldersAboveFeeds} - onChange={(event) => setShowFoldersAboveFeeds(event.target.checked)} - className="accent-text-primary" - /> - <span>show folders above feeds</span> - </label> - </div> - <div> - <h3 className="mb-2 text-text-primary">reading time</h3> - <p className="mb-3 text-text-dim"> - display estimated reading time for articles - </p> - <label className="flex cursor-pointer items-center gap-2 text-text-primary"> - <input - type="checkbox" - checked={showReadingTime} - onChange={(event) => setShowReadingTime(event.target.checked)} - className="accent-text-primary" - /> - <span>show reading time</span> - </label> - </div> + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="compact">compact</option> + <option value="comfortable">comfortable</option> + <option value="expanded">expanded</option> + </select> + </div> + <div> + <h3 className="mb-2 text-text-primary">entry images</h3> + <p className="mb-3 text-text-dim"> + show thumbnail images next to entries in the expanded view + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showEntryImages} + onChange={(event) => setShowEntryImages(event.target.checked)} + className="accent-text-primary" + /> + <span>show entry images</span> + </label> + </div> + <div> + <h3 className="mb-2 text-text-primary">entry favicons</h3> + <p className="mb-3 text-text-dim"> + show website icons next to feed names in the entry list + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showEntryFavicons} + onChange={(event) => setShowEntryFavicons(event.target.checked)} + className="accent-text-primary" + /> + <span>show entry favicons</span> + </label> + </div> + <div> + <h3 className="mb-2 text-text-primary">reading time</h3> + <p className="mb-3 text-text-dim"> + display estimated reading time for articles + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showReadingTime} + onChange={(event) => setShowReadingTime(event.target.checked)} + className="accent-text-primary" + /> + <span>show reading time</span> + </label> + </div> + <div> + <h3 className="mb-2 text-text-primary">time display</h3> + <p className="mb-3 text-text-dim"> + choose between relative timestamps (e.g. “2h ago”) or + absolute dates + </p> + <select + value={timeDisplayFormat} + onChange={(event) => + setTimeDisplayFormat( + event.target.value as "relative" | "absolute" + ) + } + className="border border-border bg-background-primary px-3 py-2 text-text-primary outline-none focus:border-text-dim" + > + <option value="relative">relative</option> + <option value="absolute">absolute</option> + </select> + </div> + </SettingsSection> + + <SettingsSection title="sidebar"> + <div> + <h3 className="mb-2 text-text-primary">feed favicons</h3> + <p className="mb-3 text-text-dim"> + show website icons next to feed names in the sidebar + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showFeedFavicons} + onChange={(event) => setShowFeedFavicons(event.target.checked)} + className="accent-text-primary" + /> + <span>show favicons</span> + </label> + </div> + <div> + <h3 className="mb-2 text-text-primary">sidebar ordering</h3> + <p className="mb-3 text-text-dim"> + display folders above ungrouped feeds in the sidebar + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={showFoldersAboveFeeds} + onChange={(event) => setShowFoldersAboveFeeds(event.target.checked)} + className="accent-text-primary" + /> + <span>show folders above feeds</span> + </label> + </div> + </SettingsSection> + + <SettingsSection title="behaviour"> + <div> + <h3 className="mb-2 text-text-primary">focus follows interaction</h3> + <p className="mb-3 text-text-dim"> + automatically move keyboard panel focus to the last pane you + interacted with (clicked or scrolled) + </p> + <label className="flex cursor-pointer items-center gap-2 text-text-primary"> + <input + type="checkbox" + checked={focusFollowsInteraction} + onChange={(event) => + setFocusFollowsInteraction(event.target.checked) + } + className="accent-text-primary" + /> + <span>enable focus follows interaction</span> + </label> + </div> + </SettingsSection> </div> ) } |