checkbox

Nested checkbox group

Nested checkbox group

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

src/checkbox/c-checkbox-16.vue

<script setup lang="ts">
import { reactive } from "vue"
import CheckboxTreeItem16 from "./CheckboxTreeItem16.vue"

interface Item {
  id: string
  label: string
  children?: Item[]
}

const data: Item[] = [
  {
    id: "admin",
    label: "Administration",
    children: [
      {
        id: "user-management",
        label: "User Management",
        children: [
          { id: "view-users", label: "View Users" },
          { id: "create-users", label: "Create Users" },
          { id: "edit-users", label: "Edit Users" },
        ],
      },
      {
        id: "role-management",
        label: "Role Management",
        children: [
          { id: "view-roles", label: "View Roles" },
          { id: "assign-roles", label: "Assign Roles" },
        ],
      },
    ],
  },
]

const checked = reactive<Record<string, boolean>>({
  "view-users": true,
  "create-users": true,
  "view-roles": true,
})

function getLeafIds(item: Item): string[] {
  if (!item.children) return [item.id]
  return item.children.flatMap(getLeafIds)
}

function isChecked(item: Item): boolean {
  const leafIds = getLeafIds(item)
  return leafIds.every((id) => checked[id])
}

function isIndeterminate(item: Item): boolean {
  const leafIds = getLeafIds(item)
  const checkedCount = leafIds.filter((id) => checked[id]).length
  return checkedCount > 0 && checkedCount < leafIds.length
}

function toggle(item: Item) {
  const leafIds = getLeafIds(item)
  const shouldCheck = !isChecked(item)
  leafIds.forEach((id) => {
    checked[id] = shouldCheck
  })
}
</script>

<template>
  <div class="mx-auto flex flex-col gap-3">
    <CheckboxTreeItem16
      v-for="item in data"
      :key="item.id"
      :item="item"
      :is-checked="isChecked"
      :is-indeterminate="isIndeterminate"
      :toggle="toggle"
    />
  </div>
</template>

src/checkbox/CheckboxTreeItem16.vue

<!--
  Внутренний хелпер для c-checkbox-16.vue: рекурсивный узел дерева
  чекбоксов. Не экспортируется из index.ts — это деталь реализации одного
  блока, а не отдельный публичный компонент.
-->
<script setup lang="ts">
import { Checkbox } from "@/components/ui/checkbox"
import { Field, FieldLabel } from "@/components/ui/field"

interface Item {
  id: string
  label: string
  children?: Item[]
}

defineOptions({ name: "CheckboxTreeItem16" })

const props = withDefaults(
  defineProps<{
    item: Item
    level?: number
    isChecked: (item: Item) => boolean
    isIndeterminate: (item: Item) => boolean
    toggle: (item: Item) => void
  }>(),
  { level: 0 }
)
</script>

<template>
  <div class="flex flex-col gap-3">
    <Field orientation="horizontal">
      <Checkbox
        :id="props.item.id"
        :model-value="props.isIndeterminate(props.item) ? 'indeterminate' : props.isChecked(props.item)"
        @update:model-value="props.toggle(props.item)"
      />
      <FieldLabel :for="props.item.id" :class="props.level === 0 ? 'font-semibold' : 'text-sm'">
        {{ props.item.label }}
      </FieldLabel>
    </Field>

    <div v-if="props.item.children" class="ml-7 flex flex-col gap-3">
      <CheckboxTreeItem16
        v-for="child in props.item.children"
        :key="child.id"
        :item="child"
        :level="props.level + 1"
        :is-checked="props.isChecked"
        :is-indeterminate="props.isIndeterminate"
        :toggle="props.toggle"
      />
    </div>
  </div>
</template>

Установка

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

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

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