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>