components/fumastudio/page-tree.tsx "use client";
import { colors } from "@/components/fumastudio/colors";
import { Tree, TreeItem, TreeItemLabel } from "@/components/fumastudio/tree";
import {
Sidebar,
SidebarContent,
SidebarGroup,
SidebarHeader,
SidebarMenu,
SidebarMenuItem,
SidebarRail,
} from "@/components/ui/sidebar";
import { syncDataLoaderFeature } from "@headless-tree/core";
import { useTree } from "@headless-tree/react";
import { type ClientFiletree, ClientSidebarItem } from "@trythis/nextjs";
import { type PageEntry } from "@trythis/nextjs/utils";
import { Loader2 } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import React, { CSSProperties } from "react";
const PAGE_TREE_INDENT = 15;
const SIDEBAR_WIDTH = "17rem";
const Loader = () => (
<div className="flex items-center w-full bg-red-400">
<Loader2 className="w-6 h-6 animate-spin text-muted-foreground" />
</div>
);
export type PageTreeProps = React.ComponentProps<typeof Sidebar> & {
tree: ClientFiletree;
};
export function PageTree({ ...props }: PageTreeProps) {
const items = props?.tree;
const pathName = usePathname();
const rootItemId = "/";
const tree = useTree<ClientSidebarItem>({
rootItemId,
initialState: { expandedItems: Object.keys(items) },
getItemName: (item) => item.getItemData().name,
isItemFolder: (item) => item.getItemData().type === "folder",
dataLoader: {
getItem: (slug) => items![slug] || {},
getChildren: (itemId) => {
if (!itemId || !items) return [];
const entry = items![itemId];
return entry?.type === "folder" ? entry.children : [];
},
},
features: [syncDataLoaderFeature],
});
return (
<Sidebar
style={
{
"--sidebar-width": SIDEBAR_WIDTH,
} as CSSProperties
}
{...props}>
{!props.tree && <Loader />}
{props.tree && (
<>
<SidebarHeader className="bg-whitex">
<SidebarMenu>
<SidebarMenuItem>
<div className="flex items-center h-16 p-3 gap-x-3">
<div className="flex items-center justify-center rounded-xl bg-white/5 p-1.5 shadow-sm">
<Image
src="https://github.com/usebruno/bruno/blob/main/assets/images/logo.png?raw=true"
alt="Bruno Logo"
width={40}
height={40}
className="object-contain"
/>
</div>
<div className="flex flex-col leading-tight">
<span className="text-lg font-semibold tracking-tight">
Bruno
</span>
<span className="text-xs text-muted-foreground">
Reinventing
the API Client
</span>
</div>
</div>
</SidebarMenuItem>
</SidebarMenu>
</SidebarHeader>
<SidebarContent className="flex flex-col">
<SidebarGroup className="overflow-y-scroll scrollbar-hide">
<div className="flex h-full scroll-auto flex-col gap-2 first:*:grow font-inter">
<Tree
indent={PAGE_TREE_INDENT}
tree={tree}
className="before:-ms-1 relative before:absolute before:inset-0 before:bg-[repeating-linear-gradient(to_right,transparent_0,transparent_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)-1px),var(--border)_calc(var(--tree-indent)))]">
{tree
.getItems()
.map((item) => {
const name =
item.getItemName();
const data =
item.getItemData();
const slug =
data.slug ||
"/";
const isActive =
(slug ===
pathName ||
pathName.includes(
slug,
) ||
pathName.endsWith(
slug,
)) &&
!item.isFolder();
const showLabel =
item.isLoading() ||
!!name;
const method =
(
data as PageEntry
)
?.method;
let truncatedMethod:
| string
| null =
"";
if (
typeof method ===
"string" &&
method ===
"DELETE"
) {
truncatedMethod =
"DEL";
} else {
truncatedMethod =
method;
}
return (
<TreeItem
item={
item
}
key={item.getId()}>
<TreeItemLabel
chevronPosition="right"
className={`${isActive ? "bg-accent" : ""} ${
showLabel
? "before:-inset-y-0.5 before:-z-10 relative before:absolute before:inset-x-0 before:bg-background"
: "hidden"
}`}>
<div className="flex items-center w-full -order-1 text-muted-foreground">
{name &&
item.isFolder() && (
<div className="flex items-center gap-2">
{
name
}
</div>
)}
{name &&
!item.isFolder() && (
<Link
href={
slug
}
className="w-full">
<div className="flex items-center justify-between gap-2 w-full">
<span className="text-left">
{
name
}
</span>
<span
className={`flex items-center justify-center w-9 h-4 px-1 rounded-md text-xs leading-tight font-bold ${colors(method, { tryit: true })}`}>
{
truncatedMethod
}
</span>
</div>
</Link>
)}
</div>
</TreeItemLabel>
</TreeItem>
);
})}
</Tree>
</div>
</SidebarGroup>
</SidebarContent>
<SidebarRail />
</>
)}
</Sidebar>
);
}