crates/storybook2/src/stories/components.rs 🔗
@@ -5,6 +5,7 @@ pub mod chat_panel;
pub mod collab_panel;
pub mod facepile;
pub mod keybinding;
+pub mod palette;
pub mod panel;
pub mod project_panel;
pub mod tab;
Marshall Bowers created
crates/storybook2/src/stories/components.rs | 1
crates/storybook2/src/stories/components/palette.rs | 63 ++++++
crates/storybook2/src/story_selector.rs | 2
crates/ui2/src/components.rs | 2
crates/ui2/src/components/palette.rs | 152 +++++++++++++++
5 files changed, 220 insertions(+)
@@ -5,6 +5,7 @@ pub mod chat_panel;
pub mod collab_panel;
pub mod facepile;
pub mod keybinding;
+pub mod palette;
pub mod panel;
pub mod project_panel;
pub mod tab;
@@ -0,0 +1,63 @@
+use std::marker::PhantomData;
+
+use ui::prelude::*;
+use ui::{Keybinding, ModifierKeys, Palette, PaletteItem};
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct PaletteStory<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> PaletteStory<S> {
+ pub fn new() -> Self {
+ Self {
+ state_type: PhantomData,
+ }
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ Story::container(cx)
+ .child(Story::title_for::<_, Palette<S>>(cx))
+ .child(Story::label(cx, "Default"))
+ .child(Palette::new(ScrollState::default()))
+ .child(Story::label(cx, "With Items"))
+ .child(
+ Palette::new(ScrollState::default())
+ .placeholder("Execute a command...")
+ .items(vec![
+ PaletteItem::new("theme selector: toggle").keybinding(
+ Keybinding::new_chord(
+ ("k".to_string(), ModifierKeys::new().command(true)),
+ ("t".to_string(), ModifierKeys::new().command(true)),
+ ),
+ ),
+ PaletteItem::new("assistant: inline assist").keybinding(Keybinding::new(
+ "enter".to_string(),
+ ModifierKeys::new().command(true),
+ )),
+ PaletteItem::new("assistant: quote selection").keybinding(Keybinding::new(
+ ">".to_string(),
+ ModifierKeys::new().command(true),
+ )),
+ PaletteItem::new("assistant: toggle focus").keybinding(Keybinding::new(
+ "?".to_string(),
+ ModifierKeys::new().command(true),
+ )),
+ PaletteItem::new("auto update: check"),
+ PaletteItem::new("auto update: view release notes"),
+ PaletteItem::new("branches: open recent").keybinding(Keybinding::new(
+ "b".to_string(),
+ ModifierKeys::new().command(true).alt(true),
+ )),
+ PaletteItem::new("chat panel: toggle focus"),
+ PaletteItem::new("cli: install"),
+ PaletteItem::new("client: sign in"),
+ PaletteItem::new("client: sign out"),
+ PaletteItem::new("editor: cancel")
+ .keybinding(Keybinding::new("escape".to_string(), ModifierKeys::new())),
+ ]),
+ )
+ }
+}
@@ -43,6 +43,7 @@ pub enum ComponentStory {
CollabPanel,
Facepile,
Keybinding,
+ Palette,
Panel,
ProjectPanel,
Tab,
@@ -68,6 +69,7 @@ impl ComponentStory {
Self::CollabPanel => components::collab_panel::CollabPanelStory::new().into_any(),
Self::Facepile => components::facepile::FacepileStory::new().into_any(),
Self::Keybinding => components::keybinding::KeybindingStory::new().into_any(),
+ Self::Palette => components::palette::PaletteStory::new().into_any(),
Self::Panel => components::panel::PanelStory::new().into_any(),
Self::ProjectPanel => components::project_panel::ProjectPanelStory::new().into_any(),
Self::Tab => components::tab::TabStory::new().into_any(),
@@ -8,6 +8,7 @@ mod facepile;
mod icon_button;
mod keybinding;
mod list;
+mod palette;
mod panel;
mod panes;
mod player_stack;
@@ -31,6 +32,7 @@ pub use facepile::*;
pub use icon_button::*;
pub use keybinding::*;
pub use list::*;
+pub use palette::*;
pub use panel::*;
pub use panes::*;
pub use player_stack::*;
@@ -0,0 +1,152 @@
+use std::marker::PhantomData;
+
+use crate::prelude::*;
+use crate::theme::theme;
+use crate::{h_stack, v_stack, Keybinding, Label, LabelColor};
+
+#[derive(Element)]
+pub struct Palette<S: 'static + Send + Sync + Clone> {
+ state_type: PhantomData<S>,
+ scroll_state: ScrollState,
+ input_placeholder: &'static str,
+ empty_string: &'static str,
+ items: Vec<PaletteItem<S>>,
+ default_order: OrderMethod,
+}
+
+impl<S: 'static + Send + Sync + Clone> Palette<S> {
+ pub fn new(scroll_state: ScrollState) -> Self {
+ Self {
+ state_type: PhantomData,
+ scroll_state,
+ input_placeholder: "Find something...",
+ empty_string: "No items found.",
+ items: vec![],
+ default_order: OrderMethod::default(),
+ }
+ }
+
+ pub fn items(mut self, items: Vec<PaletteItem<S>>) -> Self {
+ self.items = items;
+ self
+ }
+
+ pub fn placeholder(mut self, input_placeholder: &'static str) -> Self {
+ self.input_placeholder = input_placeholder;
+ self
+ }
+
+ pub fn empty_string(mut self, empty_string: &'static str) -> Self {
+ self.empty_string = empty_string;
+ self
+ }
+
+ // TODO: Hook up sort order
+ pub fn default_order(mut self, default_order: OrderMethod) -> Self {
+ self.default_order = default_order;
+ self
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ let theme = theme(cx);
+
+ v_stack()
+ .w_96()
+ .rounded_lg()
+ .fill(theme.lowest.base.default.background)
+ .border()
+ .border_color(theme.lowest.base.default.border)
+ .child(
+ v_stack()
+ .gap_px()
+ .child(v_stack().py_0p5().px_1().child(
+ div().px_2().py_0p5().child(
+ Label::new(self.input_placeholder).color(LabelColor::Placeholder),
+ ),
+ ))
+ .child(div().h_px().w_full().fill(theme.lowest.base.default.border))
+ .child(
+ v_stack()
+ .py_0p5()
+ .px_1()
+ .grow()
+ .max_h_96()
+ .overflow_y_scroll(self.scroll_state.clone())
+ .children(
+ vec![if self.items.is_empty() {
+ Some(h_stack().justify_between().px_2().py_1().child(
+ Label::new(self.empty_string).color(LabelColor::Muted),
+ ))
+ } else {
+ None
+ }]
+ .into_iter()
+ .flatten(),
+ )
+ .children(self.items.iter().map(|item| {
+ h_stack()
+ .justify_between()
+ .px_2()
+ .py_0p5()
+ .rounded_lg()
+ // .hover()
+ // .fill(theme.lowest.base.hovered.background)
+ // .active()
+ // .fill(theme.lowest.base.pressed.background)
+ .child(item.clone())
+ })),
+ ),
+ )
+ }
+}
+
+#[derive(Element, Clone)]
+pub struct PaletteItem<S: 'static + Send + Sync + Clone> {
+ pub label: &'static str,
+ pub sublabel: Option<&'static str>,
+ pub keybinding: Option<Keybinding<S>>,
+}
+
+impl<S: 'static + Send + Sync + Clone> PaletteItem<S> {
+ pub fn new(label: &'static str) -> Self {
+ Self {
+ label,
+ sublabel: None,
+ keybinding: None,
+ }
+ }
+
+ pub fn label(mut self, label: &'static str) -> Self {
+ self.label = label;
+ self
+ }
+
+ pub fn sublabel<L: Into<Option<&'static str>>>(mut self, sublabel: L) -> Self {
+ self.sublabel = sublabel.into();
+ self
+ }
+
+ pub fn keybinding<K>(mut self, keybinding: K) -> Self
+ where
+ K: Into<Option<Keybinding<S>>>,
+ {
+ self.keybinding = keybinding.into();
+ self
+ }
+
+ fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+ let theme = theme(cx);
+
+ div()
+ .flex()
+ .flex_row()
+ .grow()
+ .justify_between()
+ .child(
+ v_stack()
+ .child(Label::new(self.label))
+ .children(self.sublabel.map(|sublabel| Label::new(sublabel))),
+ )
+ .children(self.keybinding.clone())
+ }
+}