Finder
macOS Finder dan ilhomlangan ustunga asoslangan ierarxik navigator. Ikki rejim: levels (levels prop i bilan qat'iy chuqurlik) yoki dynamic (load prop i bilan rekursiv/noma'lum chuqurlik). Element tanlanganda keyingi ustun avtomatik yuklanib ochiladi. Barg tugunlari to'liq tanlov zanjiri bilan detail panel ni ochadi.
Detail Panel
Oxirgi darajada elementni tanlaganingizda, o'ngda #detail slot ochiladi. Slot tanlangan item va to'liq selections zanjirini oladi — shunday qilib, tafsilotlarni olish yoki boshqa komponentni ochish uchun istalgan ota-ona kalitga kirishingiz mumkin.
Chuqurlashib kiring: Departments → Teams → Members, keyin a'zoni bosib uning profil kartasini ko'ring.
<template>
<VxFinder
:levels="levels"
height="400px"
:detail-width="320"
>
<template #detail="{ item, selections }">
<!-- item = the last-level selected item (with all your custom fields) -->
<!-- selections = full chain: [{ levelKey, key, item }, ...] -->
<MemberProfile
:id="item.key"
:department-id="selections[0]?.key"
:team-id="selections[1]?.key"
/>
</template>
</VxFinder>
</template>
<script setup>
const levels = [
{
key: 'departments',
title: 'Departments',
showAdd: true,
load: () => fetchDepartments(),
},
{
key: 'teams',
title: 'Teams',
showAdd: true,
load: (deptId) => fetchTeams(deptId),
},
{
key: 'members',
title: 'Members',
// This is the last level — selecting opens #detail
load: (teamId) => fetchMembers(teamId),
},
]
</script>Custom Item Templates
Har bir daraja uchun elementlarning qanday render qilinishini scoped slotlar yoki daraja konfiguratsiyasidagi component orqali sozlang. Ustuvorlik: #item-{levelKey} slot → #item slot → component maydoni → sukutdagi yorliq.
Scoped slotlar — aniq daraja uchun #item-{levelKey}, yoki barcha darajalar uchun #item dan foydalaning:
<template>
<VxFinder :levels="levels">
<!-- Custom render for 'members' level only -->
<template #item-members="{ item, active }">
<div class="member-row">
<Avatar :name="item.label" />
<span>{{ item.label }}</span>
<VxBadge>{{ item.role }}</VxBadge>
</div>
</template>
<!-- Fallback for all other levels -->
<template #item="{ item, levelKey }">
<span>{{ item.label }}</span>
</template>
</VxFinder>
</template>Daraja konfiguratsiyasidagi komponent — Vue komponentini to'g'ridan-to'g'ri JSON ichida uzating:
<script setup>
import MemberItem from '../../components/MemberItem.vue'
const levels = [
{
key: 'departments',
title: 'Departments',
load: () => fetchDepartments(),
},
{
key: 'members',
title: 'Members',
// Custom component receives { item, active, levelKey } props
component: MemberItem,
load: (deptId) => fetchMembers(deptId),
},
]
</script><!-- MemberItem.vue -->
<script setup>
defineProps({ item: Object, active: Boolean })
</script>
<template>
<div class="member-row">
<img :src="item.avatar" class="member-avatar" />
<div>
<div>{{ item.label }}</div>
<small>{{ item.role }}</small>
</div>
</div>
</template>Async Loading with Detail
Region → City → District ierarxiyasi. Tumanni tanlash to'liq yo'l va barcha ota-ona ID larini ko'rsatuvchi detail kartani ochadi.
<template>
<VxFinder :levels="geoLevels" :detail-width="280">
<template #detail="{ item, selections }">
<!-- Use selections to get parent IDs -->
<DistrictDetail
:id="item.key"
:region-id="selections[0]?.key"
:city-id="selections[1]?.key"
/>
</template>
</VxFinder>
</template>File Browser (Sync + Detail)
Oxirgi ustunda faylni tanlash fayl ma'lumotlari va to'liq papka yo'li bilan oldindan ko'rishni ochadi.
Recursive / Dynamic Depth (load prop)
Noma'lum chuqurlikdagi ierarxiyalar uchun — masalan, HR tizimida filiallar cheksiz joylashishi mumkin bo'lgan holatda — levels o'rniga load prop dan foydalaning. load funksiyasi bosilgan element (yoki ildiz uchun null) va to'liq tanlash zanjirini qabul qiladi. Ustun qo'shish uchun FinderColumnResult qaytaring yoki barg element uchun null (detal paneli ochiladi).
Chuqurlashib boring: Filial → Filial → Bo'lim → Xodim → Foydalanuvchi kartasi. Filiallar istalgan chuqurlikda sub-filiallar yoki bo'limlarni o'z ichiga olishi mumkin.
<template>
<VxFinder :load="hrLoad" height="420px" :detail-width="300">
<template #detail="{ item, selections }">
<EmployeeCard :employee="item" :path="selections" />
</template>
</VxFinder>
</template>
<script setup>
async function hrLoad(item, selections) {
if (!item) {
// Root: fetch top-level branches
const branches = await api.getBranches()
return { key: 'branches', title: 'Branches', items: branches, filterable: true }
}
if (item.type === 'branch') {
// Branch: may contain sub-branches + departments
const children = await api.getBranchChildren(item.key)
return { key: `sub-${item.key}`, title: item.label, items: children, filterable: true }
}
if (item.type === 'department') {
// Department: show employees
const employees = await api.getEmployees(item.key)
return { key: `staff-${item.key}`, title: `${item.label} Staff`, items: employees }
}
// Employee: leaf node → opens #detail panel
return null
}
</script>Add / Edit / Delete
Darajalarda showAdd, editable va deletable ni yoqib, o'rnatilgan harakat tugmalarini olish mumkin. @add, @edit, @delete hodisalarini tinglang, ma'lumotlarni o'zgartiring, so'ng shu ustunni qayta yuklash uchun ref.refresh(levelKey) ni chaqiring.
Qo'shish uchun sarlavhadagi + tugmasini bosing, Edit va Delete bilan ... menyusini ochish uchun elementga kursor olib boring.
<template>
<VxFinder
ref="finderRef"
:levels="levels"
@add="onAdd"
@edit="onEdit"
@delete="onDelete"
/>
</template>
<script setup>
import { ref, computed } from 'vue'
const finderRef = ref(null)
const categories = ref([
{ key: 'cat-1', label: 'Frontend' },
{ key: 'cat-2', label: 'Backend' },
])
const items = ref({
'cat-1': [
{ key: 'item-1', label: 'Vue.js', status: 'success' },
{ key: 'item-2', label: 'React', status: 'success' },
],
'cat-2': [
{ key: 'item-3', label: 'Node.js', status: 'success' },
],
})
let nextId = 100
const levels = computed(() => [
{
key: 'categories',
title: 'Categories',
showAdd: true, // "+" button in header
editable: true, // edit action on hover menu
deletable: true, // delete action on hover menu
filterable: true,
load: () => categories.value,
},
{
key: 'items',
title: 'Items',
showAdd: true,
editable: true,
deletable: true,
filterable: true,
load: (parentKey) => items.value[parentKey] || [],
},
])
function onAdd(levelKey) {
const name = prompt(`New ${levelKey}:`)
if (!name) return
const id = `new-${++nextId}`
if (levelKey === 'categories') {
categories.value.push({ key: id, label: name })
} else {
const parentKey = finderRef.value?.selections[0]?.key
if (!parentKey) return
if (!items.value[parentKey]) items.value[parentKey] = []
items.value[parentKey].push({ key: id, label: name, status: 'success' })
}
// Refresh the column to show new data
finderRef.value?.refresh(levelKey)
}
function onEdit(levelKey, item) {
const name = prompt(`Rename "${item.label}":`, item.label)
if (!name || name === item.label) return
if (levelKey === 'categories') {
const cat = categories.value.find(c => c.key === item.key)
if (cat) cat.label = name
} else {
for (const arr of Object.values(items.value)) {
const found = arr.find(i => i.key === item.key)
if (found) { found.label = name; break }
}
}
finderRef.value?.refresh(levelKey)
}
function onDelete(levelKey, item) {
if (!confirm(`Delete "${item.label}"?`)) return
if (levelKey === 'categories') {
categories.value = categories.value.filter(c => c.key !== item.key)
delete items.value[item.key]
} else {
for (const arr of Object.values(items.value)) {
const idx = arr.findIndex(i => i.key === item.key)
if (idx !== -1) { arr.splice(idx, 1); break }
}
}
finderRef.value?.refresh(levelKey)
}
</script>Slot Mode (Manual)
Agar levels prop berilmasa, VxFinderColumn va VxFinderItem ni qo'lda yig'ish uchun slot dan foydalaning.
<template>
<VxFinder height="250px">
<VxFinderColumn title="Fruits" :width="200">
<VxFinderItem
v-for="f in fruits"
:key="f.id"
:active="selected === f.id"
@click="selected = f.id"
>{{ f.name }}</VxFinderItem>
</VxFinderColumn>
<VxFinderColumn v-if="children.length" title="Colors">
<VxFinderItem v-for="c in children" :key="c.id" :status="c.status">
{{ c.name }}
</VxFinderItem>
</VxFinderColumn>
</VxFinder>
</template>Detail Slot API
#detail slot ikkita prop qabul qiladi:
| Prop | Type | Description |
|---|---|---|
item | FinderItemData | Oxirgi darajadan tanlangan element (barcha maxsus maydonlar bilan) |
selections | FinderSelection[] | Har bir darajadan tanlovlarning to'liq zanjiri |
interface FinderSelection {
levelKey: string // Level key (e.g. 'departments')
key: string | number // Selected item's key
item: FinderItemData // Full item data
}
// Access any parent key:
selections[0]?.key // First level's selected key
selections[1]?.key // Second level's selected key
// ...VxFinder Props
| Prop | Type | Default | Description |
|---|---|---|---|
| levels | FinderLevel[] | [] | Qat'iy chuqurlikli daraja ta'riflari. Bo'sh bo'lsa, slot rejimidan foydalanadi |
| load | (item, selections) => FinderColumnResult | null | — | Rekursiv/noma'lum chuqurlik uchun dinamik yuklash funksiyasi. Ustun konfiguratsiyasini yoki barg uchun null qaytaradi |
| height | string | '100%' | Konteyner balandligi |
| gap | number | 6 | Ustunlar orasidagi masofa |
| collapsible | boolean | true | Ustunlarni yig'ish imkoni |
| resizable | boolean | true | Ustunlar o'lchamini o'zgartirish imkoni |
| minWidth | number | 150 | Ustunning minimal kengligi |
| maxWidth | number | 500 | Ustunning maksimal kengligi |
| detailWidth | number | string | 360 | Detal panelining kengligi |
VxFinder Events
| Event | Payload | Description |
|---|---|---|
| select | (levelKey, item) | Element tanlandi |
| add | (levelKey) | Qo'shish tugmasi bosildi |
| edit | (levelKey, item) | Element ustidagi tahrirlash harakati |
| delete | (levelKey, item) | Element ustidagi o'chirish harakati |
VxFinder Slots
| Slot | Props | Description |
|---|---|---|
| default | — | Qo'lda rejim tarkibi |
| item | { item, active, levelKey } | Barcha daraja elementlari uchun maxsus shablon |
| item- | { item, active, levelKey } | Muayyan daraja elementlari uchun maxsus shablon |
| detail | { item, selections } | Oxirgi daraja elementi tanlanganda ko'rsatiladigan panel |
VxFinder Exposed
| Name | Type | Description |
|---|---|---|
refresh(key?) | method | Ustunni yoki barchasini qayta yuklash |
selections | FinderSelection[] | Joriy tanlash zanjiri |
VxFinderColumn Props
| Prop | Type | Default | Description |
|---|---|---|---|
| title | string | '' | Sarlavha matni |
| collapsible | boolean | true | Yig'ishga ruxsat berish |
| collapsed | boolean | false | Yig'ilgan holat (v-model) |
| resizable | boolean | true | O'lchamini o'zgartirishga ruxsat berish |
| width | number | 260 | Boshlang'ich kenglik (v-model) |
| minWidth | number | 150 | Minimal kenglik |
| maxWidth | number | 500 | Maksimal kenglik |
| showAdd | boolean | false | Qo'shish tugmasini ko'rsatish |
| loading | boolean | false | Yuklash indikatorini ko'rsatish |
| filterable | boolean | false | Ustun yuqorisida qidiruv maydonini ko'rsatish |
| filterPlaceholder | string | 'Search...' | Filtr kiritish maydoni uchun joy ushlovchi matni |
VxFinderItem Props
| Prop | Type | Default | Description |
|---|---|---|---|
| active | boolean | false | Tanlangan holat |
| disabled | boolean | false | O'chirilgan holat |
| status | 'default' | 'success' | 'warning' | 'danger' | — | Status nuqtasi |
| editable | boolean | false | Tahrirlash harakatini ko'rsatish |
| deletable | boolean | false | O'chirish harakatini ko'rsatish |