Components
Loading preview...
Here is Tree component
npx shadcn@latest add https://21st.dev/r/originui/tree"use client"
import React from "react"
import {
expandAllFeature,
hotkeysCoreFeature,
selectionFeature,
syncDataLoaderFeature,
} from "@headless-tree/core"
import { useTree } from "@headless-tree/react"
import {
FolderIcon,
FolderOpenIcon,
ListCollapseIcon,
ListTreeIcon,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Tree, TreeItem, TreeItemLabel } from "@/components/ui/tree"
interface Item {
name: string
children?: string[]
}
const items: Record<string, Item> = {
company: {
name: "Company",
children: ["engineering", "marketing", "operations"],
},
engineering: {
name: "Engineering",
children: ["frontend", "backend", "platform-team"],
},
frontend: { name: "Frontend", children: ["design-system", "web-platform"] },
"design-system": {
name: "Design System",
children: ["components", "tokens", "guidelines"],
},
components: { name: "Components" },
tokens: { name: "Tokens" },
guidelines: { name: "Guidelines" },
"web-platform": { name: "Web Platform" },
backend: { name: "Backend", children: ["apis", "infrastructure"] },
apis: { name: "APIs" },
infrastructure: { name: "Infrastructure" },
"platform-team": { name: "Platform Team" },
marketing: { name: "Marketing", children: ["content", "seo"] },
content: { name: "Content" },
seo: { name: "SEO" },
operations: { name: "Operations", children: ["hr", "finance"] },
hr: { name: "HR" },
finance: { name: "Finance" },
}
const indent = 20
export default function Component() {
const tree = useTree<Item>({
initialState: {
expandedItems: ["engineering", "frontend", "design-system"],
selectedItems: ["components"],
},
indent,
rootItemId: "company",
getItemName: (item) => item.getItemData().name,
isItemFolder: (item) => (item.getItemData()?.children?.length ?? 0) > 0,
dataLoader: {
getItem: (itemId) => items[itemId],
getChildren: (itemId) => items[itemId].children ?? [],
},
features: [
syncDataLoaderFeature,
selectionFeature,
hotkeysCoreFeature,
expandAllFeature,
],
})
return (
<div className="flex h-full flex-col gap-2 *:nth-2:grow">
<div className="flex items-center gap-2">
<Button size="sm" variant="outline" onClick={() => tree.expandAll()}>
<ListTreeIcon
className="-ms-1 opacity-60"
size={16}
aria-hidden="true"
/>
Expand all
</Button>
<Button size="sm" variant="outline" onClick={tree.collapseAll}>
<ListCollapseIcon
className="-ms-1 opacity-60"
size={16}
aria-hidden="true"
/>
Collapse all
</Button>
</div>
<Tree indent={indent} tree={tree}>
{tree.getItems().map((item) => {
return (
<TreeItem key={item.getId()} item={item}>
<TreeItemLabel>
<span className="flex items-center gap-2">
{item.isFolder() &&
(item.isExpanded() ? (
<FolderOpenIcon className="text-muted-foreground pointer-events-none size-4" />
) : (
<FolderIcon className="text-muted-foreground pointer-events-none size-4" />
))}
{item.getItemName()}
{item.isFolder() && (
<span className="text-muted-foreground -ms-1">
{`(${item.getChildren().length})`}
</span>
)}
</span>
</TreeItemLabel>
</TreeItem>
)
})}
</Tree>
<p
aria-live="polite"
role="region"
className="text-muted-foreground mt-2 text-xs"
>
Tree with expand/collapse all buttons ∙{" "}
<a
href="https://headless-tree.lukasbach.com/"
className="hover:text-foreground underline"
target="_blank"
rel="noopener noreferrer"
>
API
</a>
</p>
</div>
)
}