Add `AssistantPanel` component

Marshall Bowers created

Change summary

crates/storybook2/src/stories/components.rs                 |  1 
crates/storybook2/src/stories/components/assistant_panel.rs | 26 ++
crates/storybook2/src/story_selector.rs                     |  4 
crates/ui2/src/components.rs                                |  2 
crates/ui2/src/components/assistant_panel.rs                | 90 +++++++
5 files changed, 123 insertions(+)

Detailed changes

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

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

crates/storybook2/src/story_selector.rs 🔗

@@ -32,6 +32,7 @@ impl ElementStory {
 #[derive(Debug, PartialEq, Eq, Clone, Copy, strum::Display, EnumString, EnumIter)]
 #[strum(serialize_all = "snake_case")]
 pub enum ComponentStory {
+    AssistantPanel,
     Panel,
 }
 
@@ -40,6 +41,9 @@ impl ComponentStory {
         use crate::stories::components;
 
         match self {
+            Self::AssistantPanel => {
+                components::assistant_panel::AssistantPanelStory::new().into_any()
+            }
             Self::Panel => components::panel::PanelStory::new().into_any(),
         }
     }

crates/ui2/src/components.rs 🔗

@@ -1,7 +1,9 @@
+mod assistant_panel;
 mod icon_button;
 mod list;
 mod panel;
 
+pub use assistant_panel::*;
 pub use icon_button::*;
 pub use list::*;
 pub use panel::*;

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

@@ -0,0 +1,90 @@
+use std::marker::PhantomData;
+
+use gpui3::{rems, AbsoluteLength};
+
+use crate::prelude::*;
+use crate::{Icon, IconButton, Label, Panel, PanelSide};
+
+#[derive(Element)]
+pub struct AssistantPanel<S: 'static + Send + Sync + Clone> {
+    state_type: PhantomData<S>,
+    scroll_state: ScrollState,
+    current_side: PanelSide,
+}
+
+impl<S: 'static + Send + Sync + Clone> AssistantPanel<S> {
+    pub fn new() -> Self {
+        Self {
+            state_type: PhantomData,
+            scroll_state: ScrollState::default(),
+            current_side: PanelSide::default(),
+        }
+    }
+
+    pub fn side(mut self, side: PanelSide) -> Self {
+        self.current_side = side;
+        self
+    }
+
+    fn render(&mut self, cx: &mut ViewContext<S>) -> impl Element<State = S> {
+        let theme = theme(cx);
+
+        struct PanelPayload {
+            pub scroll_state: ScrollState,
+        }
+
+        Panel::new(
+            self.scroll_state.clone(),
+            |_, payload| {
+                let payload = payload.downcast_ref::<PanelPayload>().unwrap();
+
+                vec![div()
+                    .flex()
+                    .flex_col()
+                    .h_full()
+                    .px_2()
+                    .gap_2()
+                    // Header
+                    .child(
+                        div()
+                            .flex()
+                            .justify_between()
+                            .gap_2()
+                            .child(
+                                div()
+                                    .flex()
+                                    .child(IconButton::new(Icon::Menu))
+                                    .child(Label::new("New Conversation")),
+                            )
+                            .child(
+                                div()
+                                    .flex()
+                                    .items_center()
+                                    .gap_px()
+                                    .child(IconButton::new(Icon::SplitMessage))
+                                    .child(IconButton::new(Icon::Quote))
+                                    .child(IconButton::new(Icon::MagicWand))
+                                    .child(IconButton::new(Icon::Plus))
+                                    .child(IconButton::new(Icon::Maximize)),
+                            ),
+                    )
+                    // Chat Body
+                    .child(
+                        div()
+                            .w_full()
+                            .flex()
+                            .flex_col()
+                            .gap_3()
+                            .overflow_y_scroll(payload.scroll_state.clone())
+                            .child(Label::new("Is this thing on?")),
+                    )
+                    .into_any()]
+            },
+            Box::new(PanelPayload {
+                scroll_state: self.scroll_state.clone(),
+            }),
+        )
+        .side(self.current_side)
+        .width(AbsoluteLength::Rems(rems(32.)))
+    }
+}