1use crate::{prelude::*, ListItemVariant};
2use crate::{v_stack, Label, List, ListEntry, ListItem, ListSeparator, ListSubHeader};
3
4pub enum ContextMenuItem<S: 'static + Send + Sync> {
5 Header(SharedString),
6 Entry(Label<S>),
7 Separator,
8}
9
10impl<S: 'static + Send + Sync> ContextMenuItem<S> {
11 fn to_list_item(self) -> ListItem<S> {
12 match self {
13 ContextMenuItem::Header(label) => ListSubHeader::new(label).into(),
14 ContextMenuItem::Entry(label) => {
15 ListEntry::new(label).variant(ListItemVariant::Inset).into()
16 }
17 ContextMenuItem::Separator => ListSeparator::new().into(),
18 }
19 }
20
21 pub fn header(label: impl Into<SharedString>) -> Self {
22 Self::Header(label.into())
23 }
24
25 pub fn separator() -> Self {
26 Self::Separator
27 }
28
29 pub fn entry(label: Label<S>) -> Self {
30 Self::Entry(label)
31 }
32}
33
34#[derive(Element)]
35pub struct ContextMenu<S: 'static + Send + Sync> {
36 items: Vec<ContextMenuItem<S>>,
37}
38
39impl<S: 'static + Send + Sync> ContextMenu<S> {
40 pub fn new(items: impl IntoIterator<Item = ContextMenuItem<S>>) -> Self {
41 Self {
42 items: items.into_iter().collect(),
43 }
44 }
45 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
46 let theme = theme(cx);
47
48 v_stack()
49 .flex()
50 .bg(theme.elevated_surface)
51 .border()
52 .border_color(theme.border)
53 .child(
54 List::new(
55 self.items
56 .drain(..)
57 .map(ContextMenuItem::to_list_item)
58 .collect(),
59 )
60 .toggle(ToggleState::Toggled),
61 )
62 }
63}
64
65#[cfg(feature = "stories")]
66pub use stories::*;
67
68#[cfg(feature = "stories")]
69mod stories {
70 use std::marker::PhantomData;
71
72 use crate::story::Story;
73
74 use super::*;
75
76 #[derive(Element)]
77 pub struct ContextMenuStory<S: 'static + Send + Sync> {
78 state_type: PhantomData<S>,
79 }
80
81 impl<S: 'static + Send + Sync> ContextMenuStory<S> {
82 pub fn new() -> Self {
83 Self {
84 state_type: PhantomData,
85 }
86 }
87
88 fn render(
89 &mut self,
90 _view: &mut S,
91 cx: &mut ViewContext<S>,
92 ) -> impl Element<ViewState = S> {
93 Story::container(cx)
94 .child(Story::title_for::<_, ContextMenu<S>>(cx))
95 .child(Story::label(cx, "Default"))
96 .child(ContextMenu::new([
97 ContextMenuItem::header("Section header"),
98 ContextMenuItem::Separator,
99 ContextMenuItem::entry(Label::new("Some entry")),
100 ]))
101 }
102 }
103}