gallery: Add to support search stories. (#802)

<img width="1196" alt="image"
src="https://github.com/user-attachments/assets/daa3f47a-0c4d-404c-a673-8e18880e8f6c"
/>
This commit is contained in:
Jason Lee 2025-04-17 20:46:57 +08:00 committed by GitHub
parent be63d9701c
commit 16eb0f304b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 114 additions and 51 deletions

View file

@ -1,6 +1,7 @@
use gpui::{prelude::*, *}; use gpui::{prelude::*, *};
use gpui_component::{ use gpui_component::{
h_flex, h_flex,
input::TextInput,
sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem}, sidebar::{Sidebar, SidebarGroup, SidebarHeader, SidebarMenu, SidebarMenuItem},
v_flex, ActiveTheme as _, Icon, IconName, v_flex, ActiveTheme as _, Icon, IconName,
}; };
@ -8,13 +9,26 @@ use story::*;
pub struct Gallery { pub struct Gallery {
stories: Vec<(&'static str, Vec<Entity<StoryContainer>>)>, stories: Vec<(&'static str, Vec<Entity<StoryContainer>>)>,
active_group_index: usize, active_group_index: Option<usize>,
active_index: usize, active_index: Option<usize>,
collapsed: bool, collapsed: bool,
search_input: Entity<TextInput>,
_subscriptions: Vec<Subscription>,
} }
impl Gallery { impl Gallery {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self { pub fn new(window: &mut Window, cx: &mut Context<Self>) -> 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![ let stories = vec![
( (
"Getting Started", "Getting Started",
@ -61,10 +75,12 @@ impl Gallery {
]; ];
Self { Self {
search_input,
stories, stories,
active_group_index: 0, active_group_index: Some(0),
active_index: 0, active_index: Some(0),
collapsed: false, collapsed: false,
_subscriptions,
} }
} }
@ -75,11 +91,37 @@ impl Gallery {
impl Render for Gallery { impl Render for Gallery {
fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement { fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let active_group = &self.stories[self.active_group_index]; let query = self.search_input.read(cx).text().trim().to_lowercase();
let stories = &active_group.1;
let active_story = stories[self.active_index].clone(); let stories: Vec<_> = self
let story_name = active_story.read(cx).name.clone(); .stories
let description = active_story.read(cx).description.clone(); .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() h_flex()
.id("gallery-container") .id("gallery-container")
@ -88,56 +130,75 @@ impl Render for Gallery {
Sidebar::left() Sidebar::left()
.collapsed(self.collapsed) .collapsed(self.collapsed)
.header( .header(
SidebarHeader::new() v_flex()
.w_full() .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( .child(
div() div()
.flex() .bg(cx.theme().sidebar_border)
.items_center() .px_1()
.justify_center() .rounded_full()
.rounded(cx.theme().radius) .flex_1()
.bg(cx.theme().primary) .mx_1()
.text_color(cx.theme().primary_foreground) .child(self.search_input.clone()),
.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()),
)
}),
) )
.children(self.stories.iter().enumerate().map( .children(stories.clone().into_iter().enumerate().map(
|(group_ix, (group_name, stories))| { |(group_ix, (group_name, sub_stories))| {
SidebarGroup::new(*group_name).child(SidebarMenu::new().children( 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()) SidebarMenuItem::new(story.read(cx).name.clone())
.active( .active(
self.active_group_index == group_ix self.active_group_index == Some(group_ix)
&& self.active_index == ix, && self.active_index == Some(ix),
) )
.on_click(cx.listener( .on_click(cx.listener(
move |this, _: &ClickEvent, _, cx| { move |this, _: &ClickEvent, _, cx| {
this.active_group_index = group_ix; this.active_group_index = Some(group_ix);
this.active_index = ix; this.active_index = Some(ix);
cx.notify(); cx.notify();
}, },
)) ))
@ -175,7 +236,9 @@ impl Render for Gallery {
.id("story") .id("story")
.flex_1() .flex_1()
.overflow_y_scroll() .overflow_y_scroll()
.child(active_story), .when_some(active_story, |this, active_story| {
this.child(active_story.clone())
}),
), ),
) )
} }

View file

@ -1859,7 +1859,7 @@ impl Render for TextInput {
.id("suffix") .id("suffix")
.absolute() .absolute()
.gap(gap_x) .gap(gap_x)
.bg(bg) .when(self.appearance, |this| this.bg(bg))
.items_center() .items_center()
.when(suffix.is_none(), |this| this.pr_1()) .when(suffix.is_none(), |this| this.pr_1())
.right_0() .right_0()