UNPKG

@darksnow-ui/commander

Version:

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

1,232 lines (1,099 loc) 30.9 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.