This commit is contained in:
2026-02-27 22:07:33 +01:00
parent 4823cca418
commit f856edc916
2 changed files with 67 additions and 43 deletions
+26 -19
View File
@@ -1,3 +1,5 @@
"use client";
import { import {
Sidebar, Sidebar,
SidebarContent, SidebarContent,
@@ -8,25 +10,30 @@ import {
SidebarProvider, SidebarProvider,
useSidebar, useSidebar,
} from "@/components/ui/sidebar"; } from "@/components/ui/sidebar";
import EntitySwitch from "./Entity"; import EntitySwitch from "./Entity";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "../ui/dropdown-menu"; } from "../ui/dropdown-menu";
import { ChevronDown } from "lucide-react"; 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({ export default function AppSidebar({
children, children,
workspace, workspace,
setRoute, setRoute,
}: Readonly<{ }: AppSidebarProps) {
children: React.ReactNode;
workspace: Workspace;
setRoute: SetState<Route>;
}>) {
return ( return (
<SidebarProvider> <SidebarProvider>
<Sidebar> <Sidebar>
@@ -37,52 +44,52 @@ export default function AppSidebar({
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<button className="flex w-full items-center justify-between px-2 py-1 text-sm"> <button className="flex w-full items-center justify-between px-2 py-1 text-sm">
{workspace.name} {workspace.name}
<ChevronDown className="ml-auto" /> <ChevronDown className="ml-auto h-4 w-4 opacity-60" />
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent <DropdownMenuContent
align="start" align="start"
className="w-(--radix-popper-anchor-width)" className="w-[--radix-popper-anchor-width]"
> >
<DropdownMenuItem>Acme Inc</DropdownMenuItem> {/* Future: real workspace switching */}
<DropdownMenuItem>Acme Corp.</DropdownMenuItem> <DropdownMenuItem>Rename Workspace</DropdownMenuItem>
<DropdownMenuItem>Export Workspace</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>
</SidebarHeader> </SidebarHeader>
<SidebarContent> <SidebarContent>
<SidebarMenu> <SidebarMenu>
{workspace.layout.map((entity, i) => ( {workspace.layout.map((entity) => (
<EntitySwitch <EntitySwitch
key={entity.id}
entity={entity} entity={entity}
workspace={workspace} workspace={workspace}
key={i}
setRoute={setRoute} setRoute={setRoute}
/> />
))} ))}
</SidebarMenu> </SidebarMenu>
</SidebarContent> </SidebarContent>
<SidebarFooter /> <SidebarFooter />
</Sidebar> </Sidebar>
<main className="w-svw">{children}</main> <main className="w-svw">{children}</main>
</SidebarProvider> </SidebarProvider>
); );
} }
export function SidebarTrigger({ export function SidebarTrigger(props: React.ComponentProps<"div">) {
className,
...props
}: React.ComponentProps<"div">) {
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
return ( return (
<div <div
data-sidebar="trigger" data-sidebar="trigger"
data-slot="sidebar-trigger" data-slot="sidebar-trigger"
onClick={(event) => { onClick={toggleSidebar}
toggleSidebar();
}}
{...props} {...props}
/> />
); );
+41 -24
View File
@@ -1,3 +1,5 @@
"use client";
import { CollapsibleTrigger } from "@radix-ui/react-collapsible"; import { CollapsibleTrigger } from "@radix-ui/react-collapsible";
import { import {
SidebarGroup, SidebarGroup,
@@ -9,11 +11,12 @@ import {
} from "../ui/sidebar"; } from "../ui/sidebar";
import { Collapsible, CollapsibleContent } from "../ui/collapsible"; import { Collapsible, CollapsibleContent } from "../ui/collapsible";
import { ChevronDown } from "lucide-react"; import { ChevronDown } from "lucide-react";
import { EntityId, Route, SetState, Workspace } from "@/lib/types"; import { Route, Workspace } from "@/lib/types";
export type SidebarEntity = export type SidebarEntity =
| { | {
kind: "group"; kind: "group";
id: string; // ✅ add stable id
name: string; name: string;
children: SidebarEntity[]; children: SidebarEntity[];
} }
@@ -22,35 +25,39 @@ export type SidebarEntity =
id: string; id: string;
}; };
type Props = {
entity: SidebarEntity;
workspace: Workspace;
setRoute: (route: Route) => void;
};
export default function EntitySwitch({ export default function EntitySwitch({
entity, entity,
workspace, workspace,
setRoute, setRoute,
}: { }: Props) {
entity: SidebarEntity; // ---- GROUP ----
workspace: Workspace;
setRoute: SetState<Route>;
}) {
if (entity.kind === "group") { if (entity.kind === "group") {
return ( return (
<Collapsible defaultOpen className="group/collapsible"> <Collapsible defaultOpen className="group/collapsible">
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel asChild> <SidebarGroupLabel asChild>
<SidebarMenuButton asChild> <SidebarMenuButton asChild>
<CollapsibleTrigger> <CollapsibleTrigger className="flex w-full items-center">
{entity.name} {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> </CollapsibleTrigger>
</SidebarMenuButton> </SidebarMenuButton>
</SidebarGroupLabel> </SidebarGroupLabel>
<CollapsibleContent> <CollapsibleContent>
<SidebarGroupContent> <SidebarGroupContent>
<SidebarMenu> <SidebarMenu>
{entity.children?.map((entity, i) => ( {entity.children.map((child) => (
<EntitySwitch <EntitySwitch
entity={entity} key={child.id} // ✅ stable key
entity={child}
workspace={workspace} workspace={workspace}
key={i}
setRoute={setRoute} setRoute={setRoute}
/> />
))} ))}
@@ -60,17 +67,27 @@ export default function EntitySwitch({
</SidebarGroup> </SidebarGroup>
</Collapsible> </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>
);
}