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>

Установка

npx shadcn-vue@latest add https://revueui.rootapi.dev/r/c-tree-6.json

Зависимости реестра

npm-зависимости

  • @headless-tree/core

Источник: порт из ReUI (Keenthemes, MIT)