Sidebar
This commit is contained in:
@@ -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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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 {
|
}
|
||||||
|
|
||||||
|
// ---- ITEM ----
|
||||||
|
const workspaceEntity = workspace.entities[entity.id];
|
||||||
|
|
||||||
|
if (!workspaceEntity) {
|
||||||
|
return null; // defensive against stale layout
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SidebarMenuItem>
|
<SidebarMenuItem>
|
||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
onClick={() => {
|
onClick={() =>
|
||||||
setRoute({ workspace: workspace.id, entity: entity.id });
|
setRoute({
|
||||||
}}
|
workspace: workspace.id,
|
||||||
|
entity: entity.id,
|
||||||
|
})
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{workspace.entities[entity.id].name}
|
{workspaceEntity.name}
|
||||||
</SidebarMenuButton>
|
</SidebarMenuButton>
|
||||||
</SidebarMenuItem>
|
</SidebarMenuItem>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user