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)
 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(div().h_px().w_full().bg(cx.theme().colors().element))
 60                    .child(
 61                        v_stack()
 62                            .id("items")
 63                            .py_0p5()
 64                            .px_1()
 65                            .grow()
 66                            .max_h_96()
 67                            .overflow_y_scroll()
 68                            .children(
 69                                vec![if self.items.is_empty() {
 70                                    Some(
 71                                        h_stack().justify_between().px_2().py_1().child(
 72                                            Label::new(self.empty_string.clone())
 73                                                .color(LabelColor::Muted),
 74                                        ),
 75                                    )
 76                                } else {
 77                                    None
 78                                }]
 79                                .into_iter()
 80                                .flatten(),
 81                            )
 82                            .children(self.items.into_iter().enumerate().map(|(index, item)| {
 83                                h_stack()
 84                                    .id(index)
 85                                    .justify_between()
 86                                    .px_2()
 87                                    .py_0p5()
 88                                    .rounded_lg()
 89                                    .hover(|style| {
 90                                        style.bg(cx.theme().colors().ghost_element_hover)
 91                                    })
 92                                    .active(|style| {
 93                                        style.bg(cx.theme().colors().ghost_element_active)
 94                                    })
 95                                    .child(item)
 96                            })),
 97                    ),
 98            )
 99    }
100}
101
102#[derive(Component)]
103pub struct PaletteItem {
104    pub label: SharedString,
105    pub sublabel: Option<SharedString>,
106    pub keybinding: Option<Keybinding>,
107}
108
109impl PaletteItem {
110    pub fn new(label: impl Into<SharedString>) -> Self {
111        Self {
112            label: label.into(),
113            sublabel: None,
114            keybinding: None,
115        }
116    }
117
118    pub fn label(mut self, label: impl Into<SharedString>) -> Self {
119        self.label = label.into();
120        self
121    }
122
123    pub fn sublabel(mut self, sublabel: impl Into<Option<SharedString>>) -> Self {
124        self.sublabel = sublabel.into();
125        self
126    }
127
128    pub fn keybinding<K>(mut self, keybinding: K) -> Self
129    where
130        K: Into<Option<Keybinding>>,
131    {
132        self.keybinding = keybinding.into();
133        self
134    }
135
136    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
137        div()
138            .flex()
139            .flex_row()
140            .grow()
141            .justify_between()
142            .child(
143                v_stack()
144                    .child(Label::new(self.label.clone()))
145                    .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
146            )
147            .children(self.keybinding)
148    }
149}
150
151use gpui2::ElementId;
152#[cfg(feature = "stories")]
153pub use stories::*;
154
155#[cfg(feature = "stories")]
156mod stories {
157    use gpui2::{Div, Render};
158
159    use crate::{ModifierKeys, Story};
160
161    use super::*;
162
163    pub struct PaletteStory;
164
165    impl Render for PaletteStory {
166        type Element = Div<Self>;
167
168        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
169            {
170                Story::container(cx)
171                    .child(Story::title_for::<_, Palette>(cx))
172                    .child(Story::label(cx, "Default"))
173                    .child(Palette::new("palette-1"))
174                    .child(Story::label(cx, "With Items"))
175                    .child(
176                        Palette::new("palette-2")
177                            .placeholder("Execute a command...")
178                            .items(vec![
179                                PaletteItem::new("theme selector: toggle").keybinding(
180                                    Keybinding::new_chord(
181                                        ("k".to_string(), ModifierKeys::new().command(true)),
182                                        ("t".to_string(), ModifierKeys::new().command(true)),
183                                    ),
184                                ),
185                                PaletteItem::new("assistant: inline assist").keybinding(
186                                    Keybinding::new(
187                                        "enter".to_string(),
188                                        ModifierKeys::new().command(true),
189                                    ),
190                                ),
191                                PaletteItem::new("assistant: quote selection").keybinding(
192                                    Keybinding::new(
193                                        ">".to_string(),
194                                        ModifierKeys::new().command(true),
195                                    ),
196                                ),
197                                PaletteItem::new("assistant: toggle focus").keybinding(
198                                    Keybinding::new(
199                                        "?".to_string(),
200                                        ModifierKeys::new().command(true),
201                                    ),
202                                ),
203                                PaletteItem::new("auto update: check"),
204                                PaletteItem::new("auto update: view release notes"),
205                                PaletteItem::new("branches: open recent").keybinding(
206                                    Keybinding::new(
207                                        "b".to_string(),
208                                        ModifierKeys::new().command(true).alt(true),
209                                    ),
210                                ),
211                                PaletteItem::new("chat panel: toggle focus"),
212                                PaletteItem::new("cli: install"),
213                                PaletteItem::new("client: sign in"),
214                                PaletteItem::new("client: sign out"),
215                                PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
216                                    "escape".to_string(),
217                                    ModifierKeys::new(),
218                                )),
219                            ]),
220                    )
221            }
222        }
223    }
224}