Components

CommandPalette

Tavolozza comandi stile Cmd+K con ricerca, navigazione tastiera e accessibilità ARIA.

Panoramica

CommandPalette è il componente UI della command palette. Si apre automaticamente con Ctrl+K (Windows/Linux) o Cmd+K (macOS) e mostra l'elenco dei comandi registrati tramite useCommandPalette. Supporta ricerca testuale incrementale, navigazione con frecce, esecuzione con Enter, chiusura con Esc o click sull'overlay.

Il componente usa <Teleport to="body"> ed è quindi inseribile in qualsiasi punto del template senza vincoli di posizionamento.

Anteprima live

Click sul pulsante per aprire la palette, oppure premi Ctrl+K / Cmd+K. Naviga con frecce, conferma con Enter, chiudi con Esc.

Props

CommandPalette non espone props pubbliche: lo state è interamente gestito dal singleton useCommandPalette. Per personalizzare aspetto e comportamento agisci sul composable e sulla registrazione dei comandi.

Eventi

Il componente non emette eventi Vue. L'esecuzione di un comando avviene tramite l'handler definito sul comando stesso. La chiusura avviene automaticamente al termine dell'handler (anche in caso di errore).

Slot

CommandPalette non espone slot pubblici nella versione 1.0.0. La visualizzazione di un comando combina icon, label, description e category direttamente dal tipo Command.

Layout

La struttura del componente è la seguente:

  • Overlay: fixed inset-0 z-50 con sfondo bg-black/50. Click sull'overlay chiude la palette
  • Dialog: bg-white dark:bg-gray-900 rounded-lg shadow-2xl max-w-2xl, ARIA role="dialog" con aria-modal="true"
  • Input ricerca: role="combobox" con aria-controls, aria-autocomplete="list", aria-activedescendant sincronizzato con la voce selezionata
  • Listbox risultati: role="listbox" con max-h-96 overflow-y-auto. Ogni voce è role="option" con aria-selected
  • Annuncio screen reader: regione aria-live="polite" che riporta il numero di risultati per la query corrente
  • Empty state: messaggio "Nessun comando trovato" quando il filtro non produce match

Il dark mode è gestito tramite la classe dark: di Tailwind.

Interazioni tastiera

TastoAzione
Ctrl+K / Cmd+KApre/chiude la palette (gestito da useCommandPalette)
ArrowDownSelezione successiva
ArrowUpSelezione precedente
EnterEsegue il comando selezionato
EscapeChiude la palette
Tab / Shift+TabFocus trap interno alla modale

L'apertura sposta il focus sull'input di ricerca e azzera l'indice selezionato a 0. Alla chiusura il focus viene riportato sull'elemento che lo possedeva prima dell'apertura, garantendo continuità di navigazione tastiera.

Esempio base

<script setup lang="ts">
import { CommandPalette, useCommandPalette } from '@pzeta/vue-scorciatoie'

useCommandPalette([
  {
    id: 'apri-anagrafica',
    label: 'Apri anagrafica',
    handler: () => router.push('/anagrafica'),
  },
  {
    id: 'nuova-attivita',
    label: 'Nuova attività',
    handler: () => apriDialogAttivita(),
  },
])
</script>

<template>
  <CommandPalette />
  <RouterView />
</template>

Esempio con categorie e icone

<script setup lang="ts">
import { CommandPalette, useCommandPalette } from '@pzeta/vue-scorciatoie'

useCommandPalette([
  {
    id: 'nav-dashboard',
    label: 'Dashboard',
    description: 'Vai alla dashboard principale',
    category: 'Navigazione',
    icon: 'pi pi-home',
    keywords: ['home', 'inizio'],
    handler: () => router.push('/'),
  },
  {
    id: 'nav-anagrafica',
    label: 'Anagrafiche',
    description: 'Elenco clienti e fornitori',
    category: 'Navigazione',
    icon: 'pi pi-users',
    keywords: ['clienti', 'fornitori'],
    handler: () => router.push('/anagrafica'),
  },
  {
    id: 'azione-nuovo-ticket',
    label: 'Nuovo ticket',
    description: 'Apre il form per creare un ticket',
    category: 'Azioni',
    icon: 'pi pi-plus',
    keywords: ['support', 'crea'],
    handler: () => apriFormTicket(),
  },
  {
    id: 'azione-logout',
    label: 'Logout',
    category: 'Sistema',
    icon: 'pi pi-sign-out',
    handler: () => authStore.logout(),
  },
])
</script>

<template>
  <CommandPalette />
</template>

Le keyword non sono visualizzate ma partecipano al filtro testuale: digitare "support" trova "Nuovo ticket" anche se la parola non compare nel label.

Esempio con comandi dinamici

I comandi possono essere registrati e rimossi a runtime. Usa addCommand/removeCommand per registrazioni che dipendono dal contesto (es. moduli caricati on-demand, permessi utente):

import { useCommandPalette } from '@pzeta/vue-scorciatoie'

const { addCommand, removeCommand } = useCommandPalette()

watch(modulePermissions, (perms) => {
  if (perms.includes('admin.users')) {
    addCommand({
      id: 'admin-users',
      label: 'Gestione utenti',
      category: 'Admin',
      handler: () => router.push('/admin/users'),
    })
  } else {
    removeCommand('admin-users')
  }
})

Note

  • I comandi con enabled: false vengono filtrati e non compaiono nei risultati
  • L'esecuzione dell'handler avviene in try/catch: gli errori vengono loggati con console.error ma la palette si chiude comunque
  • Il componente è completamente accessibile (WCAG 2.1) con focus trap, annuncio risultati e gestione aria-activedescendant
  • Lo styling è basato su Tailwind con dark mode automatico via classi dark: