Skip to content

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.

vue
<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:

vue
<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:

vue
<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>
vue
<!-- 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.

vue
<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.

vue
<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.

vue
<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.

Fruits
Apple
Banana
Cherry
Date
vue
<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:

PropTypeDescription
itemFinderItemDataOxirgi darajadan tanlangan element (barcha maxsus maydonlar bilan)
selectionsFinderSelection[]Har bir darajadan tanlovlarning to'liq zanjiri
ts
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 ​

PropTypeDefaultDescription
levelsFinderLevel[][]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
heightstring'100%'Konteyner balandligi
gapnumber6Ustunlar orasidagi masofa
collapsiblebooleantrueUstunlarni yig'ish imkoni
resizablebooleantrueUstunlar o'lchamini o'zgartirish imkoni
minWidthnumber150Ustunning minimal kengligi
maxWidthnumber500Ustunning maksimal kengligi
detailWidthnumber | string360Detal panelining kengligi

VxFinder Events ​

EventPayloadDescription
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 ​

SlotPropsDescription
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 ​

NameTypeDescription
refresh(key?)methodUstunni yoki barchasini qayta yuklash
selectionsFinderSelection[]Joriy tanlash zanjiri

VxFinderColumn Props ​

PropTypeDefaultDescription
titlestring''Sarlavha matni
collapsiblebooleantrueYig'ishga ruxsat berish
collapsedbooleanfalseYig'ilgan holat (v-model)
resizablebooleantrueO'lchamini o'zgartirishga ruxsat berish
widthnumber260Boshlang'ich kenglik (v-model)
minWidthnumber150Minimal kenglik
maxWidthnumber500Maksimal kenglik
showAddbooleanfalseQo'shish tugmasini ko'rsatish
loadingbooleanfalseYuklash indikatorini ko'rsatish
filterablebooleanfalseUstun yuqorisida qidiruv maydonini ko'rsatish
filterPlaceholderstring'Search...'Filtr kiritish maydoni uchun joy ushlovchi matni

VxFinderItem Props ​

PropTypeDefaultDescription
activebooleanfalseTanlangan holat
disabledbooleanfalseO'chirilgan holat
status'default' | 'success' | 'warning' | 'danger'—Status nuqtasi
editablebooleanfalseTahrirlash harakatini ko'rsatish
deletablebooleanfalseO'chirish harakatini ko'rsatish