Sidebar
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@@ -8,25 +10,30 @@ import {
|
||||
SidebarProvider,
|
||||
useSidebar,
|
||||
} from "@/components/ui/sidebar";
|
||||
|
||||
import EntitySwitch from "./Entity";
|
||||
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "../ui/dropdown-menu";
|
||||
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { EntityId, Route, SetState, Workspace } from "@/lib/types";
|
||||
import { Route, SetState, Workspace } from "@/lib/types";
|
||||
|
||||
type AppSidebarProps = {
|
||||
children: React.ReactNode;
|
||||
workspace: Workspace;
|
||||
setRoute: SetState<Route>;
|
||||
};
|
||||
|
||||
export default function AppSidebar({
|
||||
children,
|
||||
workspace,
|
||||
setRoute,
|
||||
}: Readonly<{
|
||||
children: React.ReactNode;
|
||||
workspace: Workspace;
|
||||
setRoute: SetState<Route>;
|
||||
}>) {
|
||||
}: AppSidebarProps) {
|
||||
return (
|
||||
<SidebarProvider>
|
||||
<Sidebar>
|
||||
@@ -37,52 +44,52 @@ export default function AppSidebar({
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex w-full items-center justify-between px-2 py-1 text-sm">
|
||||
{workspace.name}
|
||||
<ChevronDown className="ml-auto" />
|
||||
<ChevronDown className="ml-auto h-4 w-4 opacity-60" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent
|
||||
align="start"
|
||||
className="w-(--radix-popper-anchor-width)"
|
||||
className="w-[--radix-popper-anchor-width]"
|
||||
>
|
||||
<DropdownMenuItem>Acme Inc</DropdownMenuItem>
|
||||
<DropdownMenuItem>Acme Corp.</DropdownMenuItem>
|
||||
{/* Future: real workspace switching */}
|
||||
<DropdownMenuItem>Rename Workspace</DropdownMenuItem>
|
||||
<DropdownMenuItem>Export Workspace</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</SidebarHeader>
|
||||
|
||||
<SidebarContent>
|
||||
<SidebarMenu>
|
||||
{workspace.layout.map((entity, i) => (
|
||||
{workspace.layout.map((entity) => (
|
||||
<EntitySwitch
|
||||
key={entity.id}
|
||||
entity={entity}
|
||||
workspace={workspace}
|
||||
key={i}
|
||||
setRoute={setRoute}
|
||||
/>
|
||||
))}
|
||||
</SidebarMenu>
|
||||
</SidebarContent>
|
||||
|
||||
<SidebarFooter />
|
||||
</Sidebar>
|
||||
|
||||
<main className="w-svw">{children}</main>
|
||||
</SidebarProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export function SidebarTrigger({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<"div">) {
|
||||
export function SidebarTrigger(props: React.ComponentProps<"div">) {
|
||||
const { toggleSidebar } = useSidebar();
|
||||
|
||||
return (
|
||||
<div
|
||||
data-sidebar="trigger"
|
||||
data-slot="sidebar-trigger"
|
||||
onClick={(event) => {
|
||||
toggleSidebar();
|
||||
}}
|
||||
onClick={toggleSidebar}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { CollapsibleTrigger } from "@radix-ui/react-collapsible";
|
||||
import {
|
||||
SidebarGroup,
|
||||
@@ -9,11 +11,12 @@ import {
|
||||
} from "../ui/sidebar";
|
||||
import { Collapsible, CollapsibleContent } from "../ui/collapsible";
|
||||
import { ChevronDown } from "lucide-react";
|
||||
import { EntityId, Route, SetState, Workspace } from "@/lib/types";
|
||||
import { Route, Workspace } from "@/lib/types";
|
||||
|
||||
export type SidebarEntity =
|
||||
| {
|
||||
kind: "group";
|
||||
id: string; // ✅ add stable id
|
||||
name: string;
|
||||
children: SidebarEntity[];
|
||||
}
|
||||
@@ -22,35 +25,39 @@ export type SidebarEntity =
|
||||
id: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
entity: SidebarEntity;
|
||||
workspace: Workspace;
|
||||
setRoute: (route: Route) => void;
|
||||
};
|
||||
|
||||
export default function EntitySwitch({
|
||||
entity,
|
||||
workspace,
|
||||
setRoute,
|
||||
}: {
|
||||
entity: SidebarEntity;
|
||||
workspace: Workspace;
|
||||
setRoute: SetState<Route>;
|
||||
}) {
|
||||
}: Props) {
|
||||
// ---- GROUP ----
|
||||
if (entity.kind === "group") {
|
||||
return (
|
||||
<Collapsible defaultOpen className="group/collapsible">
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel asChild>
|
||||
<SidebarMenuButton asChild>
|
||||
<CollapsibleTrigger>
|
||||
<CollapsibleTrigger className="flex w-full items-center">
|
||||
{entity.name}
|
||||
<ChevronDown className="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
||||
<ChevronDown className="ml-auto h-4 w-4 transition-transform group-data-[state=open]/collapsible:rotate-180" />
|
||||
</CollapsibleTrigger>
|
||||
</SidebarMenuButton>
|
||||
</SidebarGroupLabel>
|
||||
|
||||
<CollapsibleContent>
|
||||
<SidebarGroupContent>
|
||||
<SidebarMenu>
|
||||
{entity.children?.map((entity, i) => (
|
||||
{entity.children.map((child) => (
|
||||
<EntitySwitch
|
||||
entity={entity}
|
||||
key={child.id} // ✅ stable key
|
||||
entity={child}
|
||||
workspace={workspace}
|
||||
key={i}
|
||||
setRoute={setRoute}
|
||||
/>
|
||||
))}
|
||||
@@ -60,17 +67,27 @@ export default function EntitySwitch({
|
||||
</SidebarGroup>
|
||||
</Collapsible>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
onClick={() => {
|
||||
setRoute({ workspace: workspace.id, entity: entity.id });
|
||||
}}
|
||||
>
|
||||
{workspace.entities[entity.id].name}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
// ---- ITEM ----
|
||||
const workspaceEntity = workspace.entities[entity.id];
|
||||
|
||||
if (!workspaceEntity) {
|
||||
return null; // defensive against stale layout
|
||||
}
|
||||
|
||||
return (
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton
|
||||
onClick={() =>
|
||||
setRoute({
|
||||
workspace: workspace.id,
|
||||
entity: entity.id,
|
||||
})
|
||||
}
|
||||
>
|
||||
{workspaceEntity.name}
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user