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_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<Entity<StoryContainer>>)>,
active_group_index: usize,
active_index: usize,
active_group_index: Option<usize>,
active_index: Option<usize>,
collapsed: bool,
search_input: Entity<TextInput>,
_subscriptions: Vec<Subscription>,
}
impl Gallery {
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![
(
"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<Self>) -> 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())
}),
),
)
}

View file

@ -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()