fix(select-tree): adjust tree node indentation and alignment logic

- Add level prop to track node hierarchy
- Fix indentation calculation for leaves and nodes
- Simplify alignment logic based on node level
This commit is contained in:
Khafid Prayoga
2025-09-16 15:14:37 +07:00
parent b106b1d23e
commit c5ec8ccd32
4 changed files with 18 additions and 12 deletions
+1 -1
View File
@@ -21,7 +21,7 @@ function handleSelect(value: string) {
<CommandItem
:value="item.value"
class="flex items-center justify-between p-2 w-full text-sm font-normal hover:text-primary cursor-pointer rounded-md"
:class="{ 'pl-12': shouldAlign }"
:class="{ 'pl-8': shouldAlign }"
@select="() => handleSelect(item.value)"
>
<span class="text-sm font-normal">{{ item.label }}</span>
@@ -7,14 +7,13 @@ const props = defineProps<{
item: TreeItem
selectedValue?: string
onFetchChildren: (parentId: string) => Promise<void>
level?: number
}>()
const emit = defineEmits(['select'])
// Computed untuk memastikan reactivity pada children
const hasChildren = computed(() => props.item.children && props.item.children.length > 0)
// State terpisah untuk chevron animation dan loading
const isOpen = ref(false)
const isLoading = ref(false)
const isChevronRotated = ref(false)
@@ -27,14 +26,11 @@ function handleLabelClick() {
handleSelect(props.item.value)
}
// Watch untuk handle fetch data ketika collapsible dibuka
watch(isOpen, async (newValue) => {
console.log(`[TreeNode] ${props.item.label} - isOpen changed to:`, newValue)
// Update chevron rotation berdasarkan open state
isChevronRotated.value = newValue
// Jika membuka dan belum ada children, fetch data
if (newValue && props.item.hasChildren && !props.item.children && !isLoading.value) {
console.log(`[TreeNode] Fetching children for: ${props.item.label}`)
isLoading.value = true
@@ -65,9 +61,7 @@ watch(isOpen, async (newValue) => {
variant="ghost"
class="h-4 w-4 p-0 flex items-center justify-center"
>
<!-- Loading State -->
<Loader2 v-if="isLoading" class="w-4 h-4 animate-spin text-muted-foreground" />
<!-- Chevron dengan animasi terpisah -->
<ChevronRight
v-else
class="w-4 h-4 transition-transform duration-200 ease-in-out text-muted-foreground"
@@ -93,7 +87,7 @@ watch(isOpen, async (newValue) => {
</div>
<!-- Children Container -->
<CollapsibleContent class="pl-8">
<CollapsibleContent class="pl-6">
<div v-if="!hasChildren" class="text-sm text-muted-foreground p-2">
{{ isLoading ? 'Memuat...' : 'Tidak ada data' }}
</div>
@@ -102,6 +96,7 @@ watch(isOpen, async (newValue) => {
:data="item.children!"
:selected-value="selectedValue"
:on-fetch-children="onFetchChildren"
:level="(level || 0) + 1"
@select="handleSelect"
/>
</CollapsibleContent>
@@ -58,6 +58,7 @@ const selectedLabel = computed(() => {
:data="data"
:selected-value="modelValue"
:on-fetch-children="onFetchChildren"
:level="0"
@select="handleSelect"
/>
</CommandGroup>
@@ -7,6 +7,7 @@ const props = defineProps<{
data: TreeItem[]
selectedValue?: string
onFetchChildren: (parentId: string) => Promise<void>
level?: number
}>()
const emit = defineEmits(['select'])
@@ -19,25 +20,34 @@ function handleSelect(value: string) {
const hasAnyChildrenInLevel = computed(() => {
return props.data.some(item => item.hasChildren)
})
// Computed untuk menentukan apakah perlu alignment berdasarkan level
const shouldAlignLeaves = computed(() => {
// Di root level (level 0), selalu align leaf dengan tree nodes jika ada mixed content
// Di level lain, hanya align jika ada mixed content
const isRootLevel = (props.level || 0) === 0
const hasMixedContent = hasAnyChildrenInLevel.value && props.data.some(item => !item.hasChildren)
return isRootLevel ? hasAnyChildrenInLevel.value : hasMixedContent
})
</script>
<template>
<div class="tree-view min-w-max">
<template v-for="item in data" :key="item.value">
<!-- Jika item memiliki children, gunakan TreeNode -->
<TreeNode
v-if="item.hasChildren"
:item="item"
:selected-value="selectedValue"
:on-fetch-children="onFetchChildren"
:level="level || 0"
@select="handleSelect"
/>
<!-- Jika item tidak memiliki children, gunakan Leaf -->
<Leaf
v-else
:item="item"
:selected-value="selectedValue"
:should-align="hasAnyChildrenInLevel"
:should-align="shouldAlignLeaves"
@select="handleSelect"
/>
</template>