- Implemented a new ContextMenu component with various subcomponents for better UI interaction. - Updated DropdownMenu and Select components to include hover effects for improved user experience. - Enhanced global styles with new CSS variables for better theming and consistency across components.
159 lines
4.8 KiB
TypeScript
159 lines
4.8 KiB
TypeScript
import { useState } from 'react';
|
|
import { Link, useRouterState } from '@tanstack/react-router';
|
|
import {
|
|
House,
|
|
ChartGantt,
|
|
ClipboardCheck,
|
|
FolderKanban,
|
|
PanelLeft,
|
|
ChevronUp,
|
|
} from 'lucide-react';
|
|
import { trpc } from '@/lib/trpc';
|
|
import {
|
|
Sidebar,
|
|
SidebarContent,
|
|
SidebarFooter,
|
|
SidebarGroup,
|
|
SidebarGroupContent,
|
|
SidebarHeader,
|
|
SidebarInset,
|
|
SidebarMenu,
|
|
SidebarMenuButton,
|
|
SidebarMenuItem,
|
|
SidebarProvider,
|
|
useSidebar,
|
|
} from '@/components/ui/sidebar';
|
|
|
|
const NAV_ITEMS = [
|
|
{ to: '/', icon: House, label: 'Home' },
|
|
{ to: '/timeline', icon: ChartGantt, label: 'Timeline' },
|
|
{ to: '/tasks', icon: ClipboardCheck, label: 'Tasks' },
|
|
{ to: '/projects', icon: FolderKanban, label: 'Projects' },
|
|
] as const;
|
|
|
|
interface AppShellProps {
|
|
children: React.ReactNode;
|
|
}
|
|
|
|
export function AppShell({ children }: AppShellProps) {
|
|
const collapsedQuery = trpc.settings.getSidebarCollapsed.useQuery(undefined, {
|
|
staleTime: Infinity,
|
|
});
|
|
const setSidebarCollapsedMutation = trpc.settings.setSidebarCollapsed.useMutation();
|
|
|
|
const routerState = useRouterState();
|
|
const currentPath = routerState.location.pathname;
|
|
|
|
// Controlled open state (spec: "Controlled Sidebar" pattern)
|
|
const [open, setOpen] = useState(() =>
|
|
collapsedQuery.data === undefined ? true : !collapsedQuery.data
|
|
);
|
|
|
|
const handleOpenChange = (value: boolean) => {
|
|
setOpen(value);
|
|
setSidebarCollapsedMutation.mutate({ collapsed: !value });
|
|
};
|
|
|
|
return (
|
|
<SidebarProvider open={open} onOpenChange={handleOpenChange}>
|
|
<AppSidebar currentPath={currentPath} />
|
|
<SidebarInset>
|
|
{children}
|
|
|
|
{/* Right-edge vertical 'keep scrolling for AI' affordance (non-interactive) */}
|
|
<div className="absolute right-0 top-0 flex items-end justify-center pt-8 pointer-events-none select-none">
|
|
<div className="flex flex-col items-center gap-1.5 pr-2">
|
|
<ChevronUp size={10} className="text-muted-foreground/30" />
|
|
<span
|
|
className="text-[9px] text-muted-foreground/30 tracking-widest uppercase font-medium"
|
|
style={{ writingMode: 'vertical-rl', transform: 'rotate(180deg)' }}
|
|
>
|
|
keep scrolling up for AI
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</SidebarInset>
|
|
</SidebarProvider>
|
|
);
|
|
}
|
|
|
|
function AppSidebar({ currentPath }: { currentPath: string }) {
|
|
const { toggleSidebar } = useSidebar();
|
|
|
|
return (
|
|
<Sidebar collapsible="icon">
|
|
{/* Logo */}
|
|
<SidebarHeader>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton size="lg" asChild>
|
|
<div className="cursor-default">
|
|
<div className="size-7 rounded-lg bg-primary flex items-center justify-center shrink-0">
|
|
<svg
|
|
width="16"
|
|
height="16"
|
|
viewBox="0 0 24 24"
|
|
fill="none"
|
|
className="text-primary-foreground"
|
|
>
|
|
<path
|
|
d="M12 2L13.5 8.5L20 10L13.5 11.5L12 18L10.5 11.5L4 10L10.5 8.5L12 2Z"
|
|
fill="currentColor"
|
|
/>
|
|
</svg>
|
|
</div>
|
|
<span className="font-semibold text-sm text-foreground">
|
|
Adiuva
|
|
</span>
|
|
</div>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarHeader>
|
|
|
|
{/* Nav */}
|
|
<SidebarContent>
|
|
<SidebarGroup>
|
|
<SidebarGroupContent>
|
|
<SidebarMenu>
|
|
{NAV_ITEMS.map(({ to, icon: Icon, label }) => {
|
|
const isActive =
|
|
to === '/'
|
|
? currentPath === '/'
|
|
: currentPath.startsWith(to);
|
|
|
|
return (
|
|
<SidebarMenuItem key={to}>
|
|
<SidebarMenuButton
|
|
asChild
|
|
isActive={isActive}
|
|
tooltip={label}
|
|
>
|
|
<Link to={to}>
|
|
<Icon />
|
|
<span>{label}</span>
|
|
</Link>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
);
|
|
})}
|
|
</SidebarMenu>
|
|
</SidebarGroupContent>
|
|
</SidebarGroup>
|
|
</SidebarContent>
|
|
|
|
{/* Collapse toggle — spec: useSidebar() + custom trigger */}
|
|
<SidebarFooter>
|
|
<SidebarMenu>
|
|
<SidebarMenuItem>
|
|
<SidebarMenuButton onClick={toggleSidebar} tooltip="Toggle Sidebar">
|
|
<PanelLeft />
|
|
<span>Collapse</span>
|
|
</SidebarMenuButton>
|
|
</SidebarMenuItem>
|
|
</SidebarMenu>
|
|
</SidebarFooter>
|
|
</Sidebar>
|
|
);
|
|
}
|