From d30dc7f7608294325e71e4b886cc80132a5c6e3e Mon Sep 17 00:00:00 2001 From: npeter83 Date: Tue, 16 Jun 2026 01:05:05 +0200 Subject: [PATCH] feat(nav): Settings as a page module (Design B) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move Settings out of the right-side overlay into a left-nav page (page='settings'), so it opens where you're already looking — no cross-screen mouse travel. The Settings rail item now sets the page (with active highlight) instead of opening a dialog; the panel is refactored to an in-flow .glass card (keeps the frosted look over the ambient backdrop), with the page title shown in the header. Removed the overlay + Esc/backdrop close path. --- frontend/src/App.tsx | 33 +++--- frontend/src/components/Header.tsx | 4 +- frontend/src/components/NavSidebar.tsx | 11 +- frontend/src/components/SettingsPanel.tsx | 119 ++++++++-------------- frontend/src/lib/urlState.ts | 6 +- 5 files changed, 71 insertions(+), 102 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9b12ad3..8bee085 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -57,7 +57,13 @@ function loadInitialPage(): Page { const params = new URLSearchParams(window.location.search); if (params.has("page")) return readPage(); const stored = localStorage.getItem(PAGE_KEY); - if (stored === "channels" || stored === "stats" || stored === "playlists") return stored; + if ( + stored === "channels" || + stored === "stats" || + stored === "playlists" || + stored === "settings" + ) + return stored; return "feed"; } @@ -83,7 +89,6 @@ export default function App() { const [view, setView] = useState<"grid" | "list">("grid"); const [sidebarLayout, setSidebarLayoutState] = useState(loadLayout); const [page, setPageState] = useState(loadInitialPage); - const [settingsOpen, setSettingsOpen] = useState(false); const [wizardOpen, setWizardOpen] = useState(false); const [channelFilter, setChannelFilter] = useState("all"); const [aboutOpen, setAboutOpen] = useState(false); @@ -198,7 +203,6 @@ export default function App() { me={meQuery.data!} page={page} setPage={setPage} - onOpenSettings={() => setSettingsOpen(true)} onOpenAbout={() => setAboutOpen(true)} />
@@ -239,6 +243,15 @@ export default function App() { ) : page === "playlists" ? ( + ) : page === "settings" ? ( + setWizardOpen(true)} + /> ) : (
- {settingsOpen && ( - setSettingsOpen(false)} - onOpenWizard={() => { - setSettingsOpen(false); - setWizardOpen(true); - }} - /> - )} {wizardOpen && meQuery.data && ( setWizardOpen(false)} /> )} diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 8bf9752..840e567 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -76,7 +76,9 @@ export default function Header({ ? t("header.usageStats") : page === "playlists" ? t("header.account.playlists") - : t("header.channelManager")} + : page === "settings" + ? t("settings.title") + : t("header.channelManager")} )} diff --git a/frontend/src/components/NavSidebar.tsx b/frontend/src/components/NavSidebar.tsx index 1ea7c09..827ce62 100644 --- a/frontend/src/components/NavSidebar.tsx +++ b/frontend/src/components/NavSidebar.tsx @@ -23,13 +23,11 @@ export default function NavSidebar({ me, page, setPage, - onOpenSettings, onOpenAbout, }: { me: Me; page: Page; setPage: (p: Page) => void; - onOpenSettings: () => void; onOpenAbout: () => void; }) { const { t } = useTranslation(); @@ -115,9 +113,14 @@ export default function NavSidebar({
-
- -
- {/* Vertical tab rail — never wraps; active is a clear accent pill. */} - - - {/* All tabs stacked in one grid cell so the panel sizes to the tallest tab - (stable height, no jump on switch); the active one is shown on top. */} -
- {TABS.map((tabItem) => ( -
+
+ {/* Vertical tab rail — never wraps; active is a clear accent pill. */} +
- ))} -
+ + {t(`settings.tabs.${tabItem.id}`)} + + ); + })} + + + {/* All tabs stacked in one grid cell so the panel sizes to the tallest tab + (stable height, no jump on switch); the active one is shown on top. */} +
+ {TABS.map((tabItem) => ( +
+ {tabItem.id === "appearance" && ( + + )} + {tabItem.id === "notifications" && } + {tabItem.id === "sync" && } + {tabItem.id === "account" && } +
+ ))}
diff --git a/frontend/src/lib/urlState.ts b/frontend/src/lib/urlState.ts index 325f742..a0ad9d2 100644 --- a/frontend/src/lib/urlState.ts +++ b/frontend/src/lib/urlState.ts @@ -78,11 +78,13 @@ export function hasFilterParams(params: URLSearchParams): boolean { return KEYS.some((k) => params.has(k)); } -export type Page = "feed" | "channels" | "stats" | "playlists"; +export type Page = "feed" | "channels" | "stats" | "playlists" | "settings"; export function readPage(): Page { const p = new URLSearchParams(window.location.search).get("page"); - return p === "channels" || p === "stats" || p === "playlists" ? p : "feed"; + return p === "channels" || p === "stats" || p === "playlists" || p === "settings" + ? p + : "feed"; } /** Build a shareable absolute URL that reproduces the current filters (+ page). Used by the