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}