palette.rs

  1use crate::{h_stack, prelude::*, v_stack, KeyBinding, Label};
  2use gpui::prelude::*;
  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(TextColor::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(TextColor::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 key_binding: 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            key_binding: 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 key_binding(mut self, key_binding: impl Into<Option<KeyBinding>>) -> Self {
134        self.key_binding = key_binding.into();
135        self
136    }
137
138    fn render<V: 'static>(self, _view: &mut V, cx: &mut ViewContext<V>) -> impl Component<V> {
139        div()
140            .flex()
141            .flex_row()
142            .grow()
143            .justify_between()
144            .child(
145                v_stack()
146                    .child(Label::new(self.label.clone()))
147                    .children(self.sublabel.clone().map(|sublabel| Label::new(sublabel))),
148            )
149            .children(self.key_binding)
150    }
151}
152
153use gpui::ElementId;
154#[cfg(feature = "stories")]
155pub use stories::*;
156
157#[cfg(feature = "stories")]
158mod stories {
159    use gpui::{Div, Render};
160
161    use crate::{binding, Story};
162
163    use super::*;
164
165    pub struct PaletteStory;
166
167    impl Render for PaletteStory {
168        type Element = Div<Self>;
169
170        fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
171            {
172                Story::container(cx)
173                    .child(Story::title_for::<_, Palette>(cx))
174                    .child(Story::label(cx, "Default"))
175                    .child(Palette::new("palette-1"))
176                    .child(Story::label(cx, "With Items"))
177                    .child(
178                        Palette::new("palette-2")
179                            .placeholder("Execute a command...")
180                            .items(vec![
181                                PaletteItem::new("theme selector: toggle")
182                                    .key_binding(KeyBinding::new(binding("cmd-k cmd-t"))),
183                                PaletteItem::new("assistant: inline assist")
184                                    .key_binding(KeyBinding::new(binding("cmd-enter"))),
185                                PaletteItem::new("assistant: quote selection")
186                                    .key_binding(KeyBinding::new(binding("cmd-<"))),
187                                PaletteItem::new("assistant: toggle focus")
188                                    .key_binding(KeyBinding::new(binding("cmd-?"))),
189                                PaletteItem::new("auto update: check"),
190                                PaletteItem::new("auto update: view release notes"),
191                                PaletteItem::new("branches: open recent")
192                                    .key_binding(KeyBinding::new(binding("cmd-alt-b"))),
193                                PaletteItem::new("chat panel: toggle focus"),
194                                PaletteItem::new("cli: install"),
195                                PaletteItem::new("client: sign in"),
196                                PaletteItem::new("client: sign out"),
197                                PaletteItem::new("editor: cancel")
198                                    .key_binding(KeyBinding::new(binding("escape"))),
199                            ]),
200                    )
201            }
202        }
203    }
204}