palette.rs

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