UNPKG

@darksnow-ui/commander

Version:

Command pattern implementation with React hooks for building command palettes and keyboard-driven UIs

1,219 lines (1,092 loc) 30.5 kB
# useCustomCommand - Exemplos Práticos > 🎯 **Comandos temporários que existem apenas enquanto o componente está montado** O `useCustomCommand` é o hook principal para criar comandos contextuais que aparecem automaticamente no Command Palette (Ctrl+Shift+P) e desaparecem quando o componente é desmontado. ## 📋 Índice 1. [Modal com Comandos](#modal-com-comandos) 2. [Editor de Texto](#editor-de-texto) 3. [Formulário com Ações](#formulário-com-ações) 4. [Lista com Ações Contextuais](#lista-com-ações-contextuais) 5. [Player de Mídia](#player-de-mídia) 6. [Dashboard com Widgets](#dashboard-com-widgets) 7. [Tabela de Dados](#tabela-de-dados) 8. [Integração com Rotas](#integração-com-rotas) --- ## Modal com Comandos ```tsx import { useState } from 'react' import { useCustomCommand } from '@darksnow-ui/commander' function UserProfileModal({ user, isOpen, onClose }) { const [isEditing, setIsEditing] = useState(false) const [formData, setFormData] = useState(user) // Comando para fechar o modal (ESC) useCustomCommand({ key: `modal:close:${user.id}`, label: 'Fechar Modal', icon: '❌', shortcut: 'escape', handle: async () => { onClose() } }) // Comando para editar/salvar const saveCommand = useCustomCommand({ key: `user:save:${user.id}`, label: isEditing ? 'Salvar Alterações' : 'Editar Perfil', icon: isEditing ? '💾' : '✏️', shortcut: isEditing ? 'ctrl+s' : 'ctrl+e', category: 'user', when: () => isOpen, // Só disponível quando modal está aberto handle: async () => { if (isEditing) { await api.updateUser(user.id, formData) setIsEditing(false) toast.success('Perfil atualizado!') } else { setIsEditing(true) } } }) // Comando para deletar usuário useCustomCommand({ key: `user:delete:${user.id}`, label: 'Deletar Usuário', icon: '🗑️', category: 'user', tags: ['danger', 'delete'], priority: -1, // Baixa prioridade na busca when: () => isOpen && user.role !== 'admin', handle: async () => { if (confirm('Tem certeza?')) { await api.deleteUser(user.id) onClose() } } }) // Comando para enviar mensagem useCustomCommand({ key: `user:message:${user.id}`, label: `Enviar mensagem para ${user.name}`, icon: '💬', shortcut: 'ctrl+m', category: 'communication', handle: async () => { openMessageComposer(user) } }) if (!isOpen) return null return ( <Modal onClose={onClose}> {/* UI do modal */} </Modal> ) } ``` --- ## Editor de Texto ```tsx import { useCustomCommand, useAction } from '@darksnow-ui/commander' function RichTextEditor({ documentId, initialContent, onSave }) { const [content, setContent] = useState(initialContent) const [isDirty, setIsDirty] = useState(false) const [selectedText, setSelectedText] = useState('') const [undoStack, setUndoStack] = useState([]) const [redoStack, setRedoStack] = useState([]) // Comando de salvar const saveCommand = useCustomCommand({ key: `doc:save:${documentId}`, label: 'Salvar Documento', icon: '💾', shortcut: 'ctrl+s', category: 'file', when: () => isDirty, // Só aparece quando há mudanças handle: async () => { const result = await onSave(content) setIsDirty(false) return result } }) // Comandos de formatação useCustomCommand({ key: `format:bold:${documentId}`, label: 'Negrito', icon: '🅱️', shortcut: 'ctrl+b', category: 'format', when: () => selectedText.length > 0, handle: async () => { document.execCommand('bold') } }) useCustomCommand({ key: `format:italic:${documentId}`, label: 'Itálico', icon: '𝐼', shortcut: 'ctrl+i', category: 'format', when: () => selectedText.length > 0, handle: async () => { document.execCommand('italic') } }) // Comandos de edição useAction( `edit:undo:${documentId}`, 'Desfazer', () => { if (undoStack.length > 0) { const previous = undoStack[undoStack.length - 1] setRedoStack([...redoStack, content]) setContent(previous) setUndoStack(undoStack.slice(0, -1)) } }, { shortcut: 'ctrl+z', icon: '↩️', when: () => undoStack.length > 0 } ) useAction( `edit:redo:${documentId}`, 'Refazer', () => { if (redoStack.length > 0) { const next = redoStack[redoStack.length - 1] setUndoStack([...undoStack, content]) setContent(next) setRedoStack(redoStack.slice(0, -1)) } }, { shortcut: 'ctrl+y', icon: '↪️', when: () => redoStack.length > 0 } ) // Comandos avançados useCustomCommand({ key: `edit:find:${documentId}`, label: 'Buscar e Substituir', icon: '🔍', shortcut: 'ctrl+f', category: 'edit', handle: async () => { const searchTerm = prompt('Buscar por:') if (searchTerm) { const replaceTerm = prompt('Substituir por:') if (replaceTerm !== null) { const newContent = content.replaceAll(searchTerm, replaceTerm) setContent(newContent) setIsDirty(true) return { replaced: content.split(searchTerm).length - 1 } } } } }) // Comando de exportação useCustomCommand({ key: `export:pdf:${documentId}`, label: 'Exportar como PDF', icon: '📄', shortcut: 'ctrl+shift+e', category: 'file', tags: ['export', 'pdf', 'download'], handle: async () => { const blob = await generatePDF(content) downloadBlob(blob, `document-${documentId}.pdf`) } }) return ( <div className="editor"> <div className="toolbar"> <button onClick={() => saveCommand.invoke()}> Salvar {isDirty && '*'} </button> </div> <textarea value={content} onChange={(e) => { setContent(e.target.value) setIsDirty(true) setUndoStack([...undoStack, content].slice(-50)) // Max 50 undos }} onSelect={(e) => { const target = e.target as HTMLTextAreaElement setSelectedText( target.value.substring(target.selectionStart, target.selectionEnd) ) }} /> </div> ) } ``` --- ## Formulário com Ações ```tsx import { useCustomCommand, useToggleCommand } from '@darksnow-ui/commander' function ContactForm() { const [formData, setFormData] = useState({ name: '', email: '', message: '' }) const [isSubmitting, setIsSubmitting] = useState(false) const [autoSave, setAutoSave] = useState(true) // Toggle de auto-save useToggleCommand( 'form:autosave', 'Auto-save', autoSave, setAutoSave, { icon: autoSave ? '✅' : '❌', category: 'settings' } ) // Comando para limpar formulário useCustomCommand({ key: 'form:clear', label: 'Limpar Formulário', icon: '🧹', shortcut: 'ctrl+shift+delete', category: 'form', when: () => Object.values(formData).some(v => v !== ''), handle: async () => { if (confirm('Limpar todos os campos?')) { setFormData({ name: '', email: '', message: '' }) } } }) // Comando para preencher com dados de teste useCustomCommand({ key: 'form:fill-test', label: 'Preencher com Dados de Teste', icon: '🧪', category: 'debug', tags: ['test', 'development'], when: () => process.env.NODE_ENV === 'development', handle: async () => { setFormData({ name: 'João Silva', email: 'joao@example.com', message: 'Mensagem de teste para desenvolvimento' }) } }) // Comando para enviar const submitCommand = useCustomCommand({ key: 'form:submit', label: 'Enviar Formulário', icon: '📤', shortcut: 'ctrl+enter', category: 'form', when: () => formData.name && formData.email && formData.message && !isSubmitting, handle: async () => { setIsSubmitting(true) try { await api.submitContact(formData) toast.success('Mensagem enviada!') setFormData({ name: '', email: '', message: '' }) } catch (error) { toast.error('Erro ao enviar') } finally { setIsSubmitting(false) } } }) // Auto-save com debounce useEffect(() => { if (!autoSave) return const timer = setTimeout(() => { if (Object.values(formData).some(v => v !== '')) { localStorage.setItem('contact-form-draft', JSON.stringify(formData)) } }, 1000) return () => clearTimeout(timer) }, [formData, autoSave]) return ( <form onSubmit={(e) => { e.preventDefault() submitCommand.invoke() }}> {/* Campos do formulário */} </form> ) } ``` --- ## Lista com Ações Contextuais ```tsx import { useContextualCommands, useCustomCommand } from '@darksnow-ui/commander' function TaskList({ projectId }) { const [tasks, setTasks] = useState([]) const [selectedTasks, setSelectedTasks] = useState([]) const [filter, setFilter] = useState('all') // Comandos para cada task individual useContextualCommands( tasks, (task) => ({ key: `task:toggle:${task.id}`, label: task.completed ? `Reabrir: ${task.title}` : `Completar: ${task.title}`, icon: task.completed ? '↩️' : '✅', category: 'task', tags: ['task', task.priority], priority: task.priority === 'high' ? 10 : 5, handle: async () => { await api.updateTask(task.id, { completed: !task.completed }) setTasks(tasks.map(t => t.id === task.id ? { ...t, completed: !t.completed } : t )) } }) ) // Comandos para seleção múltipla useCustomCommand({ key: 'tasks:select-all', label: 'Selecionar Todas', icon: '☑️', shortcut: 'ctrl+a', category: 'selection', when: () => tasks.length > 0, handle: async () => { setSelectedTasks(tasks.map(t => t.id)) } }) useCustomCommand({ key: 'tasks:complete-selected', label: `Completar ${selectedTasks.length} tarefas`, icon: '✅', category: 'bulk', when: () => selectedTasks.length > 0, handle: async () => { await api.bulkUpdate(selectedTasks, { completed: true }) setTasks(tasks.map(t => selectedTasks.includes(t.id) ? { ...t, completed: true } : t )) setSelectedTasks([]) } }) // Comandos de filtro useCustomCommand({ key: 'filter:active', label: 'Mostrar Apenas Ativas', icon: '🔵', shortcut: 'alt+1', category: 'view', when: () => filter !== 'active', handle: async () => setFilter('active') }) useCustomCommand({ key: 'filter:completed', label: 'Mostrar Apenas Completas', icon: '🟢', shortcut: 'alt+2', category: 'view', when: () => filter !== 'completed', handle: async () => setFilter('completed') }) // Comando para adicionar nova task useCustomCommand({ key: 'task:add', label: 'Nova Tarefa', icon: '➕', shortcut: 'ctrl+n', category: 'task', handle: async () => { const title = prompt('Título da tarefa:') if (title) { const newTask = await api.createTask({ projectId, title }) setTasks([...tasks, newTask]) } } }) const filteredTasks = tasks.filter(task => { if (filter === 'active') return !task.completed if (filter === 'completed') return task.completed return true }) return ( <div> <div className="filters"> <button onClick={() => setFilter('all')}>Todas</button> <button onClick={() => setFilter('active')}>Ativas</button> <button onClick={() => setFilter('completed')}>Completas</button> </div> {filteredTasks.map(task => ( <TaskItem key={task.id} task={task} isSelected={selectedTasks.includes(task.id)} onToggleSelect={() => { setSelectedTasks( selectedTasks.includes(task.id) ? selectedTasks.filter(id => id !== task.id) : [...selectedTasks, task.id] ) }} /> ))} </div> ) } ``` --- ## Player de Mídia ```tsx import { useCustomCommand, useAction } from '@darksnow-ui/commander' function VideoPlayer({ video }) { const videoRef = useRef(null) const [isPlaying, setIsPlaying] = useState(false) const [volume, setVolume] = useState(1) const [playbackRate, setPlaybackRate] = useState(1) const [currentTime, setCurrentTime] = useState(0) // Play/Pause useCustomCommand({ key: `video:playpause:${video.id}`, label: isPlaying ? 'Pausar' : 'Reproduzir', icon: isPlaying ? '⏸️' : '▶️', shortcut: 'space', category: 'playback', handle: async () => { if (isPlaying) { videoRef.current?.pause() } else { videoRef.current?.play() } setIsPlaying(!isPlaying) } }) // Controles de velocidade const speeds = [0.5, 0.75, 1, 1.25, 1.5, 2] speeds.forEach(speed => { useAction( `video:speed:${speed}:${video.id}`, `Velocidade ${speed}x`, () => { if (videoRef.current) { videoRef.current.playbackRate = speed setPlaybackRate(speed) } }, { category: 'playback', icon: '⚡', when: () => playbackRate !== speed } ) }) // Volume useCustomCommand({ key: `video:mute:${video.id}`, label: volume > 0 ? 'Silenciar' : 'Ativar Som', icon: volume > 0 ? '🔇' : '🔊', shortcut: 'm', category: 'audio', handle: async () => { if (videoRef.current) { const newVolume = volume > 0 ? 0 : 1 videoRef.current.volume = newVolume setVolume(newVolume) } } }) // Navegação useAction( `video:skip-forward:${video.id}`, 'Avançar 10s', () => { if (videoRef.current) { videoRef.current.currentTime += 10 } }, { shortcut: 'right', icon: '⏩', category: 'navigation' } ) useAction( `video:skip-backward:${video.id}`, 'Voltar 10s', () => { if (videoRef.current) { videoRef.current.currentTime -= 10 } }, { shortcut: 'left', icon: '⏪', category: 'navigation' } ) // Fullscreen useCustomCommand({ key: `video:fullscreen:${video.id}`, label: 'Tela Cheia', icon: '🖥️', shortcut: 'f', category: 'view', handle: async () => { if (videoRef.current) { if (document.fullscreenElement) { document.exitFullscreen() } else { videoRef.current.requestFullscreen() } } } }) // Screenshot useCustomCommand({ key: `video:screenshot:${video.id}`, label: 'Capturar Tela', icon: '📸', shortcut: 's', category: 'tools', handle: async () => { const canvas = document.createElement('canvas') canvas.width = videoRef.current.videoWidth canvas.height = videoRef.current.videoHeight const ctx = canvas.getContext('2d') ctx.drawImage(videoRef.current, 0, 0) canvas.toBlob((blob) => { const url = URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `screenshot-${video.id}-${Date.now()}.png` a.click() }) } }) return ( <div className="video-player"> <video ref={videoRef} src={video.url} onTimeUpdate={(e) => setCurrentTime(e.currentTarget.currentTime)} /> {/* Controles do player */} </div> ) } ``` --- ## Dashboard com Widgets ```tsx import { useCustomCommand, useModalCommand } from '@darksnow-ui/commander' function Dashboard() { const [widgets, setWidgets] = useState([]) const [layout, setLayout] = useState('grid') const [refreshInterval, setRefreshInterval] = useState(30000) // Comando para adicionar widget const addWidgetCommand = useModalCommand( 'dashboard:add-widget', 'Adicionar Widget', async () => { const widgetType = await showWidgetPicker() if (widgetType) { const newWidget = await createWidget(widgetType) setWidgets([...widgets, newWidget]) return newWidget } }, { icon: '➕', shortcut: 'ctrl+shift+w', category: 'dashboard' } ) // Comandos para cada widget widgets.forEach(widget => { // Refresh individual useCustomCommand({ key: `widget:refresh:${widget.id}`, label: `Atualizar: ${widget.title}`, icon: '🔄', category: 'widget', tags: ['refresh', widget.type], handle: async () => { await widget.refresh() toast.success(`${widget.title} atualizado`) } }) // Configurar widget useModalCommand( `widget:config:${widget.id}`, `Configurar: ${widget.title}`, async () => { const config = await showWidgetConfig(widget) if (config) { await updateWidget(widget.id, config) setWidgets(widgets.map(w => w.id === widget.id ? { ...w, ...config } : w )) } }, { icon: '⚙️', category: 'widget' } ) // Remover widget useCustomCommand({ key: `widget:remove:${widget.id}`, label: `Remover: ${widget.title}`, icon: '🗑️', category: 'widget', priority: -1, handle: async () => { if (confirm(`Remover ${widget.title}?`)) { setWidgets(widgets.filter(w => w.id !== widget.id)) } } }) }) // Comandos de layout useCustomCommand({ key: 'dashboard:layout:grid', label: 'Layout em Grade', icon: '⚏', category: 'view', when: () => layout !== 'grid', handle: async () => setLayout('grid') }) useCustomCommand({ key: 'dashboard:layout:list', label: 'Layout em Lista', icon: '☰', category: 'view', when: () => layout !== 'list', handle: async () => setLayout('list') }) // Refresh global useCustomCommand({ key: 'dashboard:refresh-all', label: 'Atualizar Todos os Widgets', icon: '🔄', shortcut: 'ctrl+r', category: 'dashboard', handle: async () => { await Promise.all(widgets.map(w => w.refresh())) toast.success('Dashboard atualizado') } }) // Auto-refresh toggle useToggleCommand( 'dashboard:auto-refresh', 'Auto-refresh', refreshInterval > 0, (enabled) => setRefreshInterval(enabled ? 30000 : 0), { category: 'settings', icon: '⏱️' } ) // Exportar dashboard useCustomCommand({ key: 'dashboard:export', label: 'Exportar Dashboard', icon: '📊', shortcut: 'ctrl+e', category: 'dashboard', tags: ['export', 'download'], handle: async () => { const format = await showExportOptions() if (format) { const data = await exportDashboard(widgets, format) downloadFile(data, `dashboard-${Date.now()}.${format}`) } } }) return ( <div className={`dashboard layout-${layout}`}> <button onClick={() => addWidgetCommand.invoke()}> Add Widget </button> {widgets.map(widget => ( <Widget key={widget.id} {...widget} /> ))} </div> ) } ``` --- ## Tabela de Dados ```tsx import { useCustomCommand, useContextualCommands } from '@darksnow-ui/commander' function DataTable({ data, columns, onUpdate }) { const [selectedRows, setSelectedRows] = useState([]) const [sortConfig, setSortConfig] = useState(null) const [filters, setFilters] = useState({}) const [editingCell, setEditingCell] = useState(null) // Comandos para linhas selecionadas useCustomCommand({ key: 'table:export-selected', label: `Exportar ${selectedRows.length} linhas`, icon: '📥', shortcut: 'ctrl+shift+e', category: 'table', when: () => selectedRows.length > 0, handle: async () => { const selectedData = data.filter(row => selectedRows.includes(row.id)) const csv = convertToCSV(selectedData) downloadFile(csv, 'export.csv') } }) useCustomCommand({ key: 'table:delete-selected', label: `Deletar ${selectedRows.length} linhas`, icon: '🗑️', shortcut: 'delete', category: 'table', when: () => selectedRows.length > 0, priority: -1, handle: async () => { if (confirm(`Deletar ${selectedRows.length} linhas?`)) { await onUpdate('delete', selectedRows) setSelectedRows([]) } } }) // Comandos de ordenação para cada coluna columns.forEach(column => { if (column.sortable) { useCustomCommand({ key: `sort:${column.key}:asc`, label: `Ordenar por ${column.label} (A-Z)`, icon: '⬆️', category: 'sort', when: () => sortConfig?.key !== column.key || sortConfig?.direction !== 'asc', handle: async () => { setSortConfig({ key: column.key, direction: 'asc' }) } }) useCustomCommand({ key: `sort:${column.key}:desc`, label: `Ordenar por ${column.label} (Z-A)`, icon: '⬇️', category: 'sort', when: () => sortConfig?.key !== column.key || sortConfig?.direction !== 'desc', handle: async () => { setSortConfig({ key: column.key, direction: 'desc' }) } }) } }) // Comandos contextuais para cada linha useContextualCommands( data, (row) => ({ key: `row:edit:${row.id}`, label: `Editar: ${row.name || row.title || row.id}`, icon: '✏️', category: 'row', handle: async () => { const updatedRow = await showRowEditor(row) if (updatedRow) { await onUpdate('update', row.id, updatedRow) } } }) ) // Comandos de filtro useCustomCommand({ key: 'table:clear-filters', label: 'Limpar Filtros', icon: '🧹', category: 'filter', when: () => Object.keys(filters).length > 0, handle: async () => { setFilters({}) } }) // Comando de busca global useCustomCommand({ key: 'table:search', label: 'Buscar na Tabela', icon: '🔍', shortcut: 'ctrl+f', category: 'table', handle: async () => { const searchTerm = prompt('Buscar por:') if (searchTerm) { setFilters({ ...filters, global: searchTerm }) } } }) // Copiar dados selecionados useCustomCommand({ key: 'table:copy-selected', label: 'Copiar Seleção', icon: '📋', shortcut: 'ctrl+c', category: 'table', when: () => selectedRows.length > 0, handle: async () => { const selectedData = data.filter(row => selectedRows.includes(row.id)) const text = selectedData.map(row => columns.map(col => row[col.key]).join('\t') ).join('\n') await navigator.clipboard.writeText(text) toast.success('Copiado para área de transferência') } }) const sortedAndFilteredData = useMemo(() => { let result = [...data] // Apply filters Object.entries(filters).forEach(([key, value]) => { if (key === 'global') { result = result.filter(row => Object.values(row).some(v => String(v).toLowerCase().includes(value.toLowerCase()) ) ) } else { result = result.filter(row => String(row[key]).toLowerCase().includes(value.toLowerCase()) ) } }) // Apply sorting if (sortConfig) { result.sort((a, b) => { const aVal = a[sortConfig.key] const bVal = b[sortConfig.key] if (sortConfig.direction === 'asc') { return aVal > bVal ? 1 : -1 } else { return aVal < bVal ? 1 : -1 } }) } return result }, [data, filters, sortConfig]) return ( <div className="data-table"> <table> <thead> <tr> {columns.map(column => ( <th key={column.key}> {column.label} {column.sortable && ( <button onClick={() => { const newDir = sortConfig?.key === column.key && sortConfig?.direction === 'asc' ? 'desc' : 'asc' setSortConfig({ key: column.key, direction: newDir }) }}> {sortConfig?.key === column.key ? (sortConfig.direction === 'asc' ? '⬆️' : '⬇️') : '↕️' } </button> )} </th> ))} </tr> </thead> <tbody> {sortedAndFilteredData.map(row => ( <tr key={row.id} className={selectedRows.includes(row.id) ? 'selected' : ''} onClick={() => { setSelectedRows( selectedRows.includes(row.id) ? selectedRows.filter(id => id !== row.id) : [...selectedRows, row.id] ) }} > {columns.map(column => ( <td key={column.key}> {row[column.key]} </td> ))} </tr> ))} </tbody> </table> </div> ) } ``` --- ## Integração com Rotas ```tsx import { useNavigationCommand, useCustomCommand } from '@darksnow-ui/commander' import { useRouter, useParams } from 'next/router' function ProductPage() { const router = useRouter() const { productId } = useParams() const [product, setProduct] = useState(null) const [relatedProducts, setRelatedProducts] = useState([]) // Navegação entre produtos useNavigationCommand( 'nav:products', 'Voltar para Produtos', '/products', router.push, { icon: '🏪', shortcut: 'alt+p' } ) // Navegação para produtos relacionados relatedProducts.forEach(related => { useNavigationCommand( `nav:product:${related.id}`, `Ver: ${related.name}`, `/products/${related.id}`, router.push, { icon: '🔗', category: 'related', tags: ['product', related.category] } ) }) // Ações do produto useCustomCommand({ key: `product:share:${productId}`, label: 'Compartilhar Produto', icon: '🔗', shortcut: 'ctrl+shift+s', category: 'product', handle: async () => { const url = `${window.location.origin}/products/${productId}` if (navigator.share) { await navigator.share({ title: product.name, text: product.description, url }) } else { await navigator.clipboard.writeText(url) toast.success('Link copiado!') } } }) // Adicionar ao carrinho const addToCartCommand = useCustomCommand({ key: `product:cart:${productId}`, label: 'Adicionar ao Carrinho', icon: '🛒', shortcut: 'ctrl+enter', category: 'product', priority: 10, when: () => product?.inStock, handle: async () => { await addToCart(productId) toast.success('Adicionado ao carrinho!') } }) // Admin commands const isAdmin = useIsAdmin() useCustomCommand({ key: `product:edit:${productId}`, label: 'Editar Produto', icon: '✏️', category: 'admin', when: () => isAdmin, handle: async () => { router.push(`/admin/products/${productId}/edit`) } }) useCustomCommand({ key: `product:analytics:${productId}`, label: 'Ver Analytics', icon: '📊', category: 'admin', when: () => isAdmin, handle: async () => { const analytics = await fetchProductAnalytics(productId) showAnalyticsModal(analytics) } }) return ( <div> {/* UI do produto */} <button onClick={() => addToCartCommand.invoke()}> Adicionar ao Carrinho </button> </div> ) } ``` --- ## 💡 Dicas e Melhores Práticas ### 1. **Use IDs únicos nas keys** ```tsx // ✅ Bom - ID único por instância useCustomCommand({ key: `modal:save:${modalId}`, // ... }) // ❌ Evite - Pode causar conflitos useCustomCommand({ key: 'modal:save', // ... }) ``` ### 2. **Aproveite a função `when`** ```tsx // Comando só aparece quando relevante useCustomCommand({ key: 'form:submit', label: 'Enviar', when: () => isFormValid && !isSubmitting, handle: async () => { /* ... */ } }) ``` ### 3. **Use categorias para organização** ```tsx // Agrupe comandos relacionados category: 'file' // Operações de arquivo category: 'edit' // Edição category: 'view' // Visualização category: 'tools' // Ferramentas category: 'debug' // Desenvolvimento ``` ### 4. **Retorne valores úteis** ```tsx // Retorne informações sobre a operação handle: async () => { const result = await saveFile() return { success: true, fileSize: result.size, savedAt: new Date() } } ``` ### 5. **Use os hooks especializados** ```tsx // Para ações simples useAction('toggle:sidebar', 'Toggle Sidebar', () => { setSidebarOpen(!sidebarOpen) }) // Para toggles useToggleCommand('dark:mode', 'Dark Mode', isDark, setIsDark) // Para modais useModalCommand('user:edit', 'Edit User', async () => { return await showUserEditModal() }) ``` ### 6. **Integre com o estado do componente** ```tsx // Comandos que refletem o estado atual const [filter, setFilter] = useState('all') ['all', 'active', 'completed'].forEach(filterType => { useCustomCommand({ key: `filter:${filterType}`, label: `Show ${filterType}`, when: () => filter !== filterType, handle: async () => setFilter(filterType) }) }) ``` --- ## 🎯 Conclusão O `useCustomCommand` transforma qualquer componente em uma interface poderosa e acessível. Os comandos aparecem automaticamente no Command Palette, permitindo que usuários avançados naveguem e controlem sua aplicação com eficiência. **Lembre-se**: Os comandos são temporários e existem apenas enquanto o componente está montado, garantindo que o Command Palette sempre reflita o contexto atual da aplicação.