253 lines
10 KiB
TypeScript
253 lines
10 KiB
TypeScript
import Icon from "@mdi/react";
|
|
import { BookPlus, Calendar, ChevronsUpDown, Home, Inbox, ListTodo, LogOut, Monitor, Moon, Palette, PanelLeft, Plus, Search, Sun, Waypoints } from "lucide-react";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Sidebar, SidebarContent, SidebarFooter, SidebarGroup, SidebarGroupContent, SidebarGroupLabel, SidebarMenu, SidebarMenuBadge, SidebarMenuButton, SidebarMenuItem, useSidebar } from "~/components/ui/sidebar";
|
|
import { type ColorName, colors } from "~/lib/color";
|
|
import { type IconName, icons } from "~/lib/icon";
|
|
import { NewNoteDialog } from "~/components/note/new_note_dialog";
|
|
import { NewCollectionDialog } from "~/components/collection/new_collection_dialog";
|
|
import { cn } from "~/lib/utils";
|
|
import { useCollectionNotesMetadata, useCollections } from "~/hooks/use-metadata";
|
|
import { useIsMobile } from "~/hooks/use-mobile";
|
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from "~/components/ui/dropdown-menu";
|
|
import { UserAvatar } from "~/components/user/user_avatar";
|
|
import { Link } from "@tanstack/react-router";
|
|
import { useTheme } from "~/hooks/use-theme";
|
|
import { ToggleGroup, ToggleGroupItem } from "./ui/toggle-group";
|
|
|
|
export function AppSidebar() {
|
|
const collections = useCollections();
|
|
|
|
return (
|
|
<Sidebar variant="inset" collapsible="icon">
|
|
<SidebarContent>
|
|
<HeaderButtons>
|
|
<NewNoteDialog>
|
|
<Button variant="ghost"><Plus /></Button>
|
|
</NewNoteDialog>
|
|
<NewCollectionDialog>
|
|
<Button variant="ghost"><BookPlus /></Button>
|
|
</NewCollectionDialog>
|
|
</HeaderButtons>
|
|
<SidebarGroup>
|
|
<SidebarMenu>
|
|
<NavButton href="/app/" label="Overview" icon={<Home />} />
|
|
<NavButton href="/app/search" label="Search" icon={<Search />} />
|
|
<InboxButton />
|
|
<NavButton href="/app/graph" label="Graph" icon={<Waypoints />} />
|
|
<NavButton href="/app/calendar" label="Calendar" icon={<Calendar />} />
|
|
<NavButton href="/app/todo" label="Todo" icon={<ListTodo />} />
|
|
</SidebarMenu>
|
|
</SidebarGroup>
|
|
<SidebarGroup>
|
|
<SidebarGroupLabel>Collections</SidebarGroupLabel>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{collections.map((collection) => (<CollectionButton
|
|
key={collection.get("id")}
|
|
id={collection.get("id")}
|
|
name={collection.get("name")}
|
|
icon={collection.get("icon")}
|
|
color={collection.get("color")}
|
|
/>))}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
<SidebarFooter>
|
|
<SidebarMenu>
|
|
<SidebarToggle />
|
|
<NavUser />
|
|
</SidebarMenu>
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
);
|
|
}
|
|
|
|
type NavButtonProps = {
|
|
href: string;
|
|
label: string;
|
|
icon?: React.ReactNode;
|
|
children?: React.ReactNode;
|
|
}
|
|
|
|
function NavButton(props: NavButtonProps) {
|
|
const { setOpenMobile } = useSidebar();
|
|
|
|
return (
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild>
|
|
<Link to={props.href} onClick={() => setOpenMobile(false)}>
|
|
{props.icon}
|
|
<span>{props.label}</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
{props.children}
|
|
</SidebarMenuItem>
|
|
);
|
|
}
|
|
|
|
function HeaderButtons(props: { children: React.ReactNode }) {
|
|
return (
|
|
<div className={cn(
|
|
"flex flex-row justify-between",
|
|
"transition-[marign,opa] duration-200 ease-linear mt-0",
|
|
"group-data-[collapsible=icon]:opacity-0 group-data-[collapsible=icon]:-mt-9")}>
|
|
{props.children}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
type CollectionButtonProps = {
|
|
id: string;
|
|
name: string;
|
|
icon: IconName | "";
|
|
color: ColorName | "";
|
|
}
|
|
|
|
function CollectionButton(props: CollectionButtonProps) {
|
|
const icon = props.icon && icons[props.icon];
|
|
const color = props.color ? colors[props.color] : colors.white;
|
|
|
|
return (
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild>
|
|
<Link
|
|
to="/app/collection/$id"
|
|
params={{ id: props.id }}
|
|
className="w-full flex items-center gap-2 group/item"
|
|
>
|
|
<div className="flex items-center justify-center h-4 w-4">
|
|
{icon && <Icon path={icon.path} size={0.75} color={color.base} />}
|
|
</div>
|
|
<span
|
|
className="group-hover/item:text-[var(--hover-color)]"
|
|
style={{ "--hover-color": color.hover, }}
|
|
>
|
|
{props.name}
|
|
</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
);
|
|
}
|
|
|
|
function SidebarToggle() {
|
|
const isMobile = useIsMobile();
|
|
const { toggleSidebar } = useSidebar()
|
|
|
|
if (isMobile) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton asChild>
|
|
<Button
|
|
data-sidebar="trigger"
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={toggleSidebar}
|
|
className="justify-start"
|
|
>
|
|
<PanelLeft />
|
|
<span>Toggle Sidebar</span>
|
|
</Button>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
);
|
|
}
|
|
|
|
function InboxButton() {
|
|
const notes = useCollectionNotesMetadata("");
|
|
const count = notes.length;
|
|
|
|
return (
|
|
<NavButton href="/app/inbox" label="Inbox" icon={<Inbox />}>
|
|
<SidebarMenuBadge>{count}</SidebarMenuBadge>
|
|
</NavButton>
|
|
);
|
|
}
|
|
|
|
function NavUser() {
|
|
const isMobile = useIsMobile();
|
|
const [theme, setTheme] = useTheme();
|
|
// const { data: session } = useSession();
|
|
// const user = session?.user;
|
|
const user = {
|
|
name: "Kalle Struik",
|
|
email: "kalle@kallestruik.nl"
|
|
};
|
|
|
|
const email = user?.email ?? undefined;
|
|
const name = user?.name ?? undefined
|
|
|
|
function handleSignOut() {
|
|
// // End the session
|
|
// signOut();
|
|
// // Clear all locally stored data
|
|
// localStorage.clear();
|
|
// indexedDB.databases().then((dbs) => {
|
|
// dbs.forEach((db) => {
|
|
// db.name && indexedDB.deleteDatabase(db.name);
|
|
// });
|
|
// });
|
|
}
|
|
|
|
return (
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<SidebarMenuButton
|
|
size="lg"
|
|
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
|
|
>
|
|
<UserAvatar user={user} className="h-8 w-8 rounded-lg" />
|
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
<span className="truncate font-semibold">{name}</span>
|
|
<span className="truncate text-xs">{email}</span>
|
|
</div>
|
|
<ChevronsUpDown className="ml-auto size-4" />
|
|
</SidebarMenuButton>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
className="w-[--radix-dropdown-menu-trigger-width] min-w-56 rounded-lg"
|
|
side={isMobile ? "top" : "right"}
|
|
align="end"
|
|
sideOffset={4}
|
|
>
|
|
<DropdownMenuLabel className="p-0 font-normal">
|
|
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
|
|
<UserAvatar user={user} className="h-8 w-8 rounded-lg" />
|
|
<div className="grid flex-1 text-left text-sm leading-tight">
|
|
<span className="truncate font-semibold">{name}</span>
|
|
<span className="truncate text-xs">{email}</span>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuLabel className="py-0">
|
|
<div className="flex flex-row place-content-between w-full items-center">
|
|
<div className="flex flex-row items-center gap-2">
|
|
<Palette className="size-4 text-muted-foreground" />
|
|
Theme
|
|
</div>
|
|
<ToggleGroup type="single" size="sm" defaultValue={theme}>
|
|
<ToggleGroupItem value="system" onClick={() => setTheme("system")}><Monitor /></ToggleGroupItem>
|
|
<ToggleGroupItem value="light" onClick={() => setTheme("light")}><Sun /></ToggleGroupItem>
|
|
<ToggleGroupItem value="dark" onClick={() => setTheme("dark")}><Moon /></ToggleGroupItem>
|
|
</ToggleGroup>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={handleSignOut}>
|
|
<LogOut />
|
|
Log out
|
|
</DropdownMenuItem>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
);
|
|
}
|