feat(nav): group rail modules + move chrome controls into the sidebar
- Split the rail into a content group (Feed/Channels/Playlists) and an admin group (Stats/Scheduler) separated by a divider (system group hidden for non-admins). - Move the language switcher, About and notification bell out of the top header into an icon cluster above Settings (horizontal expanded, vertical collapsed). Their popovers portal to <body> and anchor right + above the button, escaping the nav's backdrop-filter. About is removed from the account popover (now in the cluster). - LanguageSwitcher/NotificationCenter gain a 'rail' variant for the above. - Also relocate the Toaster mount into the (now relative) content column.
This commit is contained in:
parent
dfaa1551dc
commit
df0ca24a23
5 changed files with 194 additions and 54 deletions
|
|
@ -16,9 +16,12 @@ import {
|
|||
Tv,
|
||||
UserPlus,
|
||||
} from "lucide-react";
|
||||
import { api, type Me } from "../lib/api";
|
||||
import { api, type FeedFilters, type Me } from "../lib/api";
|
||||
import type { Page } from "../lib/urlState";
|
||||
import { type LangCode } from "../i18n";
|
||||
import AvatarImg from "./Avatar";
|
||||
import LanguageSwitcher from "./LanguageSwitcher";
|
||||
import NotificationCenter from "./NotificationCenter";
|
||||
|
||||
// Primary app navigation: a collapsible left rail with icon+label entries (Design C). The
|
||||
// modules used to hide under the avatar dropdown; they now live here. Collapsed, it becomes
|
||||
|
|
@ -28,11 +31,19 @@ export default function NavSidebar({
|
|||
page,
|
||||
setPage,
|
||||
onOpenAbout,
|
||||
onChangeLanguage,
|
||||
language,
|
||||
filters,
|
||||
setFilters,
|
||||
}: {
|
||||
me: Me;
|
||||
page: Page;
|
||||
setPage: (p: Page) => void;
|
||||
onOpenAbout: () => void;
|
||||
onChangeLanguage: (code: LangCode) => void;
|
||||
language: LangCode;
|
||||
filters: FeedFilters;
|
||||
setFilters: (f: FeedFilters) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [collapsed, setCollapsed] = useState<boolean>(
|
||||
|
|
@ -105,20 +116,40 @@ export default function NavSidebar({
|
|||
}
|
||||
}
|
||||
|
||||
const items: { page: Page; icon: typeof Home; label: string }[] = [
|
||||
type NavItem = { page: Page; icon: typeof Home; label: string };
|
||||
// User-facing content modules vs. admin/system modules, separated by a divider in the rail.
|
||||
const userItems: NavItem[] = [
|
||||
{ page: "feed", icon: Home, label: t("header.account.feed") },
|
||||
{ page: "channels", icon: Tv, label: t("header.account.channels") },
|
||||
{ page: "playlists", icon: ListVideo, label: t("header.account.playlists") },
|
||||
];
|
||||
if (me.role === "admin") {
|
||||
items.push({ page: "stats", icon: BarChart3, label: t("header.account.stats") });
|
||||
items.push({ page: "scheduler", icon: Activity, label: t("header.account.scheduler") });
|
||||
}
|
||||
const systemItems: NavItem[] =
|
||||
me.role === "admin"
|
||||
? [
|
||||
{ page: "stats", icon: BarChart3, label: t("header.account.stats") },
|
||||
{ page: "scheduler", icon: Activity, label: t("header.account.scheduler") },
|
||||
]
|
||||
: [];
|
||||
|
||||
const rowBase =
|
||||
"w-full flex items-center gap-3 rounded-lg px-2.5 py-2 text-sm transition";
|
||||
const name = me.display_name ?? me.email.split("@")[0];
|
||||
|
||||
const renderItem = ({ page: p, icon: Icon, label }: NavItem) => (
|
||||
<button
|
||||
key={p}
|
||||
onClick={() => setPage(p)}
|
||||
title={collapsed ? label : undefined}
|
||||
aria-current={page === p ? "page" : undefined}
|
||||
className={`${rowBase} ${collapsed ? "justify-center px-0" : ""} ${
|
||||
page === p ? "bg-accent text-accent-fg" : "text-muted hover:text-fg hover:bg-card"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-[18px] h-[18px] shrink-0" />
|
||||
{!collapsed && <span className="truncate">{label}</span>}
|
||||
</button>
|
||||
);
|
||||
|
||||
return (
|
||||
<nav
|
||||
className={`glass relative shrink-0 border-r border-border flex flex-col py-3 transition-[width] ${
|
||||
|
|
@ -151,25 +182,30 @@ export default function NavSidebar({
|
|||
</div>
|
||||
|
||||
<div className="flex-1 min-h-0 overflow-y-auto flex flex-col gap-1">
|
||||
{items.map(({ page: p, icon: Icon, label }) => (
|
||||
<button
|
||||
key={p}
|
||||
onClick={() => setPage(p)}
|
||||
title={collapsed ? label : undefined}
|
||||
aria-current={page === p ? "page" : undefined}
|
||||
className={`${rowBase} ${collapsed ? "justify-center px-0" : ""} ${
|
||||
page === p
|
||||
? "bg-accent text-accent-fg"
|
||||
: "text-muted hover:text-fg hover:bg-card"
|
||||
}`}
|
||||
>
|
||||
<Icon className="w-[18px] h-[18px] shrink-0" />
|
||||
{!collapsed && <span className="truncate">{label}</span>}
|
||||
</button>
|
||||
))}
|
||||
{userItems.map(renderItem)}
|
||||
{systemItems.length > 0 && (
|
||||
<div className="my-1.5 border-t border-border/70" />
|
||||
)}
|
||||
{systemItems.map(renderItem)}
|
||||
</div>
|
||||
|
||||
<div className="mt-2 pt-2 border-t border-border flex flex-col gap-1">
|
||||
<div
|
||||
className={`flex items-center justify-center gap-2 pb-2 mb-1 border-b border-border ${
|
||||
collapsed ? "flex-col" : "flex-row"
|
||||
}`}
|
||||
>
|
||||
<LanguageSwitcher variant="rail" value={language} onChange={onChangeLanguage} />
|
||||
<button
|
||||
onClick={onOpenAbout}
|
||||
title={t("header.account.about")}
|
||||
aria-label={t("header.account.about")}
|
||||
className="p-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
|
||||
>
|
||||
<Info className="w-5 h-5" />
|
||||
</button>
|
||||
<NotificationCenter variant="rail" filters={filters} setFilters={setFilters} />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setPage("settings")}
|
||||
title={collapsed ? t("header.account.settings") : undefined}
|
||||
|
|
@ -257,16 +293,6 @@ export default function NavSidebar({
|
|||
<UserPlus className="w-4 h-4" />
|
||||
{t("header.account.addAccount")}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
onOpenAbout();
|
||||
setAcctOpen(false);
|
||||
}}
|
||||
className="w-full flex items-center gap-2 text-sm px-2 py-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
|
||||
>
|
||||
<Info className="w-4 h-4" />
|
||||
{t("header.account.about")}
|
||||
</button>
|
||||
<button
|
||||
onClick={logout}
|
||||
className="w-full flex items-center gap-2 text-sm px-2 py-2 rounded-lg text-muted hover:text-fg hover:bg-card transition"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue