diff --git a/crates/story/src/main.rs b/crates/story/src/main.rs index 83d62b8a..20c5548b 100644 --- a/crates/story/src/main.rs +++ b/crates/story/src/main.rs @@ -1,6 +1,7 @@ use gpui::{prelude::*, *}; use gpui_component::{ h_flex, + input::TextInput, sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, v_flex, ActiveTheme as _, Icon, IconName, }; @@ -8,13 +9,26 @@ use story::*; pub struct Gallery { stories: Vec<(&'static str, Vec>)>, - active_group_index: usize, - active_index: usize, + active_group_index: Option, + active_index: Option, collapsed: bool, + search_input: Entity, + _subscriptions: Vec, } impl Gallery { pub fn new(window: &mut Window, cx: &mut Context) -> Self { + let search_input = cx.new(|cx| { + TextInput::new(window, cx) + .appearance(false) + .cleanable() + .placeholder("Search...") + }); + let _subscriptions = vec![cx.subscribe(&search_input, |this, _, _, cx| { + this.active_group_index = None; + this.active_index = None; + cx.notify() + })]; let stories = vec![ ( "Getting Started", @@ -61,10 +75,12 @@ impl Gallery { ]; Self { + search_input, stories, - active_group_index: 0, - active_index: 0, + active_group_index: Some(0), + active_index: Some(0), collapsed: false, + _subscriptions, } } @@ -75,11 +91,37 @@ impl Gallery { impl Render for Gallery { fn render(&mut self, _: &mut Window, cx: &mut Context) -> impl IntoElement { - let active_group = &self.stories[self.active_group_index]; - let stories = &active_group.1; - let active_story = stories[self.active_index].clone(); - let story_name = active_story.read(cx).name.clone(); - let description = active_story.read(cx).description.clone(); + let query = self.search_input.read(cx).text().trim().to_lowercase(); + + let stories: Vec<_> = self + .stories + .iter() + .filter_map(|(name, items)| { + let filtered_items: Vec<_> = items + .iter() + .filter(|story| story.read(cx).name.to_lowercase().contains(&query)) + .cloned() + .collect(); + + if !filtered_items.is_empty() { + Some((name, filtered_items)) + } else { + None + } + }) + .collect(); + + let active_group = self.active_group_index.and_then(|index| stories.get(index)); + let active_story = self + .active_index + .and(active_group) + .and_then(|group| group.1.get(self.active_index.unwrap())); + let (story_name, description) = + if let Some(story) = active_story.as_ref().map(|story| story.read(cx)) { + (story.name.clone(), story.description.clone()) + } else { + ("".into(), "".into()) + }; h_flex() .id("gallery-container") @@ -88,56 +130,75 @@ impl Render for Gallery { Sidebar::left() .collapsed(self.collapsed) .header( - SidebarHeader::new() + v_flex() .w_full() + .gap_4() + .child( + SidebarHeader::new() + .w_full() + .child( + div() + .flex() + .items_center() + .justify_center() + .rounded(cx.theme().radius) + .bg(cx.theme().primary) + .text_color(cx.theme().primary_foreground) + .size_8() + .flex_shrink_0() + .when(!self.collapsed, |this| { + this.child(Icon::new(IconName::GalleryVerticalEnd)) + }) + .when(self.collapsed, |this| { + this.size_4() + .bg(cx.theme().transparent) + .text_color(cx.theme().foreground) + .child(Icon::new(IconName::GalleryVerticalEnd)) + }) + .rounded_lg(), + ) + .when(!self.collapsed, |this| { + this.child( + v_flex() + .gap_0() + .text_sm() + .flex_1() + .line_height(relative(1.25)) + .overflow_hidden() + .text_ellipsis() + .child("GPUI Component") + .child( + div() + .text_color(cx.theme().muted_foreground) + .child("Gallery") + .text_xs(), + ), + ) + }), + ) .child( div() - .flex() - .items_center() - .justify_center() - .rounded(cx.theme().radius) - .bg(cx.theme().primary) - .text_color(cx.theme().primary_foreground) - .size_8() - .flex_shrink_0() - .when(!self.collapsed, |this| { - this.child(Icon::new(IconName::GalleryVerticalEnd)) - }) - .when(self.collapsed, |this| { - this.size_4() - .bg(cx.theme().transparent) - .text_color(cx.theme().foreground) - .child(Icon::new(IconName::GalleryVerticalEnd)) - }) - .rounded_lg(), - ) - .when(!self.collapsed, |this| { - this.child( - v_flex() - .gap_0() - .text_sm() - .flex_1() - .line_height(relative(1.25)) - .overflow_hidden() - .text_ellipsis() - .child("GPUI Component") - .child(div().child("Gallery").text_xs()), - ) - }), + .bg(cx.theme().sidebar_border) + .px_1() + .rounded_full() + .flex_1() + .mx_1() + .child(self.search_input.clone()), + ), ) - .children(self.stories.iter().enumerate().map( - |(group_ix, (group_name, stories))| { + .children(stories.clone().into_iter().enumerate().map( + |(group_ix, (group_name, sub_stories))| { SidebarGroup::new(*group_name).child(SidebarMenu::new().children( - stories.iter().enumerate().map(|(ix, story)| { + sub_stories.iter().enumerate().map(|(ix, story)| { SidebarMenuItem::new(story.read(cx).name.clone()) .active( - self.active_group_index == group_ix - && self.active_index == ix, + self.active_group_index == Some(group_ix) + && self.active_index == Some(ix), ) .on_click(cx.listener( move |this, _: &ClickEvent, _, cx| { - this.active_group_index = group_ix; - this.active_index = ix; + this.active_group_index = Some(group_ix); + this.active_index = Some(ix); cx.notify(); }, )) @@ -175,7 +236,9 @@ impl Render for Gallery { .id("story") .flex_1() .overflow_y_scroll() - .child(active_story), + .when_some(active_story, |this, active_story| { + this.child(active_story.clone()) + }), ), ) } diff --git a/crates/ui/src/input/input.rs b/crates/ui/src/input/input.rs index 17950421..91d40667 100644 --- a/crates/ui/src/input/input.rs +++ b/crates/ui/src/input/input.rs @@ -1859,7 +1859,7 @@ impl Render for TextInput { .id("suffix") .absolute() .gap(gap_x) - .bg(bg) + .when(self.appearance, |this| this.bg(bg)) .items_center() .when(suffix.is_none(), |this| this.pr_1()) .right_0()