ui_storybook/components/menus/
menu_row.rs1use leptos::prelude::*;
9
10use crate::components::primitives::{Badge, BadgeKind};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14pub enum MenuRowKind {
15 #[default]
17 Default,
18 Selected,
20 Action,
23 Danger,
26 Disabled,
28}
29
30impl MenuRowKind {
31 #[must_use]
33 pub fn css(self) -> &'static str {
34 match self {
35 MenuRowKind::Default => "menu-row-default",
36 MenuRowKind::Selected => "menu-row-selected",
37 MenuRowKind::Action => "menu-row-action",
38 MenuRowKind::Danger => "menu-row-danger",
39 MenuRowKind::Disabled => "menu-row-disabled",
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct MenuBadgeView {
47 pub label: &'static str,
49 pub kind: BadgeKind,
51}
52
53#[component]
54pub fn MenuRow(
55 #[prop(optional)] kind: MenuRowKind,
56 #[prop(optional)]
58 leading: Option<Children>,
59 #[prop(into)]
61 title: String,
62 #[prop(optional, into)]
64 subtitle: Option<String>,
65 #[prop(optional)]
67 badges: Vec<MenuBadgeView>,
68 #[prop(optional)]
70 trailing: Option<Children>,
71) -> impl IntoView {
72 let disabled = kind == MenuRowKind::Disabled;
73 let class = format!("menu-row {}", kind.css());
74 view! {
75 <li class="menu-row-item" role="none">
76 <button
77 class=class
78 role="menuitem"
79 aria-disabled=disabled
80 disabled=disabled
81 >
82 {leading.map(|l| view! { <span class="menu-row-leading">{l()}</span> })}
83 <span class="menu-row-text">
84 <span class="menu-row-title">{title}</span>
85 {subtitle.map(|s| view! { <span class="menu-row-subtitle">{s}</span> })}
86 </span>
87 {(!badges.is_empty()).then(|| view! {
88 <span class="menu-row-badges">
89 {badges.into_iter()
90 .map(|b| view! { <Badge kind=b.kind>{b.label}</Badge> })
91 .collect_view()}
92 </span>
93 })}
94 {trailing.map(|t| view! { <span class="menu-row-trailing">{t()}</span> })}
95 {(kind == MenuRowKind::Selected).then(|| view! {
96 <span class="menu-row-check" aria-hidden="true">"✓"</span>
97 })}
98 </button>
99 </li>
100 }
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn each_kind_has_unique_class() {
109 let classes = [
110 MenuRowKind::Default.css(),
111 MenuRowKind::Selected.css(),
112 MenuRowKind::Action.css(),
113 MenuRowKind::Danger.css(),
114 MenuRowKind::Disabled.css(),
115 ];
116 let mut sorted = classes.to_vec();
117 sorted.sort_unstable();
118 sorted.dedup();
119 assert_eq!(sorted.len(), classes.len());
120 }
121}