ui_storybook/stories/
menus.rs1use leptos::prelude::*;
7
8use crate::components::menus::{
9 MenuBadgeView, MenuFooter, MenuList, MenuRow, MenuRowKind, MenuSection, PopoverPlacement,
10 PopoverSurface,
11};
12use crate::components::primitives::{
13 BadgeKind, Button, ButtonVariant, IconTile, IconTileKind, Kbd,
14};
15
16use super::{Story, StoryViewport, render};
17
18#[must_use]
20pub fn stories() -> Vec<Story> {
21 vec![
22 Story {
23 id: "popover-basic",
24 category: "Menus",
25 title: "Popover — header + menu list",
26 viewport: StoryViewport::Fixed {
27 width: 320,
28 height: 280,
29 },
30 render: render_popover_basic,
31 },
32 Story {
33 id: "popover-with-footer",
34 category: "Menus",
35 title: "Popover — with footer button",
36 viewport: StoryViewport::Fixed {
37 width: 320,
38 height: 320,
39 },
40 render: render_popover_with_footer,
41 },
42 Story {
43 id: "menu-row-selected",
44 category: "Menus",
45 title: "Menu row — selected",
46 viewport: StoryViewport::Fixed {
47 width: 280,
48 height: 60,
49 },
50 render: render_menu_row_selected,
51 },
52 Story {
53 id: "menu-row-action",
54 category: "Menus",
55 title: "Menu row — action",
56 viewport: StoryViewport::Fixed {
57 width: 280,
58 height: 60,
59 },
60 render: render_menu_row_action,
61 },
62 Story {
63 id: "menu-row-device",
64 category: "Menus",
65 title: "Menu row — device with subtitle + kbd",
66 viewport: StoryViewport::Fixed {
67 width: 320,
68 height: 80,
69 },
70 render: render_menu_row_device,
71 },
72 Story {
73 id: "menu-long-labels",
74 category: "Menus",
75 title: "Menu — long labels truncate cleanly",
76 viewport: StoryViewport::Fixed {
77 width: 280,
78 height: 200,
79 },
80 render: render_menu_long_labels,
81 },
82 ]
83}
84
85fn render_popover_basic() -> String {
86 render(view! {
87 <PopoverSurface
88 placement=PopoverPlacement::BottomLeft
89 width_px=280_u16
90 title="Workspaces".to_string()
91 description="Switch between your personal and team workspaces.".to_string()
92 >
93 <MenuList label="Workspaces">
94 <MenuSection heading="Personal".to_string()>
95 <MenuRow
96 kind=MenuRowKind::Selected
97 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Workspace>"PE"</IconTile> })
98 title="Personal".to_string()
99 />
100 </MenuSection>
101 <MenuSection heading="Teams".to_string()>
102 <MenuRow
103 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Workspace>"AC"</IconTile> })
104 title="Acme Inc.".to_string()
105 subtitle="3 members".to_string()
106 />
107 <MenuRow
108 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Workspace>"CD"</IconTile> })
109 title="Client demos".to_string()
110 />
111 </MenuSection>
112 </MenuList>
113 </PopoverSurface>
114 })
115}
116
117fn render_popover_with_footer() -> String {
118 render(view! {
119 <PopoverSurface
120 placement=PopoverPlacement::BottomLeft
121 width_px=300_u16
122 title="Choose camera".to_string()
123 footer=ToChildren::to_children(|| view! {
124 <MenuFooter>
125 <span>"Tip: press "<Kbd keys=vec!["C"] />" to switch"</span>
126 <Button variant=ButtonVariant::Default>"Add device"</Button>
127 </MenuFooter>
128 })
129 >
130 <MenuList label="Cameras">
131 <MenuRow
132 kind=MenuRowKind::Selected
133 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Device>"📷"</IconTile> })
134 title="FaceTime HD camera".to_string()
135 subtitle="MacBook · 1080p".to_string()
136 />
137 <MenuRow
138 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Device>"📷"</IconTile> })
139 title="iPhone 15 Pro".to_string()
140 subtitle="Continuity Camera · 4K".to_string()
141 badges=vec![MenuBadgeView { label: "New", kind: BadgeKind::Accent }]
142 />
143 <MenuRow
144 kind=MenuRowKind::Disabled
145 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Device>"📷"</IconTile> })
146 title="OBS Virtual Camera".to_string()
147 subtitle="Not connected".to_string()
148 />
149 </MenuList>
150 </PopoverSurface>
151 })
152}
153
154fn render_menu_row_selected() -> String {
155 render(view! {
156 <ul class="menu-list" role="menu" aria-label="example" style="width:260px;padding:6px;background:var(--surface-popover);border:1px solid var(--line-subtle);border-radius:8px;">
157 <MenuRow
158 kind=MenuRowKind::Selected
159 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Action>"⏵"</IconTile> })
160 title="Open editor".to_string()
161 trailing=ToChildren::to_children(|| view! { <Kbd keys=vec!["⌘", "O"] /> })
162 />
163 </ul>
164 })
165}
166
167fn render_menu_row_action() -> String {
168 render(view! {
169 <ul class="menu-list" role="menu" aria-label="example" style="width:260px;padding:6px;background:var(--surface-popover);border:1px solid var(--line-subtle);border-radius:8px;">
170 <MenuRow
171 kind=MenuRowKind::Action
172 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Action>"+"</IconTile> })
173 title="Add workspace".to_string()
174 />
175 <MenuRow
176 kind=MenuRowKind::Danger
177 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Action>"−"</IconTile> })
178 title="Remove workspace".to_string()
179 />
180 </ul>
181 })
182}
183
184fn render_menu_row_device() -> String {
185 render(view! {
186 <ul class="menu-list" role="menu" aria-label="example" style="width:300px;padding:6px;background:var(--surface-popover);border:1px solid var(--line-subtle);border-radius:8px;">
187 <MenuRow
188 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Device>"🎙"</IconTile> })
189 title="AirPods Pro".to_string()
190 subtitle="Bluetooth · 86% battery".to_string()
191 badges=vec![MenuBadgeView { label: "Live", kind: BadgeKind::Live }]
192 trailing=ToChildren::to_children(|| view! { <Kbd keys=vec!["⌘", "M"] /> })
193 />
194 </ul>
195 })
196}
197
198fn render_menu_long_labels() -> String {
199 render(view! {
200 <PopoverSurface placement=PopoverPlacement::BottomLeft width_px=260_u16>
201 <MenuList label="Long labels">
202 <MenuRow
203 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::Device>"🎙"</IconTile> })
204 title="A very long microphone label that overflows the row width".to_string()
205 subtitle="Subtitle that is also extremely long and needs to truncate cleanly".to_string()
206 />
207 <MenuRow
208 leading=ToChildren::to_children(|| view! { <IconTile kind=IconTileKind::App>"S"</IconTile> })
209 title="App name with lots of words to test truncation".to_string()
210 />
211 </MenuList>
212 </PopoverSurface>
213 })
214}