tree

Permissions tree with checkboxes

Permissions tree with checkboxes

Загрузка превью…

src/tree/c-tree-7.vue

<!--
  `TreeItem asChild` в оригинале рендерит `Comp = Fragment`, а React
  отбрасывает произвольные пропсы (data-slot/style/aria-*/обработчики) на
  `Fragment` — они не долетают до реального DOM-узла (см. c-tree-7.tsx:
  `<TreeItem item={item} asChild><div>...</div></TreeItem>`, итоговый
  `<div>` в апстриме получает НИ ОДНОГО атрибута от TreeItem). Это уже
  задокументированный пробел (`docs/PORTING.md` §5: "tree — ветка asChild
  не пробрасывает ref в Primitive; ни один текущий кейс её не задействует")
  — примитив (`packages/ui/src/reui/tree/TreeItem.vue`) не трогается, но
  здесь, где asChild впервые проверяется дифом, `TreeItem` не используется
  вовсе: обёртка — голый `<div>` без атрибутов (побайтово как в апстриме),
  `TreeItemLabel` получает `item` явным пропом вместо контекста от
  `TreeItem`. `reka-ui`'s `Primitive as-child` (в отличие от React Fragment)
  реально пробрасывает атрибуты — это дало бы иначе стилизованный,
  проиндексированный вид, не совпадающий с (сломанным) апстримом.
-->
<script setup lang="ts">
import { ref } from "vue"
import { hotkeysCoreFeature, syncDataLoaderFeature } from "@headless-tree/core"
import { Tree, TreeItemLabel, useTree } from "@/components/reui/tree"
import { Checkbox } from "@/components/ui/checkbox"

interface PermissionItem {
  name: string
  children?: string[]
}

const items: Record<string, PermissionItem> = {
  permissions: {
    name: "All Permissions",
    children: ["users", "content", "billing", "api"],
  },
  users: {
    name: "User Management",
    children: ["users-view", "users-create", "users-edit", "users-delete"],
  },
  "users-view": { name: "View users" },
  "users-create": { name: "Create users" },
  "users-edit": { name: "Edit users" },
  "users-delete": { name: "Delete users" },
  content: {
    name: "Content Management",
    children: ["content-view", "content-publish", "content-delete"],
  },
  "content-view": { name: "View content" },
  "content-publish": { name: "Publish content" },
  "content-delete": { name: "Delete content" },
  billing: { name: "Billing", children: ["billing-view", "billing-manage"] },
  "billing-view": { name: "View invoices" },
  "billing-manage": { name: "Manage subscriptions" },
  api: { name: "API Access", children: ["api-read", "api-write"] },
  "api-read": { name: "Read access" },
  "api-write": { name: "Write access" },
}

const checked = ref<Set<string>>(
  new Set([
    "users-view",
    "content-view",
    "content-publish",
    "billing-view",
    "api-read",
  ])
)

function togglePermission(id: string) {
  const next = new Set(checked.value)
  if (next.has(id)) {
    next.delete(id)
  } else {
    next.add(id)
  }
  checked.value = next
}

const indent = 24

const tree = useTree<PermissionItem>({
  initialState: {
    expandedItems: ["users", "content"],
  },
  indent,
  rootItemId: "permissions",
  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],
})
</script>

<template>
  <div class="mx-auto w-full grow place-self-start lg:w-xs">
    <Tree :indent="indent" :tree="tree" toggle-icon-type="plus-minus">
      <div v-for="item in tree.getItems()" :key="item.getId()">
        <TreeItemLabel :item="item" class="not-in-data-[folder=true]:ps-5">
          <span class="flex items-center gap-2">
            <Checkbox
              v-if="!item.isFolder()"
              :model-value="checked.has(item.getId())"
              class="size-3.5 shrink-0"
              @update:model-value="togglePermission(item.getId())"
              @click="(e: MouseEvent) => e.stopPropagation()"
            />
            {{ item.getItemName() }}
          </span>
        </TreeItemLabel>
      </div>
    </Tree>
  </div>
</template>

Установка

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

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

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

  • @headless-tree/core

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