1use std::marker::PhantomData;
2
3use crate::prelude::*;
4use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
5
6#[derive(Element)]
7pub struct Palette<S: 'static + Send + Sync + Clone> {
8 state_type: PhantomData<S>,
9 scroll_state: ScrollState,
10 input_placeholder: SharedString,
11 empty_string: SharedString,
12 items: Vec<PaletteItem<S>>,
13 default_order: OrderMethod,
14}
15
16impl<S: 'static + Send + Sync + Clone> Palette<S> {
17 pub fn new(scroll_state: ScrollState) -> Self {
18 Self {
19 state_type: PhantomData,
20 scroll_state,
21 input_placeholder: "Find something...".into(),
22 empty_string: "No items found.".into(),
23 items: vec![],
24 default_order: OrderMethod::default(),
25 }
26 }
27
28 pub fn items(mut self, items: Vec<PaletteItem<S>>) -> Self {
29 self.items = items;
30 self
31 }
32
33 pub fn placeholder(mut self, input_placeholder: impl Into<SharedString>) -> Self {
34 self.input_placeholder = input_placeholder.into();
35 self
36 }
37
38 pub fn empty_string(mut self, empty_string: impl Into<SharedString>) -> Self {
39 self.empty_string = empty_string.into();
40 self
41 }
42
43 // TODO: Hook up sort order
44 pub fn default_order(mut self, default_order: OrderMethod) -> Self {
45 self.default_order = default_order;
46 self
47 }
48
49 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
50 let color = ThemeColor::new(cx);
51
52 v_stack()
53 .w_96()
54 .rounded_lg()
55 .bg(color.elevated_surface)
56 .border()
57 .border_color(color.border)
58 .child(
59 v_stack()
60 .gap_px()
61 .child(v_stack().py_0p5().px_1().child(div().px_2().py_0p5().child(
62 Label::new(self.input_placeholder.clone()).color(LabelColor::Placeholder),
63 )))
64 .child(div().h_px().w_full().bg(color.filled_element))
65 .child(
66 v_stack()
67 .py_0p5()
68 .px_1()
69 .grow()
70 .max_h_96()
71 .overflow_y_scroll(self.scroll_state.clone())
72 .children(
73 vec![if self.items.is_empty() {
74 Some(
75 h_stack().justify_between().px_2().py_1().child(
76 Label::new(self.empty_string.clone())
77 .color(LabelColor::Muted),
78 ),
79 )
80 } else {
81 None
82 }]
83 .into_iter()
84 .flatten(),
85 )
86 .children(self.items.iter().enumerate().map(|(index, item)| {
87 h_stack()
88 .id(index)
89 .justify_between()
90 .px_2()
91 .py_0p5()
92 .rounded_lg()
93 .hover(|style| style.bg(color.ghost_element_hover))
94 .active(|style| style.bg(color.ghost_element_active))
95 .child(item.clone())
96 })),
97 ),
98 )
99 }
100}
101
102#[derive(Element, Clone)]
103pub struct PaletteItem<S: 'static + Send + Sync + Clone> {
104 pub label: SharedString,
105 pub sublabel: Option<SharedString>,
106 pub keybinding: Option<Keybinding<S>>,
107}
108
109impl<S: 'static + Send + Sync + Clone> PaletteItem<S> {
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<S>>>,
131 {
132 self.keybinding = keybinding.into();
133 self
134 }
135
136 fn render(&mut self, _view: &mut S, cx: &mut ViewContext<S>) -> impl Element<ViewState = S> {
137 let color = ThemeColor::new(cx);
138
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.keybinding.clone())
150 }
151}
152
153#[cfg(feature = "stories")]
154pub use stories::*;
155
156#[cfg(feature = "stories")]
157mod stories {
158 use crate::{ModifierKeys, Story};
159
160 use super::*;
161
162 #[derive(Element)]
163 pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
164 state_type: PhantomData<S>,
165 }
166
167 impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
168 pub fn new() -> Self {
169 Self {
170 state_type: PhantomData,
171 }
172 }
173
174 fn render(
175 &mut self,
176 _view: &mut S,
177 cx: &mut ViewContext<S>,
178 ) -> impl Element<ViewState = S> {
179 Story::container(cx)
180 .child(Story::title_for::<_, Palette<S>>(cx))
181 .child(Story::label(cx, "Default"))
182 .child(Palette::new(ScrollState::default()))
183 .child(Story::label(cx, "With Items"))
184 .child(
185 Palette::new(ScrollState::default())
186 .placeholder("Execute a command...")
187 .items(vec![
188 PaletteItem::new("theme selector: toggle").keybinding(
189 Keybinding::new_chord(
190 ("k".to_string(), ModifierKeys::new().command(true)),
191 ("t".to_string(), ModifierKeys::new().command(true)),
192 ),
193 ),
194 PaletteItem::new("assistant: inline assist").keybinding(
195 Keybinding::new(
196 "enter".to_string(),
197 ModifierKeys::new().command(true),
198 ),
199 ),
200 PaletteItem::new("assistant: quote selection").keybinding(
201 Keybinding::new(">".to_string(), ModifierKeys::new().command(true)),
202 ),
203 PaletteItem::new("assistant: toggle focus").keybinding(
204 Keybinding::new("?".to_string(), ModifierKeys::new().command(true)),
205 ),
206 PaletteItem::new("auto update: check"),
207 PaletteItem::new("auto update: view release notes"),
208 PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
209 "b".to_string(),
210 ModifierKeys::new().command(true).alt(true),
211 )),
212 PaletteItem::new("chat panel: toggle focus"),
213 PaletteItem::new("cli: install"),
214 PaletteItem::new("client: sign in"),
215 PaletteItem::new("client: sign out"),
216 PaletteItem::new("editor: cancel").keybinding(Keybinding::new(
217 "escape".to_string(),
218 ModifierKeys::new(),
219 )),
220 ]),
221 )
222 }
223 }
224}