/// CommandPalette — Ctrl+K overlay with search and command list. /// /// Mirrors the Svelte app's CommandPalette.svelte: Spotlight-style overlay /// with search input and filtered command list. use dioxus::prelude::*; use crate::state::{palette_commands, PaletteCommand}; #[component] pub fn CommandPalette( visible: Signal, ) -> Element { let mut search = use_signal(|| String::new()); let commands = palette_commands(); // Filter commands by search text let search_text = search.read().to_lowercase(); let filtered: Vec<&PaletteCommand> = if search_text.is_empty() { commands.iter().collect() } else { commands .iter() .filter(|c| c.label.to_lowercase().contains(&search_text)) .collect() }; let close = move |_| { visible.set(false); search.set(String::new()); }; let on_key = move |e: KeyboardEvent| { if e.key() == Key::Escape { visible.set(false); search.set(String::new()); } }; if !*visible.read() { return rsx! {}; } rsx! { div { class: "palette-overlay", onclick: close, onkeydown: on_key, div { class: "palette-box", // Stop click propagation so clicking inside doesn't close onclick: move |e| e.stop_propagation(), input { class: "palette-input", r#type: "text", placeholder: "Type a command...", value: "{search}", oninput: move |e| search.set(e.value()), autofocus: true, } div { class: "palette-results", for cmd in filtered.iter() { div { class: "palette-item", span { class: "palette-item-icon", "{cmd.icon}" } span { class: "palette-item-label", "{cmd.label}" } if let Some(ref shortcut) = cmd.shortcut { span { class: "palette-item-shortcut", "{shortcut}" } } } } if filtered.is_empty() { div { style: "padding: 1rem; text-align: center; color: var(--ctp-overlay0); font-size: 0.8125rem;", "No matching commands" } } } } } } }