Files
temetro/frontend/components/sidebar-02/nav-main.tsx
T
Khalid Abdi 9588d16869 Refine sidebar, Notes, and Patients per review feedback
Round-2 fixes to the 6 features:

- Logo: enlarge the sidebar mark, drop the inline wordmark, and show
  "temetro" via a hover tooltip; bigger logo on the auth screens.
- Sidebar footer: wrap the quick-nav, clinic switcher, and user menu in a
  single bordered block so it reads as one footer instead of three cards.
- Sidebar nav: highlight the active page (usePathname + data-[active]), and
  add an Appointments & Schedule sub-page under Patients that auto-expands
  for the current section. New mock /appointments page; reachable via ⌘K.
- Notes: redesign to a two-pane list + editor with an Empty state on the
  right when nothing is selected; fix the editor so text starts top-left
  (div instead of a centering <button>); compact the toolbar; add
  .note-content typography in globals.css so headings/lists/etc. actually
  render (the app has no typography plugin).
- Patients: new PatientDetail laid out to fit the side Sheet (stacked
  full-width sections, no nested click-to-expand dialogs); keep Edit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 00:49:43 +03:00

109 lines
3.4 KiB
TypeScript

"use client";
import {
SidebarMenu,
SidebarMenuAction,
SidebarMenuButton,
SidebarMenuItem,
SidebarMenuSub,
SidebarMenuSubButton,
SidebarMenuSubItem,
useSidebar,
} from "@/components/ui/sidebar";
import { cn } from "@/lib/utils";
import { ChevronDown } from "lucide-react";
import Link from "next/link";
import { usePathname } from "next/navigation";
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;
}[];
};
// True when `link` matches the current path. "/" only matches exactly; any other
// link matches itself and its nested routes (so a parent stays lit on subpages).
function useIsActive() {
const pathname = usePathname();
return (link: string) =>
link === "/" ? pathname === "/" : pathname === link || pathname.startsWith(`${link}/`);
}
export default function DashboardNavigation({ routes }: { routes: Route[] }) {
const { state } = useSidebar();
const isCollapsed = state === "collapsed";
const isActive = useIsActive();
// Manual expand/collapse override per parent; falls back to "open when active".
const [overrides, setOverrides] = useState<Record<string, boolean>>({});
return (
<SidebarMenu>
{routes.map((route) => {
const hasSubs = !!route.subs?.length;
const sectionActive =
isActive(route.link) || !!route.subs?.some((s) => isActive(s.link));
const isOpen = !isCollapsed && (overrides[route.id] ?? sectionActive);
return (
<SidebarMenuItem key={route.id}>
<SidebarMenuButton
className="text-muted-foreground"
isActive={sectionActive}
render={<Link href={route.link} prefetch={true} />}
tooltip={route.title}
>
{route.icon}
{!isCollapsed && (
<span className="ml-2 flex-1 truncate font-medium text-sm">
{route.title}
</span>
)}
</SidebarMenuButton>
{hasSubs && !isCollapsed && (
<SidebarMenuAction
aria-label={isOpen ? "Collapse" : "Expand"}
onClick={() =>
setOverrides((prev) => ({ ...prev, [route.id]: !isOpen }))
}
>
<ChevronDown
className={cn(
"transition-transform duration-200",
isOpen && "rotate-180"
)}
/>
</SidebarMenuAction>
)}
{hasSubs && isOpen && (
<SidebarMenuSub>
{route.subs?.map((subRoute) => (
<SidebarMenuSubItem key={`${route.id}-${subRoute.link}`}>
<SidebarMenuSubButton
className="text-muted-foreground"
isActive={isActive(subRoute.link)}
render={<Link href={subRoute.link} prefetch={true} />}
>
{subRoute.icon}
<span className="truncate">{subRoute.title}</span>
</SidebarMenuSubButton>
</SidebarMenuSubItem>
))}
</SidebarMenuSub>
)}
</SidebarMenuItem>
);
})}
</SidebarMenu>
);
}