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