palette.rs

  1use std::marker::PhantomData;
  2
  3use crate::prelude::*;
  4use crate::theme::theme;
  5use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
  6
  7#[derive(Element)]
  8pub struct Palette<S: 'static + Send + Sync + Clone> {
  9    state_type: PhantomData<S>,
 10    scroll_state: ScrollState,
 11    input_placeholder: SharedString,
 12    empty_string: SharedString,
 13    items: Vec<PaletteItem<S>>,
 14    default_order: OrderMethod,
 15}
 16
 17impl<S: 'static + Send + Sync + Clone> Palette<S> {
 18    pub fn new(scroll_state: ScrollState) -> Self {
 19        Self {
 20            state_type: PhantomData,
 21            scroll_state,
 22            input_placeholder: "Find something...".into(),
 23            empty_string: "No items found.".into(),
 24            items: vec![],
 25            default_order: OrderMethod::default(),
 26        }
 27    }
 28
 29    pub fn items(mut self, items: Vec<PaletteItem<S>>) -> Self {
 30        self.items = items;
 31        self
 32    }
 33
 34    pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
 35        self.input_placeholder = input_placeholder.into();
 36        self
 37    }
 38
 39    pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
 40        self.empty_string = empty_string.into();
 41        self
 42    }
 43
 44    // TODO: Hook up sort order
 45    pub fn default_order(mut self, default_order: OrderMethod) -> Self {
 46        self.default_order = default_order;
 47        self
 48    }
 49
 50    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
 51        let theme = theme(cx);
 52
 53        v_stack()
 54            .w_96()
 55            .rounded_lg()
 56            .bg(theme.lowest.base.default.background)
 57            .border()
 58            .border_color(theme.lowest.base.default.border)
 59            .child(
 60                v_stack()
 61                    .gap_px()
 62                    .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
 63                        Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder),
 64                    )))
 65                    .child(div().h_px().w_full().bg(theme.lowest.base.default.border))
 66                    .child(
 67                        v_stack()
 68                            .py_0p5()
 69                            .px_1()
 70                            .grow()
 71                            .max_h_96()
 72                            .overflow_y_scroll(self.scroll_state.clone())
 73                            .children(
 74                                vec![if self.items.is_empty() {
 75                                    Some(
 76                                        h_stack().justify_between().px_2().py_1().child(
 77                                            Label::new(self.empty_string.clone())
 78                                                .color(LabelColor::Muted),
 79                                        ),
 80                                    )
 81                                } else {
 82                                    None
 83                                }]
 84                                .into_iter()
 85                                .flatten(),
 86                            )
 87                            .children(self.items.iter().enumerate().map(|(index, item)| {
 88                                h_stack()
 89                                    .id(index)
 90                                    .justify_between()
 91                                    .px_2()
 92                                    .py_0p5()
 93                                    .rounded_lg()
 94                                    .hover(|style| style.bg(theme.lowest.base.hovered.background))
 95                                    .active(|style| style.bg(theme.lowest.base.pressed.background))
 96                                    .child(item.clone())
 97                            })),
 98                    ),
 99            )
100    }
101}
102
103#[derive(Element, Clone)]
104pub struct PaletteItem<S: 'static + Send + Sync + Clone> {
105    pub label: SharedString,
106    pub sublabel: Option<SharedString>,
107    pub keybinding: Option<Keybinding<S>>,
108}
109
110impl<S: 'static + Send + Sync + Clone> PaletteItem<S> {
111    pub fn new(label: impl Into<SharedString>) -> Self {
112        Self {
113            label: label.into(),
114            sublabel: None,
115            keybinding: None,
116        }
117    }
118
119    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
120        self.label = label.into();
121        self
122    }
123
124    pub fn sublabel(mut self, sublabel: impl Into<Option<SharedString>>) -> Self {
125        self.sublabel = sublabel.into();
126        self
127    }
128
129    pub fn keybinding<K>(mut self, keybinding: K) -> Self
130    where
131        K: Into<Option<Keybinding<S>>>,
132    {
133        self.keybinding = keybinding.into();
134        self
135    }
136
137    fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
138        let theme = theme(cx);
139
140        div()
141            .flex()
142            .flex_row()
143            .grow()
144            .justify_between()
145            .child(
146                v_stack()
147                    .child(Label::new(self.label.clone()))
148                    .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
149            )
150            .children(self.keybinding.clone())
151    }
152}
153
154#[cfg(feature = "stories")]
155pub use stories::*;
156
157#[cfg(feature = "stories")]
158mod stories {
159    use crate::{ModifierKeys, Story};
160
161    use super::*;
162
163    #[derive(Element)]
164    pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
165        state_type: PhantomData<S>,
166    }
167
168    impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
169        pub fn new() -> Self {
170            Self {
171                state_type: PhantomData,
172            }
173        }
174
175        fn render(
176            &mut self,
177            _view: &mut S,
178            cx: &mut ViewContext<S>,
179        ) -> impl Element<ViewState = S> {
180            Story::container(cx)
181                .child(Story::title_for::<_, Palette<S>>(cx))
182                .child(Story::label(cx, "Default"))
183                .child(Palette::new(ScrollState::default()))
184                .child(Story::label(cx, "With Items"))
185                .child(
186                    Palette::new(ScrollState::default())
187                        .placeholder("Execute a command...")
188                        .items(vec![
189                            PaletteItem::new("theme selector: toggle").keybinding(
190                                Keybinding::new_chord(
191                                    ("k".to_string(), ModifierKeys::new().command(true)),
192                                    ("t".to_string(), ModifierKeys::new().command(true)),
193                                ),
194                            ),
195                            PaletteItem::new("assistant: inline assist").keybinding(
196                                Keybinding::new(
197                                    "enter".to_string(),
198                                    ModifierKeys::new().command(true),
199                                ),
200                            ),
201                            PaletteItem::new("assistant: quote selection").keybinding(
202                                Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
203                            ),
204                            PaletteItem::new("assistant: toggle focus").keybinding(
205                                Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
206                            ),
207                            PaletteItem::new("auto update: check"),
208                            PaletteItem::new("auto update: view release notes"),
209                            PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
210                                "b".to_string(),
211                                ModifierKeys::new().command(true).alt(true),
212                            )),
213                            PaletteItem::new("chat panel: toggle focus"),
214                            PaletteItem::new("cli: install"),
215                            PaletteItem::new("client: sign in"),
216                            PaletteItem::new("client: sign out"),
217                            PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
218                                "escape".to_string(),
219                                ModifierKeys::new(),
220                            )),
221                        ]),
222                )
223        }
224    }
225}