CommandPalette
Ctrl+K bilan ochiladigan qidiruv va buyruqlar oynasi: fuzzy qidiruv, oxirgi ishlatilganlar, ichki sahifalar, server qidiruvi.
'use client';
import { Button, CommandPalette, Kbd, toast, type CommandPaletteGroup } from '@akhatmirzo/ui';
import {
ChartColumn,
FileSpreadsheet,
House,
Package,
Plus,
Search,
Settings,
UserPlus,
Users,
} from 'lucide-react';
const run = (label: string) => () => toast(`"${label}" bajarildi`);
const groups: CommandPaletteGroup[] = [
{
heading: 'Sahifalar',
items: [
{ label: 'Bosh sahifa', icon: <House />, onSelect: run('Bosh sahifa') },
{
label: 'Buyurtmalar',
icon: <Package />,
keywords: ['zakaz', 'savdo'],
onSelect: run('Buyurtmalar'),
},
{ label: 'Mijozlar', icon: <Users />, onSelect: run('Mijozlar') },
{
label: 'Hisobotlar',
icon: <ChartColumn />,
keywords: ['statistika'],
onSelect: run('Hisobotlar'),
},
{
label: 'Sozlamalar',
icon: <Settings />,
shortcut: ['mod', ','],
onSelect: run('Sozlamalar'),
},
],
},
{
heading: 'Amallar',
items: [
{
label: 'Yangi buyurtma',
icon: <Plus />,
shortcut: ['mod', 'N'],
onSelect: run('Yangi buyurtma'),
},
{ label: "Mijoz qo'shish", icon: <UserPlus />, onSelect: run("Mijoz qo'shish") },
{
label: 'Eksport (Excel)',
icon: <FileSpreadsheet />,
keywords: ['xlsx'],
onSelect: run('Eksport'),
},
],
},
];
export default function CommandPaletteBasic() {
return (
// Docs saytida Ctrl+K qidiruvga band, shuning uchun bu yerda mod+J
<CommandPalette groups={groups} hotkey="mod+j">
<Button variant="outline" leftIcon={<Search />} className="min-w-64 justify-start">
<span className="flex-1 text-left text-text-muted">Qidirish...</span>
<Kbd keys={['mod', 'J']} />
</Button>
</CommandPalette>
);
}
Import
import { CommandPalette } from '@akhatmirzo/ui';Qachon ishlatish
- Ko'p sahifali ilovada tez o'tish uchun — admin panel, CRM, ichki tizimlar. Foydalanuvchi
menyuni titkilamaydi:
Ctrl+K, ikki-uch harf,Enter. - Tez-tez bajariladigan amallar uchun — "Yangi buyurtma", "Eksport", "Mavzuni almashtirish".
shortcutbilan ilovadagi klaviatura yorliqlarini ham ko'rsatasiz. - Ilovada bitta bo'lsin — odatda layout'da, global
hotkeybilan. Bir sahifada bir xilhotkeyli ikkita palitra birga ochiladi. - Asosiy navigatsiya o'rnida emas — u tajribali foydalanuvchi uchun tezlashtirgich; menyu va havolalar baribir kerak.
Bu saytda Ctrl+K band
Docs saytining o'z qidiruvi Ctrl+K da, shuning uchun yuqoridagi demoda hotkey="mod+j". O'z
ilovangizda standart mod+k ni qoldiring — mod macOS'da ⌘, boshqa tizimlarda Ctrl.
Guruhlar va elementlar
groups — sarlavhali guruhlar ro'yxati. Har bir element: label, ixtiyoriy description,
icon, shortcut, keywords (sinonimlar) va onSelect. Tanlangandan keyin oyna yopiladi
(keepOpen: true — ochiq qoladi).
import { CommandPalette, type CommandPaletteGroup } from '@akhatmirzo/ui';
import { Package, Plus } from 'lucide-react';
import { useRouter } from 'next/navigation';
export function AppCommands() {
const router = useRouter();
const groups: CommandPaletteGroup[] = [
{
heading: 'Sahifalar',
items: [
{
label: 'Buyurtmalar',
icon: <Package />,
keywords: ['zakaz', 'savdo'],
onSelect: () => router.push('/orders'),
},
],
},
{
heading: 'Amallar',
items: [
{
label: 'Yangi buyurtma',
icon: <Plus />,
shortcut: ['mod', 'N'],
onSelect: () => router.push('/orders/new'),
},
],
},
];
return <CommandPalette groups={groups} />;
}Qidiruv fuzzy — harflar ketma-ketligi bo'yicha: ybu → "Yangi buyurtma". O'zbekcha
normallashtirish bor: qo'sh, qoʻsh va қўш bir xil topiladi. Natijalar moslik darajasi
bo'yicha saralanadi, mos harflar ajratib ko'rsatiladi.
Oxirgi ishlatilganlar
Bo'sh qidiruvda birinchi guruh — "Oxirgi ishlatilganlar" (standart: 3 ta, localStorage da
recentKey kaliti bilan saqlanadi). Element id si bo'yicha eslab qolinadi — id bo'lmasa,
label. Bir ilovada bir nechta palitra bo'lsa, har biriga alohida recentKey bering;
recent={false} — o'chiq.
Ichki sahifalar va faqat qidiruvdagi guruh
Elementga page bersangiz, tanlanganda yangi ro'yxat ochiladi — masalan "Mavzu" → Yorug' /
Qorong'i. Qidiruv maydoni yonida "orqaga" tugmasi chiqadi; bo'sh qidiruvda Backspace ham orqaga
qaytaradi. searchOnly: true guruh faqat qidiruvda ko'rinadi — uzun ro'yxatlar (mijozlar,
mahsulotlar) uchun qulay: qidiring, masalan, aziza.
'use client';
import { Button, CommandPalette, toast, type CommandPaletteGroup } from '@akhatmirzo/ui';
import { Globe, Moon, Sun, User } from 'lucide-react';
const customers = ['Aziza Karimova', 'Bobur Toshmatov', "G'ayrat Usmonov", 'Malika Yusupova'];
const groups: CommandPaletteGroup[] = [
{
heading: 'Sozlamalar',
items: [
{
label: 'Mavzuni almashtirish',
icon: <Moon />,
keywords: ['tungi rejim', 'dark'],
page: {
title: 'Mavzu',
placeholder: 'Mavzu tanlang...',
groups: [
{
heading: 'Mavzu',
items: [
{
id: 'theme-light',
label: "Yorug'",
icon: <Sun />,
onSelect: () => toast("Yorug' mavzu"),
},
{
id: 'theme-dark',
label: "Qorong'i",
icon: <Moon />,
onSelect: () => toast("Qorong'i mavzu"),
},
],
},
],
},
},
{
label: 'Tilni almashtirish',
icon: <Globe />,
page: {
title: 'Til',
groups: [
{
heading: 'Til',
items: [
{ id: 'lang-uz', label: "O'zbekcha", onSelect: () => toast("O'zbekcha") },
{ id: 'lang-en', label: 'English', disabled: true },
],
},
],
},
},
],
},
{
heading: 'Mijozlar',
searchOnly: true,
items: customers.map((name) => ({ label: name, icon: <User />, onSelect: () => toast(name) })),
},
];
export default function CommandPalettePages() {
return (
<CommandPalette groups={groups} hotkey={false} recent={false}>
<Button variant="soft">Sozlamalarni ochish</Button>
</CommandPalette>
);
}
Server qidiruvi
onSearch(query, { signal }) — serverdan qidirish. So'rov 300ms debounce bilan yuboriladi,
natijalar alohida "Natijalar" guruhida (labels.results) chiqadi, kutish paytida spinner
ko'rinadi. Yangi so'rov boshlanganda yoki oyna yopilganda oldingisi signal orqali bekor
qilinadi. Mahalliy guruhlardagi qidiruv bir vaqtda ishlayveradi; ichki sahifalarda server qidiruvi
chaqirilmaydi. Sinab ko'ring: ak, nodira.
'use client';
import { Button, CommandPalette, toast, type CommandPaletteItem } from '@akhatmirzo/ui';
import { Plus, User } from 'lucide-react';
const customers = [
['Akmal Ergashev', '+998 90 123 45 67'],
['Akbar Sobirov', '+998 93 555 12 34'],
['Kamola Akramova', '+998 97 700 00 11'],
['Nodira Ahmedova', '+998 91 444 22 33'],
] as const;
/** Server qidiruvini taqlid qiladi (700ms); yangi so'rov eskisini bekor qiladi */
function searchCustomers(query: string, { signal }: { signal: AbortSignal }) {
return new Promise<CommandPaletteItem[]>((resolve, reject) => {
const timer = setTimeout(() => {
const q = query.toLowerCase();
resolve(
customers
.filter(([name]) => name.toLowerCase().includes(q))
.map(([name, phone]) => ({
id: `customer-${name}`,
label: name,
description: phone,
icon: <User />,
onSelect: () => toast(name),
})),
);
}, 700);
signal.addEventListener('abort', () => {
clearTimeout(timer);
reject(new DOMException('Bekor qilindi', 'AbortError'));
});
});
}
export default function CommandPaletteServer() {
return (
<CommandPalette
hotkey={false}
recentKey="docs:command-palette:server"
placeholder="Mijoz yoki buyruq qidirish..."
onSearch={searchCustomers}
labels={{ results: 'Mijozlar (serverdan)' }}
groups={[
{
heading: 'Amallar',
items: [
{ label: "Mijoz qo'shish", icon: <Plus />, onSelect: () => toast("Mijoz qo'shish") },
],
},
]}
>
<Button variant="outline">Mijoz qidirish</Button>
</CommandPalette>
);
}
<CommandPalette
groups={groups}
onSearch={async (query, { signal }) => {
const res = await fetch(`/api/customers?q=${encodeURIComponent(query)}`, { signal });
const customers: { id: number; name: string; phone: string }[] = await res.json();
return customers.map((c) => ({
id: `customer-${c.id}`,
label: c.name,
description: c.phone,
onSelect: () => router.push(`/customers/${c.id}`),
}));
}}
/>Server natijalari "Oxirgi ishlatilganlar" ga yozilmaydi.
Ochish va boshqarish
Uch yo'l bor, birgalikda ham ishlaydi:
hotkey— global klaviatura yorlig'i (standart'mod+k'; input ichida ham ishlaydi).hotkey={false}— o'chiq.children— ochuvchi element (RadixTrigger asChild), odatda tugma.open/onOpenChange— boshqariladigan holat, masalan menyudagi "Qidiruv" bandidan ochish uchun.
Yopilganda fokus palitrani ochgan elementga qaytadi.
Props
Qolgan atributlar (className, aria-*, data-* ...) dialog paneliga beriladi.
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
groupsmajburiy | CommandPaletteGroup[] | — | Guruhlar va elementlar |
hotkey | string | false | 'mod+k' | Ochish/yopish yorlig'i; false — o'chiq |
open | boolean | — | Boshqariladigan holat |
defaultOpen | boolean | false | |
onOpenChange | (open: boolean) => void | — | |
placeholder | string | 'Qidirish yoki buyruq yozish...' | |
onSearch | (query: string, context: CommandPaletteSearchContext) => Promise<CommandPaletteItem[]> | CommandPaletteItem[] | — | Server qidiruvi (300ms debounce); context: { signal } |
recent | boolean | true | "Oxirgi ishlatilganlar" guruhi |
recentKey | string | 'ak:command-palette:recent' | localStorage kaliti |
recentLimit | number | 3 | |
labels | CommandPaletteLabels | — | Matnlar |
children | ReactElement | — | Ochuvchi element (asChild) |
container | HTMLElement | null | — | Portal konteyneri (standart — document.body) |
overlayClassName | string | — | |
onOpenAutoFocus | (event: Event) => void | — | |
onCloseAutoFocus | (event: Event) => void | — |
CommandPaletteItem
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
id | string | — | Barqaror kalit ("Oxirgi ishlatilganlar" uchun); standart — label |
labelmajburiy | string | — | |
description | string | — | Ikkinchi qator (xira) |
icon | ReactNode | — | |
shortcut | string[] | — | Ko'rsatiladigan yorliq, masalan ['mod', 'N'] |
keywords | string[] | — | Qo'shimcha qidiruv so'zlari |
disabled | boolean | — | |
onSelect | () => void | — | |
page | CommandPalettePage | — | { title, placeholder?, groups } — ichki sahifa |
keepOpen | boolean | — | true — tanlangandan keyin oyna yopilmaydi |
shortcut faqat ko'rsatadi
shortcut elementning yonida yorliqni chizadi, lekin uni tinglamaydi. Yorliq palitra yopiq paytda
ham ishlashi uchun uni useHotkeys bilan alohida ulang.
CommandPaletteGroup
| Prop | Tip | Standart | Tavsif |
|---|---|---|---|
headingmajburiy | string | — | |
itemsmajburiy | CommandPaletteItem[] | — | |
searchOnly | boolean | — | Faqat qidiruvda ko'rinadi (bo'sh qidiruvda yashirin) |
Matnlar (labels)
title (ekran o'quvchi uchun sarlavha), search, list, recent, results, loading, empty,
emptyDescription ((query) => ReactNode), back, navigate, select, close — pastki yordam
qatoridagi so'zlar.
<CommandPalette
groups={groups}
labels={{ empty: 'Buyruq topilmadi', emptyDescription: (q) => `«${q}» — bunday buyruq yo'q` }}
/>Klaviatura
| Tugma | Harakat |
|---|---|
Ctrl+K / ⌘K | Ochish / yopish (hotkey) |
↑ ↓ | Elementlar bo'ylab (oxiridan boshiga aylanadi) |
Enter | Tanlash |
Esc | Yopish |
Backspace (bo'sh qidiruv) | Ichki sahifadan orqaga |
Accessibility
- Modal dialog (Radix): fokus ichida qamaladi, ochilganda qidiruv maydoniga o'tadi, yopilganda
ochgan elementga qaytadi. Dialog sarlavhasi ko'rinmaydi, lekin ekran o'quvchi uni o'qiydi
(
labels.title, standart "Buyruqlar"). - Qidiruv va ro'yxat —
cmdkasosida: combobox + listbox, faol elementaria-activedescendantorqali e'lon qilinadi. Maydonaria-labeli —labels.search. - Ichki sahifadan orqaga tugmasi sahifa nomi bilan nomlangan: "Orqaga: Mavzu".
- Ikonkalar dekorativ; yordam qatoridagi
Kbdlar faqat ko'rinish uchun. - Animatsiya
prefers-reduced-motionda sodda paydo bo'lish bilan almashtiriladi.