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