mirror of
https://github.com/temetro/temetro.git
synced 2026-08-21 07:26:45 +00:00
97bf9ed8cc
- Linear-inspired dark theme (app/globals.css), forced dark in layout
- Clinical sidebar (components/sidebar-02) with temetro brand
- AI chat UI (components/chat): Cursor-style input, UI-only mock replies
- Settings page (components/settings): Polar-style tabs/sections, UI only
- Route-group app shell: app/(app)/{layout,page,settings}
- shadcn (Base UI) + ai-elements component libraries
- CLAUDE.md: architecture notes + per-folder commit policy
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
107 lines
4.4 KiB
TypeScript
107 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
Collapsible,
|
|
CollapsibleContent,
|
|
CollapsibleTrigger,
|
|
} from "@/components/ui/collapsible";
|
|
import {
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarMenuSub,
|
|
SidebarMenuSubButton,
|
|
SidebarMenuItem as SidebarMenuSubItem,
|
|
useSidebar,
|
|
} from "@/components/ui/sidebar";
|
|
import { cn } from "@/lib/utils";
|
|
import { ChevronDown, ChevronUp } from "lucide-react";
|
|
import Link from "next/link";
|
|
import type React from "react";
|
|
import { useState } from "react";
|
|
|
|
export type Route = {
|
|
id: string;
|
|
title: string;
|
|
icon?: React.ReactNode;
|
|
link: string;
|
|
subs?: {
|
|
title: string;
|
|
link: string;
|
|
icon?: React.ReactNode;
|
|
}[];
|
|
};
|
|
|
|
export default function DashboardNavigation({ routes }: { routes: Route[] }) {
|
|
const { state } = useSidebar();
|
|
const isCollapsed = state === "collapsed";
|
|
const [openCollapsible, setOpenCollapsible] = useState<string | null>(null);
|
|
|
|
return (
|
|
<SidebarMenu>
|
|
{routes.map((route) => {
|
|
const isOpen = !isCollapsed && openCollapsible === route.id;
|
|
const hasSubRoutes = !!route.subs?.length;
|
|
|
|
return (
|
|
<SidebarMenuItem key={route.id}>
|
|
{hasSubRoutes ? (
|
|
<Collapsible
|
|
open={isOpen}
|
|
onOpenChange={(open) =>
|
|
setOpenCollapsible(open ? route.id : null)
|
|
}
|
|
className="w-full"
|
|
>
|
|
<CollapsibleTrigger render={<SidebarMenuButton className={cn(
|
|
"flex w-full items-center rounded-lg px-2 transition-colors",
|
|
isOpen
|
|
? "bg-sidebar-muted text-foreground"
|
|
: "text-muted-foreground hover:bg-sidebar-muted hover:text-foreground",
|
|
isCollapsed && "justify-center"
|
|
)} />}>{route.icon}{!isCollapsed && (
|
|
<span className="ml-2 flex-1 text-sm font-medium">
|
|
{route.title}
|
|
</span>
|
|
)}{!isCollapsed && hasSubRoutes && (
|
|
<span className="ml-auto">
|
|
{isOpen ? (
|
|
<ChevronUp className="size-4" />
|
|
) : (
|
|
<ChevronDown className="size-4" />
|
|
)}
|
|
</span>
|
|
)}</CollapsibleTrigger>
|
|
|
|
{!isCollapsed && (
|
|
<CollapsibleContent>
|
|
<SidebarMenuSub className="my-1 ml-3.5 ">
|
|
{route.subs?.map((subRoute) => (
|
|
<SidebarMenuSubItem
|
|
key={`${route.id}-${subRoute.title}`}
|
|
className="h-auto"
|
|
>
|
|
<SidebarMenuSubButton render={<Link href={subRoute.link} prefetch={true} className="flex items-center rounded-md px-4 py-1.5 text-sm font-medium text-muted-foreground hover:bg-sidebar-muted hover:text-foreground" />}>{subRoute.title}</SidebarMenuSubButton>
|
|
</SidebarMenuSubItem>
|
|
))}
|
|
</SidebarMenuSub>
|
|
</CollapsibleContent>
|
|
)}
|
|
</Collapsible>
|
|
) : (
|
|
<SidebarMenuButton tooltip={route.title} render={<Link href={route.link} prefetch={true} className={cn(
|
|
"flex items-center rounded-lg px-2 transition-colors text-muted-foreground hover:bg-sidebar-muted hover:text-foreground",
|
|
isCollapsed && "justify-center"
|
|
)} />}>{route.icon}{!isCollapsed && (
|
|
<span className="ml-2 text-sm font-medium">
|
|
{route.title}
|
|
</span>
|
|
)}</SidebarMenuButton>
|
|
)}
|
|
</SidebarMenuItem>
|
|
);
|
|
})}
|
|
</SidebarMenu>
|
|
);
|
|
}
|