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}