tree
Organization chart tree with avatars
Organization chart tree with avatars
Загрузка превью…
src/tree/c-tree-6.vue
<!--
Сетевые unsplash-изображения заменены статичным 1x1 data-URI (PIXEL),
тем же приёмом, что и в блоках `badge`/`avatar`/`card`.
-->
<script setup lang="ts">
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { Tree, TreeItem, TreeItemLabel, useTree } from "@/components/reui/tree"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
const PIXEL =
"data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs="
interface OrgItem {
name: string
role?: string
avatar?: string
children?: string[]
}
const items: Record<string, OrgItem> = {
company: { name: "Acme Inc.", children: ["ceo"] },
ceo: {
name: "Sarah Chen",
role: "CEO",
avatar: PIXEL,
children: ["cto", "coo", "cfo"],
},
cto: {
name: "Alex Johnson",
role: "CTO",
avatar: PIXEL,
children: ["eng-lead", "design-lead"],
},
coo: {
name: "Emma Wilson",
role: "COO",
avatar: PIXEL,
children: ["ops-mgr", "hr-mgr"],
},
cfo: {
name: "David Kim",
role: "CFO",
avatar: PIXEL,
children: ["finance-mgr"],
},
"eng-lead": {
name: "Michael Rodriguez",
role: "Engineering Lead",
avatar: PIXEL,
children: ["dev-1", "dev-2"],
},
"design-lead": {
name: "Lisa Park",
role: "Design Lead",
avatar: PIXEL,
},
"ops-mgr": {
name: "James Brown",
role: "Operations Manager",
avatar: PIXEL,
},
"hr-mgr": {
name: "Amy Taylor",
role: "HR Manager",
avatar: PIXEL,
},
"finance-mgr": {
name: "Robert Davis",
role: "Finance Manager",
avatar: PIXEL,
},
"dev-1": {
name: "Tom Harris",
role: "Senior Developer",
avatar: PIXEL,
},
"dev-2": {
name: "Nina Patel",
role: "Developer",
avatar: PIXEL,
},
}
const indent = 24
const tree = useTree<OrgItem>({
initialState: {
expandedItems: ["ceo", "cto"],
},
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, hotkeysCoreFeature],
})
function initials(name: string) {
return name
.split(" ")
.map((n) => n[0])
.join("")
}
</script>
<template>
<div class="mx-auto w-full grow place-self-start lg:w-xs">
<Tree :indent="indent" :tree="tree">
<TreeItem v-for="item in tree.getItems()" :key="item.getId()" :item="item">
<TreeItemLabel class="gap-2 py-1">
<Avatar class="size-6 shrink-0">
<AvatarImage :src="item.getItemData().avatar!" :alt="item.getItemData().name" />
<AvatarFallback class="text-[9px]">
{{ initials(item.getItemData().name) }}
</AvatarFallback>
</Avatar>
<span class="flex flex-col items-start">
<span class="text-sm leading-tight">{{ item.getItemData().name }}</span>
<span v-if="item.getItemData().role" class="text-muted-foreground text-[10px] leading-tight">{{ item.getItemData().role }}</span>
</span>
</TreeItemLabel>
</TreeItem>
</Tree>
</div>
</template>