diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 23d4f13..039c216 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,6 +16,7 @@ import { type SidebarLayout, } from "./lib/sidebarLayout"; import { configureNotifications } from "./lib/notifications"; +import { setHintsEnabled } from "./lib/hints"; import Login from "./components/Login"; import Header from "./components/Header"; import Sidebar from "./components/Sidebar"; @@ -100,6 +101,9 @@ export default function App() { saveLayoutLocal(l); } if (prefs.notifications) configureNotifications(prefs.notifications); + if (typeof prefs.hints === "boolean") setHintsEnabled(prefs.hints); + if (typeof prefs.performanceMode === "boolean") + document.documentElement.dataset.perf = prefs.performanceMode ? "1" : ""; // eslint-disable-next-line react-hooks/exhaustive-deps }, [meQuery.data?.id]); diff --git a/frontend/src/components/Channels.tsx b/frontend/src/components/Channels.tsx index a5e30b6..61f811b 100644 --- a/frontend/src/components/Channels.tsx +++ b/frontend/src/components/Channels.tsx @@ -12,6 +12,7 @@ import { } from "lucide-react"; import { api, type ManagedChannel, type Tag } from "../lib/api"; import { notify } from "../lib/notifications"; +import Tooltip from "./Tooltip"; export default function Channels({ onViewChannel, @@ -76,11 +77,23 @@ export default function Channels({ {/* Per-user sync status */} {s && (
- - - - - + + + + +
)} @@ -166,23 +179,35 @@ export default function Channels({ ); } -function Stat({ label, value }: { label: string; value: string | number }) { +function Stat({ + label, + value, + hint, +}: { + label: string; + value: string | number; + hint?: string; +}) { return ( - - {value} {label} - + + + {value} {label} + + ); } -function SyncBadge({ ok, label }: { ok: boolean; label: string }) { +function SyncBadge({ ok, label, hint }: { ok: boolean; label: string; hint?: string }) { return ( - - {label} - + + + {label} + + ); } @@ -203,7 +228,7 @@ function ChannelRow({ }) { return (
@@ -232,8 +257,16 @@ function ChannelRow({ {c.subscriber_count != null && · {c.subscriber_count.toLocaleString()} subs}
- - + + {userTags.map((t) => { const on = c.tag_ids.includes(t.id); return ( diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index a5f2bd2..22f2dc9 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -36,7 +36,7 @@ function ThemeMenu({ setTheme: (t: ThemePrefs) => void; }) { return ( -
+
Color scheme
{SCHEMES.map((s) => ( @@ -219,7 +219,7 @@ function AccountMenu({ {open && ( -
+
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx index a79f418..3c20228 100644 --- a/frontend/src/components/Login.tsx +++ b/frontend/src/components/Login.tsx @@ -1,7 +1,7 @@ export default function Login() { return (
-
+
Subfeed
diff --git a/frontend/src/components/NotificationCenter.tsx b/frontend/src/components/NotificationCenter.tsx index 38bf67c..5ba96e4 100644 --- a/frontend/src/components/NotificationCenter.tsx +++ b/frontend/src/components/NotificationCenter.tsx @@ -90,7 +90,7 @@ export default function NotificationCenter({ {open && ( -
+
Notifications
{notifications.length > 0 && ( diff --git a/frontend/src/components/SettingsPanel.tsx b/frontend/src/components/SettingsPanel.tsx index 553af8a..0959912 100644 --- a/frontend/src/components/SettingsPanel.tsx +++ b/frontend/src/components/SettingsPanel.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useCallback, useEffect, useState, useSyncExternalStore } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Bell, Monitor, Pause, Play, RefreshCw, User, X } from "lucide-react"; import { SCHEMES, type Scheme, type ThemePrefs } from "../lib/theme"; @@ -9,6 +9,8 @@ import { notify, type NotifSettings, } from "../lib/notifications"; +import { hintsEnabled, setHintsEnabled, subscribeHints } from "../lib/hints"; +import Tooltip from "./Tooltip"; type TabId = "appearance" | "notifications" | "sync" | "account"; const TABS: { id: TabId; label: string; icon: typeof Monitor }[] = [ @@ -34,39 +36,65 @@ export default function SettingsPanel({ onClose: () => void; }) { const [tab, setTab] = useState("appearance"); + const [closing, setClosing] = useState(false); + + const close = useCallback(() => { + setClosing(true); + setTimeout(onClose, 190); + }, [onClose]); useEffect(() => { function onKey(e: KeyboardEvent) { - if (e.key === "Escape") onClose(); + if (e.key === "Escape") close(); } document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); - }, [onClose]); + }, [close]); return (
-
-
-
+
+
+
Settings
-
-
- {TABS.map((t) => ( - - ))} + {/* Wrapping pill tabs — no horizontal scrollbar, prominent active state. */} +
+ {TABS.map((t) => { + const active = tab === t.id; + return ( + + ); + })}
@@ -91,12 +119,28 @@ function Section({ title, children }: { title: string; children: React.ReactNode ); } -function Row({ label, children }: { label: string; children: React.ReactNode }) { +function Row({ + label, + hint, + children, +}: { + label: string; + hint?: string; + children: React.ReactNode; +}) { return ( -