Skip to main content

rustyclaw_tui/components/
command_menu.rs

1// ── Command menu ────────────────────────────────────────────────────────────
2//
3// Floating completion popup for `/` slash commands. Rendered just above the
4// input bar with the list of matching commands and a highlighted selection.
5
6use crate::theme;
7use iocraft::prelude::*;
8
9#[derive(Default, Props)]
10pub struct CommandMenuProps {
11    /// The filtered list of matching command names (without the `/` prefix).
12    pub completions: Vec<String>,
13    /// Index of the currently highlighted entry (None ⇒ nothing selected).
14    pub selected: Option<usize>,
15}
16
17#[component]
18pub fn CommandMenu(props: &CommandMenuProps) -> impl Into<AnyElement<'static>> {
19    if props.completions.is_empty() {
20        return element! { View() }.into_any();
21    }
22
23    let max_rows = props.completions.len().min(12) as u32;
24
25    element! {
26        View(
27            width: 100pct,
28            flex_direction: FlexDirection::Column,
29            max_height: max_rows + 2, // rows + top/bottom border
30            border_style: BorderStyle::Round,
31            border_color: theme::ACCENT,
32            background_color: theme::BG_SURFACE,
33        ) {
34            #(props.completions.iter().enumerate().take(max_rows as usize).map(|(i, cmd)| {
35                let is_selected = props.selected == Some(i);
36                let bg = if is_selected { theme::ACCENT_DIM } else { theme::BG_SURFACE };
37                let fg = if is_selected { theme::ACCENT_BRIGHT } else { theme::TEXT };
38                element! {
39                    View(
40                        key: i as u64,
41                        width: 100pct,
42                        background_color: bg,
43                        padding_left: 1,
44                    ) {
45                        Text(
46                            content: format!("/{}", cmd),
47                            color: fg,
48                        )
49                    }
50                }
51            }))
52        }
53    }
54    .into_any()
55}