Skip to main content

aetna_core/widgets/
command.rs

1//! Command/menu anatomy — familiar rows for command palettes and menus.
2//!
3//! This does not add new interaction state. It packages the row
4//! conventions that shadcn examples repeat constantly: icon slot, label,
5//! trailing shortcut, menu-item density, and centered inline content.
6
7use std::panic::Location;
8
9use crate::cursor::Cursor;
10use crate::metrics::MetricsRole;
11use crate::style::StyleProfile;
12use crate::tokens;
13use crate::tree::*;
14use crate::widgets::text::{mono, text};
15use crate::{IntoIconSource, icon};
16
17#[track_caller]
18pub fn command_group<I, E>(children: I) -> El
19where
20    I: IntoIterator<Item = E>,
21    E: Into<El>,
22{
23    El::new(Kind::Custom("command_group"))
24        .at_loc(Location::caller())
25        .children(children)
26        .axis(Axis::Column)
27        .align(Align::Stretch)
28        .width(Size::Fill(1.0))
29        .height(Size::Hug)
30        .default_gap(0.0)
31}
32
33#[track_caller]
34pub fn command_item<I, E>(children: I) -> El
35where
36    I: IntoIterator<Item = E>,
37    E: Into<El>,
38{
39    El::new(Kind::Custom("command_item"))
40        .at_loc(Location::caller())
41        .style_profile(StyleProfile::Solid)
42        .metrics_role(MetricsRole::MenuItem)
43        .surface_role(SurfaceRole::Raised)
44        .focusable()
45        .cursor(Cursor::Pointer)
46        .children(children)
47        .axis(Axis::Row)
48        .align(Align::Center)
49        .justify(Justify::Start)
50        .fill(tokens::CARD)
51        .default_radius(tokens::RADIUS_SM)
52        .default_padding(Sides::xy(tokens::SPACE_2, 0.0))
53        .default_gap(tokens::SPACE_2)
54        .width(Size::Fill(1.0))
55        .default_height(Size::Fixed(tokens::CONTROL_HEIGHT))
56}
57
58#[track_caller]
59pub fn command_icon(source: impl IntoIconSource) -> El {
60    El::new(Kind::Custom("command_icon"))
61        .at_loc(Location::caller())
62        .style_profile(StyleProfile::Surface)
63        .child(
64            icon(source)
65                .icon_size(tokens::ICON_XS)
66                .color(tokens::FOREGROUND),
67        )
68        .align(Align::Center)
69        .justify(Justify::Center)
70        .fill(tokens::MUTED)
71        .stroke(tokens::BORDER)
72        .default_radius(tokens::RADIUS_SM)
73        .width(Size::Fixed(24.0))
74        .height(Size::Fixed(24.0))
75}
76
77#[track_caller]
78pub fn command_label(label: impl Into<String>) -> El {
79    text(label)
80        .at_loc(Location::caller())
81        .label()
82        .font_weight(FontWeight::Regular)
83        .ellipsis()
84        .width(Size::Fill(1.0))
85}
86
87#[track_caller]
88pub fn command_shortcut(shortcut: impl Into<String>) -> El {
89    mono(shortcut)
90        .at_loc(Location::caller())
91        .caption()
92        .color(tokens::MUTED_FOREGROUND)
93        .width(Size::Hug)
94}
95
96#[track_caller]
97pub fn command_row(
98    source: impl IntoIconSource,
99    label: impl Into<String>,
100    shortcut: impl Into<String>,
101) -> El {
102    command_item([
103        command_icon(source),
104        command_label(label),
105        command_shortcut(shortcut),
106    ])
107    .at_loc(Location::caller())
108}
109
110#[cfg(test)]
111mod tests {
112    use super::*;
113
114    #[test]
115    fn command_item_uses_menu_density_and_centered_row() {
116        let item = command_item([command_label("New branch"), command_shortcut("Ctrl+B")]);
117
118        assert_eq!(item.kind, Kind::Custom("command_item"));
119        assert_eq!(item.metrics_role, Some(MetricsRole::MenuItem));
120        assert_eq!(item.axis, Axis::Row);
121        assert_eq!(item.align, Align::Center);
122        assert_eq!(item.gap, tokens::SPACE_2);
123        assert_eq!(item.width, Size::Fill(1.0));
124        assert!(item.focusable);
125    }
126
127    #[test]
128    fn command_row_builds_icon_label_and_shortcut() {
129        let row = command_row("git-branch", "New branch", "Ctrl+B");
130
131        assert_eq!(row.children.len(), 3);
132        assert_eq!(row.children[0].kind, Kind::Custom("command_icon"));
133        assert_eq!(row.children[0].width, Size::Fixed(24.0));
134        assert_eq!(row.children[1].text.as_deref(), Some("New branch"));
135        assert_eq!(row.children[1].width, Size::Fill(1.0));
136        assert_eq!(row.children[2].text.as_deref(), Some("Ctrl+B"));
137        assert_eq!(row.children[2].text_role, TextRole::Caption);
138    }
139}