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}