99 lines
2 KiB
Vue
99 lines
2 KiB
Vue
|
|
<script setup lang="ts">
|
||
|
|
import { Icon } from '@iconify/vue';
|
||
|
|
type Node = {
|
||
|
|
[key: string]: any;
|
||
|
|
selected?: boolean;
|
||
|
|
children?: Node[];
|
||
|
|
};
|
||
|
|
|
||
|
|
type Props = {
|
||
|
|
level?: number;
|
||
|
|
keyTitle?: string;
|
||
|
|
keySubtitle?: string;
|
||
|
|
decoration?: {
|
||
|
|
level?: number;
|
||
|
|
bg?: string;
|
||
|
|
fg?: string;
|
||
|
|
icon?: string;
|
||
|
|
}[];
|
||
|
|
};
|
||
|
|
|
||
|
|
const props = defineProps<Props>();
|
||
|
|
|
||
|
|
const currentStyle = props.decoration?.find((v) => {
|
||
|
|
return v.level === (props.level || 0);
|
||
|
|
});
|
||
|
|
|
||
|
|
const nodes = defineModel<Node[]>('nodes', { required: true });
|
||
|
|
</script>
|
||
|
|
|
||
|
|
<template>
|
||
|
|
<div class="tree-container">
|
||
|
|
<div v-for="node in nodes" class="tree-item">
|
||
|
|
<slot
|
||
|
|
v-if="$slots['item']"
|
||
|
|
name="item"
|
||
|
|
:data="{ node, onclick: () => (node.selected = !node.selected) }"
|
||
|
|
/>
|
||
|
|
<template v-else>
|
||
|
|
<label class="row items-center">
|
||
|
|
<input
|
||
|
|
class="item__checkbox"
|
||
|
|
type="checkbox"
|
||
|
|
v-model="node.selected"
|
||
|
|
/>
|
||
|
|
<Icon
|
||
|
|
v-if="currentStyle?.icon"
|
||
|
|
:icon="currentStyle.icon"
|
||
|
|
class="item__icon"
|
||
|
|
/>
|
||
|
|
<div class="column">
|
||
|
|
<span class="item__title">
|
||
|
|
{{ node[keyTitle || 'title'] || 'No Title' }}
|
||
|
|
</span>
|
||
|
|
<span class="item__subtitle">
|
||
|
|
{{ node[keySubtitle || 'subtitle'] || 'No Subtitle' }}
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</label>
|
||
|
|
</template>
|
||
|
|
<div class="q-pl-xl">
|
||
|
|
<TreeView
|
||
|
|
v-if="node.children"
|
||
|
|
v-model:nodes="node.children"
|
||
|
|
:level="(level || 0) + 1"
|
||
|
|
:decoration
|
||
|
|
/>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</template>
|
||
|
|
|
||
|
|
<style lang="css">
|
||
|
|
.tree-container {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: 1rem;
|
||
|
|
|
||
|
|
& .tree-item {
|
||
|
|
& .item__checkbox {
|
||
|
|
margin-right: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
& .item__icon {
|
||
|
|
margin-right: 1rem;
|
||
|
|
}
|
||
|
|
|
||
|
|
& .item__title {
|
||
|
|
font-weight: 600;
|
||
|
|
color: var(--brand-1);
|
||
|
|
}
|
||
|
|
|
||
|
|
& .item__subtitle {
|
||
|
|
font-size: 80%;
|
||
|
|
color: hsla(var(--text-mute-2));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|