Add `CommandPalette` component

Marshall Bowers created

Change summary

crates/storybook2/src/stories/components.rs                 |  1 
crates/storybook2/src/stories/components/command_palette.rs | 26 ++
crates/storybook2/src/story_selector.rs                     |  4 
crates/ui2/src/components.rs                                |  2 
crates/ui2/src/components/command_palette.rs                | 29 +++
crates/ui2/src/static_data.rs                               | 62 ++++++
6 files changed, 121 insertions(+), 3 deletions(-)

Detailed changes

crates/storybook2/src/stories/components/command_palette.rs 🔗

@@ -0,0 +1,26 @@
+use std::marker::PhantomData;
+
+use ui::prelude::*;
+use ui::CommandPalette;
+
+use crate::story::Story;
+
+#[derive(Element)]
+pub struct CommandPaletteStory<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+}
+
+impl<S: 'static + Send + Sync + Clone> CommandPaletteStory<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::<_, CommandPalette<S>>(cx))
+            .child(Story::label(cx, "Default"))
+            .child(CommandPalette::new(ScrollState::default()))
+    }
+}

crates/storybook2/src/story_selector.rs 🔗

@@ -41,6 +41,7 @@ pub enum ComponentStory {
     Buffer,
     ChatPanel,
     CollabPanel,
+    CommandPalette,
     Facepile,
     Keybinding,
     Palette,
@@ -67,6 +68,9 @@ impl ComponentStory {
             Self::Breadcrumb => components::breadcrumb::BreadcrumbStory::new().into_any(),
             Self::ChatPanel => components::chat_panel::ChatPanelStory::new().into_any(),
             Self::CollabPanel => components::collab_panel::CollabPanelStory::new().into_any(),
+            Self::CommandPalette => {
+                components::command_palette::CommandPaletteStory::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(),

crates/ui2/src/components.rs 🔗

@@ -3,6 +3,7 @@ mod breadcrumb;
 mod buffer;
 mod chat_panel;
 mod collab_panel;
+mod command_palette;
 mod editor_pane;
 mod facepile;
 mod icon_button;
@@ -27,6 +28,7 @@ pub use breadcrumb::*;
 pub use buffer::*;
 pub use chat_panel::*;
 pub use collab_panel::*;
+pub use command_palette::*;
 pub use editor_pane::*;
 pub use facepile::*;
 pub use icon_button::*;

crates/ui2/src/components/command_palette.rs 🔗

@@ -0,0 +1,29 @@
+use std::marker::PhantomData;
+
+use crate::prelude::*;
+use crate::{example_editor_actions, OrderMethod, Palette};
+
+#[derive(Element)]
+pub struct CommandPalette<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+    scroll_state: ScrollState,
+}
+
+impl<S: 'static + Send + Sync + Clone> CommandPalette<S> {
+    pub fn new(scroll_state: ScrollState) -> Self {
+        Self {
+            state_type: PhantomData,
+            scroll_state,
+        }
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        div().child(
+            Palette::new(self.scroll_state.clone())
+                .items(example_editor_actions())
+                .placeholder("Execute a command...")
+                .empty_string("No items found.")
+                .default_order(OrderMethod::Ascending),
+        )
+    }
+}

crates/ui2/src/static_data.rs 🔗

@@ -5,9 +5,10 @@ use rand::Rng;
 
 use crate::{
     Buffer, BufferRow, BufferRows, Editor, FileSystemStatus, GitStatus, HighlightColor,
-    HighlightedLine, HighlightedText, Icon, Label, LabelColor, ListEntry, ListEntrySize, ListItem,
-    Livestream, MicStatus, Player, PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus,
-    Symbol, Tab, Theme, ToggleState, VideoStatus,
+    HighlightedLine, HighlightedText, Icon, Keybinding, Label, LabelColor, ListEntry,
+    ListEntrySize, ListItem, Livestream, MicStatus, ModifierKeys, PaletteItem, Player,
+    PlayerCallStatus, PlayerWithCallStatus, ScreenShareStatus, Symbol, Tab, Theme, ToggleState,
+    VideoStatus,
 };
 
 pub fn static_tabs_example<S: 'static + Send + Sync + Clone>() -> Vec<Tab<S>> {
@@ -541,6 +542,61 @@ pub fn static_collab_panel_channels<S: 'static + Send + Sync + Clone>() -> Vec<L
     .collect()
 }
 
+pub fn example_editor_actions<S: 'static + Send + Sync + Clone>() -> Vec<PaletteItem<S>> {
+    vec![
+        PaletteItem::new("New File").keybinding(Keybinding::new(
+            "N".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Open File").keybinding(Keybinding::new(
+            "O".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Save File").keybinding(Keybinding::new(
+            "S".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Cut").keybinding(Keybinding::new(
+            "X".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Copy").keybinding(Keybinding::new(
+            "C".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Paste").keybinding(Keybinding::new(
+            "V".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Undo").keybinding(Keybinding::new(
+            "Z".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Redo").keybinding(Keybinding::new(
+            "Z".to_string(),
+            ModifierKeys::new().control(true).shift(true),
+        )),
+        PaletteItem::new("Find").keybinding(Keybinding::new(
+            "F".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Replace").keybinding(Keybinding::new(
+            "R".to_string(),
+            ModifierKeys::new().control(true),
+        )),
+        PaletteItem::new("Jump to Line"),
+        PaletteItem::new("Select All"),
+        PaletteItem::new("Deselect All"),
+        PaletteItem::new("Switch Document"),
+        PaletteItem::new("Insert Line Below"),
+        PaletteItem::new("Insert Line Above"),
+        PaletteItem::new("Move Line Up"),
+        PaletteItem::new("Move Line Down"),
+        PaletteItem::new("Toggle Comment"),
+        PaletteItem::new("Delete Line"),
+    ]
+}
+
 pub fn empty_editor_example<S: 'static + Send + Sync + Clone>() -> Editor<S> {
     Editor {
         tabs: static_tabs_example(),