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