feat: add sidebar

This commit is contained in:
Austin Pickett 2026-04-22 23:25:17 -04:00
parent 7db2703b33
commit e5d2815b41
41 changed files with 2469 additions and 1391 deletions

View file

@ -0,0 +1,89 @@
import { useLayoutEffect, useMemo, useState, type ReactNode } from "react";
import { useLocation } from "react-router-dom";
import { PageHeaderContext } from "./page-header-context";
import { resolvePageTitle } from "@/lib/resolve-page-title";
import { cn } from "@/lib/utils";
import { useI18n } from "@/i18n";
export function PageHeaderProvider({
children,
pluginTabs,
}: {
children: ReactNode;
pluginTabs: { path: string; label: string }[];
}) {
const { pathname } = useLocation();
const { t } = useI18n();
const [titleOverride, setTitleOverride] = useState<string | null>(null);
const [afterTitle, setAfterTitle] = useState<ReactNode>(null);
const [end, setEnd] = useState<ReactNode>(null);
// Clear any per-page title / toolbar slots when the path changes. Child routes
// re-fill these on mount via usePageHeader.
/* eslint-disable react-hooks/set-state-in-effect */
useLayoutEffect(() => {
setTitleOverride(null);
setAfterTitle(null);
setEnd(null);
}, [pathname]);
/* eslint-enable react-hooks/set-state-in-effect */
const defaultTitle = useMemo(
() => resolvePageTitle(pathname, t, pluginTabs),
[pathname, t, pluginTabs],
);
const displayTitle = titleOverride ?? defaultTitle;
const value = useMemo(
() => ({
setAfterTitle,
setEnd,
setTitle: setTitleOverride,
}),
[],
);
return (
<PageHeaderContext.Provider value={value}>
<div className="flex min-h-0 w-full min-w-0 flex-1 flex-col overflow-hidden">
<header
className={cn(
"z-1 box-border h-14 shrink-0 border-b border-current/20",
"bg-background-base/40 backdrop-blur-sm",
"overflow-hidden",
)}
role="banner"
>
<div
className={cn(
"flex h-full w-full min-w-0 flex-1 flex-col justify-center gap-2",
"px-3 py-2 sm:px-6",
"min-h-14 sm:min-h-0 sm:flex-row sm:items-center sm:gap-3 sm:py-0",
)}
>
<div className="flex min-w-0 flex-1 items-center gap-2 sm:gap-3">
<h1
className="font-expanded min-w-0 truncate text-sm font-bold tracking-[0.08em] text-midground"
style={{ mixBlendMode: "plus-lighter" }}
>
{displayTitle}
</h1>
{afterTitle}
</div>
{end ? (
<div className="flex w-full min-w-0 justify-end sm:max-w-md sm:flex-1">
{end}
</div>
) : null}
</div>
</header>
<main
className="min-h-0 w-full min-w-0 flex-1 overflow-y-auto overflow-x-hidden"
>
{children}
</main>
</div>
</PageHeaderContext.Provider>
);
}