Add icon, list_item, work on project panel

Nate Butler created

Change summary

crates/storybook/src/ui.rs                      | 21 +++---
crates/storybook/src/ui/component.rs            |  1 
crates/storybook/src/ui/component/list_item.rs  | 57 +++++++++++++++++++
crates/storybook/src/ui/element.rs              |  1 
crates/storybook/src/ui/element/avatar.rs       |  2 
crates/storybook/src/ui/element/icon.rs         | 55 ++++++++++++++++++
crates/storybook/src/ui/element/label.rs        |  2 
crates/storybook/src/ui/module/project_panel.rs | 40 ++++++++++---
8 files changed, 157 insertions(+), 22 deletions(-)

Detailed changes

crates/storybook/src/ui.rs 🔗

@@ -1,21 +1,22 @@
-mod component;
 mod element;
-mod module;
+pub use element::avatar::*;
+pub use element::icon::*;
+pub use element::icon_button::*;
+pub use element::indicator::*;
+pub use element::input::*;
+pub use element::label::*;
+pub use element::text_button::*;
+pub use element::tool_divider::*;
 
+mod component;
 pub use component::facepile::*;
 pub use component::follow_group::*;
+pub use component::list_item::*;
 pub use component::tab::*;
 
+mod module;
 pub use module::chat_panel::*;
 pub use module::project_panel::*;
 pub use module::status_bar::*;
 pub use module::tab_bar::*;
 pub use module::title_bar::*;
-
-pub use element::avatar::*;
-pub use element::icon_button::*;
-pub use element::indicator::*;
-pub use element::input::*;
-pub use element::label::*;
-pub use element::text_button::*;
-pub use element::tool_divider::*;

crates/storybook/src/ui/component/list_item.rs 🔗

@@ -0,0 +1,57 @@
+use crate::prelude::InteractionState;
+use crate::theme::theme;
+use crate::ui::{icon, IconAsset, Label};
+use gpui2::geometry::rems;
+use gpui2::style::StyleHelpers;
+use gpui2::{elements::div, IntoElement};
+use gpui2::{Element, ParentElement, ViewContext};
+
+#[derive(Element)]
+pub struct ListItem {
+    label: Label,
+    left_icon: Option<IconAsset>,
+    indent_level: f32,
+    state: InteractionState,
+}
+
+pub fn list_item(label: Label) -> ListItem {
+    ListItem {
+        label,
+        indent_level: 0.0,
+        left_icon: None,
+        state: InteractionState::default(),
+    }
+}
+
+impl ListItem {
+    pub fn indent_level(mut self, indent_level: f32) -> Self {
+        self.indent_level = indent_level;
+        self
+    }
+    pub fn left_icon(mut self, left_icon: Option<IconAsset>) -> Self {
+        self.left_icon = left_icon;
+        self
+    }
+    pub fn state(mut self, state: InteractionState) -> Self {
+        self.state = state;
+        self
+    }
+
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
+        let mut el = div()
+            .h_7()
+            .px_2()
+            .ml(rems(0.75 * self.indent_level.clone()))
+            .flex()
+            .gap_2()
+            .items_center();
+
+        if self.left_icon.is_some() {
+            el = el.child(icon(self.left_icon.clone().unwrap()))
+        }
+
+        el.child(self.label.clone())
+    }
+}

crates/storybook/src/ui/element/avatar.rs 🔗

@@ -5,8 +5,6 @@ use gpui2::style::StyleHelpers;
 use gpui2::{ArcCow, IntoElement};
 use gpui2::{Element, ViewContext};
 
-pub type UnknownString = ArcCow<'static, str>;
-
 #[derive(Element, Clone)]
 pub struct Avatar {
     src: ArcCow<'static, str>,

crates/storybook/src/ui/element/icon.rs 🔗

@@ -0,0 +1,55 @@
+use std::borrow::Cow;
+
+use crate::theme::theme;
+use gpui2::elements::svg;
+use gpui2::style::StyleHelpers;
+use gpui2::IntoElement;
+use gpui2::{Element, ViewContext};
+
+#[derive(Default, PartialEq, Copy, Clone)]
+pub enum IconAsset {
+    Ai,
+    ArrowLeft,
+    ArrowRight,
+    #[default]
+    ArrowUpRight,
+    Bolt,
+    Hash,
+    File,
+    Folder,
+    FolderOpen,
+}
+
+pub fn icon_asset(asset: IconAsset) -> impl Into<Cow<'static, str>> {
+    match asset {
+        IconAsset::Ai => "icons/ai.svg",
+        IconAsset::ArrowLeft => "icons/arrow_left.svg",
+        IconAsset::ArrowRight => "icons/arrow_right.svg",
+        IconAsset::ArrowUpRight => "icons/arrow_up_right.svg",
+        IconAsset::Bolt => "icons/bolt.svg",
+        IconAsset::Hash => "icons/hash.svg",
+        IconAsset::File => "icons/file_icons/file.svg",
+        IconAsset::Folder => "icons/file_icons/folder.svg",
+        IconAsset::FolderOpen => "icons/file_icons/folder_open.svg",
+    }
+}
+
+#[derive(Element, Clone)]
+pub struct Icon {
+    asset: IconAsset,
+}
+
+pub fn icon(asset: IconAsset) -> Icon {
+    Icon { asset }
+}
+
+impl Icon {
+    fn render<V: 'static>(&mut self, _: &mut V, cx: &mut ViewContext<V>) -> impl IntoElement<V> {
+        let theme = theme(cx);
+
+        svg()
+            .path(icon_asset(self.asset))
+            .size_4()
+            .fill(theme.lowest.base.default.foreground)
+    }
+}

crates/storybook/src/ui/module/project_panel.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     prelude::InteractionState,
     theme::theme,
-    ui::{input, label, LabelColor},
+    ui::{input, label, list_item, IconAsset, LabelColor},
 };
 use gpui2::{
     elements::{div, div::ScrollState},
@@ -29,7 +29,7 @@ impl<V: 'static> ProjectPanel<V> {
         let theme = theme(cx);
 
         div()
-            .w_64()
+            .w_56()
             .h_full()
             .flex()
             .flex_col()
@@ -41,17 +41,39 @@ impl<V: 'static> ProjectPanel<V> {
                     .flex_col()
                     .overflow_y_scroll(self.scroll_state.clone())
                     .child(
-                        div().py_2().flex().flex_col().children(
+                        div().flex().flex_col().children(
                             std::iter::repeat_with(|| {
                                 vec![
-                                    label("File"),
-                                    label("Modified File").color(LabelColor::Modified),
-                                    label("Created File").color(LabelColor::Created),
-                                    label("Deleted File").color(LabelColor::Deleted),
-                                    label("Hidden File").color(LabelColor::Hidden),
+                                    list_item(label("storybook").color(LabelColor::Modified))
+                                        .left_icon(IconAsset::FolderOpen.into())
+                                        .indent_level(0.0),
+                                    list_item(label("docs").color(LabelColor::Default))
+                                        .left_icon(IconAsset::Folder.into())
+                                        .indent_level(1.0),
+                                    list_item(label("src").color(LabelColor::Modified))
+                                        .left_icon(IconAsset::FolderOpen.into())
+                                        .indent_level(2.0),
+                                    list_item(label("ui").color(LabelColor::Modified))
+                                        .left_icon(IconAsset::FolderOpen.into())
+                                        .indent_level(3.0),
+                                    list_item(label("component").color(LabelColor::Created))
+                                        .left_icon(IconAsset::FolderOpen.into())
+                                        .indent_level(4.0),
+                                    list_item(label("facepile.rs").color(LabelColor::Default))
+                                        .left_icon(IconAsset::File.into())
+                                        .indent_level(5.0),
+                                    list_item(label("follow_group.rs").color(LabelColor::Default))
+                                        .left_icon(IconAsset::File.into())
+                                        .indent_level(5.0),
+                                    list_item(label("list_item.rs").color(LabelColor::Created))
+                                        .left_icon(IconAsset::File.into())
+                                        .indent_level(5.0),
+                                    list_item(label("tab.rs").color(LabelColor::Default))
+                                        .left_icon(IconAsset::File.into())
+                                        .indent_level(5.0),
                                 ]
                             })
-                            .take(60)
+                            .take(10)
                             .flatten(),
                         ),
                     ),