accordion: Fix toggle accordion item. (#749)

Close #740

![CleanShot 2025-03-27 at 11 25
54](https://github.com/user-attachments/assets/7aa2e9ed-a3f2-4683-854a-bc1768912f54)
This commit is contained in:
Jason Lee 2025-03-27 14:22:00 +08:00 committed by GitHub
parent 26b59ea23e
commit aadcf4e1df
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 87 additions and 44 deletions

View file

@ -0,0 +1,35 @@
use gpui::*;
use story::{AccordionStory, Assets};
pub struct Example {
root: Entity<AccordionStory>,
}
impl Example {
pub fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
let root = AccordionStory::view(window, cx);
Self { root }
}
fn view(window: &mut Window, cx: &mut App) -> Entity<Self> {
cx.new(|cx| Self::new(window, cx))
}
}
impl Render for Example {
fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
div().p_4().size_full().child(self.root.clone())
}
}
fn main() {
let app = Application::new().with_assets(Assets);
app.run(move |cx| {
story::init(cx);
cx.activate(true);
story::create_new_window("Accordion Example", Example::view, cx);
});
}

View file

@ -16,6 +16,7 @@ pub struct AccordionStory {
size: Size, size: Size,
bordered: bool, bordered: bool,
disabled: bool, disabled: bool,
multiple: bool,
focus_handle: FocusHandle, focus_handle: FocusHandle,
} }
@ -40,6 +41,7 @@ impl AccordionStory {
open_ixs: Vec::new(), open_ixs: Vec::new(),
size: Size::default(), size: Size::default(),
disabled: false, disabled: false,
multiple: false,
focus_handle: cx.focus_handle(), focus_handle: cx.focus_handle(),
} }
} }
@ -105,6 +107,16 @@ impl Render for AccordionStory {
this.set_size(size, window, cx); this.set_size(size, window, cx);
})), })),
) )
.child(
Checkbox::new("multiple")
.label("Multiple")
.checked(self.multiple)
.on_click(cx.listener(|this, checked, _, cx| {
this.multiple = *checked;
cx.notify();
})),
)
.child( .child(
Checkbox::new("disabled") Checkbox::new("disabled")
.label("Disabled") .label("Disabled")
@ -129,6 +141,7 @@ impl Render for AccordionStory {
.bordered(self.bordered) .bordered(self.bordered)
.with_size(self.size) .with_size(self.size)
.disabled(self.disabled) .disabled(self.disabled)
.multiple(self.multiple)
.item(|this| .item(|this|
this.open(self.open_ixs.contains(&0)) this.open(self.open_ixs.contains(&0))
.icon(IconName::Info) .icon(IconName::Info)

View file

@ -1,4 +1,4 @@
use std::{cell::Cell, rc::Rc, sync::Arc}; use std::{cell::RefCell, collections::HashSet, rc::Rc, sync::Arc};
use gpui::{ use gpui::{
div, prelude::FluentBuilder as _, rems, AnyElement, App, Div, ElementId, div, prelude::FluentBuilder as _, rems, AnyElement, App, Div, ElementId,
@ -80,18 +80,8 @@ impl Sizable for Accordion {
impl RenderOnce for Accordion { impl RenderOnce for Accordion {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement { fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut open_ixs: Vec<usize> = Vec::new(); let open_ixs = Rc::new(RefCell::new(HashSet::new()));
let multiple = self.multiple; let is_multiple = self.multiple;
let state = Rc::new(Cell::new(None));
self.children
.iter()
.enumerate()
.for_each(|(ix, accordion)| {
if accordion.open {
open_ixs.push(ix);
}
});
self.base self.base
.id(self.id) .id(self.id)
@ -100,36 +90,37 @@ impl RenderOnce for Accordion {
.into_iter() .into_iter()
.enumerate() .enumerate()
.map(|(ix, accordion)| { .map(|(ix, accordion)| {
let state = Rc::clone(&state); if accordion.open {
open_ixs.borrow_mut().insert(ix);
}
accordion accordion
.index(ix)
.with_size(self.size) .with_size(self.size)
.bordered(self.bordered) .bordered(self.bordered)
.when(self.disabled, |this| this.disabled(true)) .disabled(self.disabled)
.on_toggle_click(move |_, _, _| { .on_toggle_click({
state.set(Some(ix)); let open_ixs = Rc::clone(&open_ixs);
move |open, _, _| {
let mut open_ixs = open_ixs.borrow_mut();
if *open {
if !is_multiple {
open_ixs.clear();
}
open_ixs.insert(ix);
} else {
open_ixs.remove(&ix);
}
}
}) })
}), }),
) )
.when_some( .when_some(
self.on_toggle_click.filter(|_| !self.disabled), self.on_toggle_click.filter(|_| !self.disabled),
move |this, on_toggle_click| { move |this, on_toggle_click| {
let open_ixs = Rc::clone(&open_ixs);
this.on_click(move |_, window, cx| { this.on_click(move |_, window, cx| {
let mut open_ixs = open_ixs.clone(); let open_ixs: Vec<usize> = open_ixs.borrow().iter().map(|&ix| ix).collect();
if let Some(ix) = state.get() {
if multiple {
if let Some(pos) = open_ixs.iter().position(|&i| i == ix) {
open_ixs.remove(pos);
} else {
open_ixs.push(ix);
}
} else {
let was_open = open_ixs.iter().any(|&i| i == ix);
open_ixs.clear();
if !was_open {
open_ixs.push(ix);
}
}
}
on_toggle_click(&open_ixs, window, cx); on_toggle_click(&open_ixs, window, cx);
}) })
@ -141,6 +132,7 @@ impl RenderOnce for Accordion {
/// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it. /// An Accordion is a vertically stacked list of items, each of which can be expanded to reveal the content associated with it.
#[derive(IntoElement)] #[derive(IntoElement)]
pub struct AccordionItem { pub struct AccordionItem {
index: usize,
icon: Option<Icon>, icon: Option<Icon>,
title: AnyElement, title: AnyElement,
content: AnyElement, content: AnyElement,
@ -154,6 +146,7 @@ pub struct AccordionItem {
impl AccordionItem { impl AccordionItem {
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
index: 0,
icon: None, icon: None,
title: SharedString::default().into_any_element(), title: SharedString::default().into_any_element(),
content: SharedString::default().into_any_element(), content: SharedString::default().into_any_element(),
@ -165,6 +158,11 @@ impl AccordionItem {
} }
} }
fn index(mut self, index: usize) -> Self {
self.index = index;
self
}
pub fn icon(mut self, icon: impl Into<Icon>) -> Self { pub fn icon(mut self, icon: impl Into<Icon>) -> Self {
self.icon = Some(icon.into()); self.icon = Some(icon.into());
self self
@ -230,7 +228,7 @@ impl RenderOnce for AccordionItem {
.text_size(text_size) .text_size(text_size)
.child( .child(
h_flex() h_flex()
.id("accordion-title") .id(self.index)
.justify_between() .justify_between()
.map(|this| match self.size { .map(|this| match self.size {
Size::XSmall => this.py_0().px_1p5(), Size::XSmall => this.py_0().px_1p5(),
@ -274,17 +272,14 @@ impl RenderOnce for AccordionItem {
.xsmall() .xsmall()
.text_color(cx.theme().muted_foreground), .text_color(cx.theme().muted_foreground),
) )
}) .when_some(self.on_toggle_click, |this, on_toggle_click| {
.when_some(
self.on_toggle_click.filter(|_| !self.disabled),
|this, on_toggle_click| {
this.on_click({ this.on_click({
move |_, window, cx| { move |_, window, cx| {
on_toggle_click(&!self.open, window, cx); on_toggle_click(&!self.open, window, cx);
} }
}) })
}, })
), }),
) )
.when(self.open, |this| { .when(self.open, |this| {
this.child( this.child(