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,
bordered: bool,
disabled: bool,
multiple: bool,
focus_handle: FocusHandle,
}
@ -40,6 +41,7 @@ impl AccordionStory {
open_ixs: Vec::new(),
size: Size::default(),
disabled: false,
multiple: false,
focus_handle: cx.focus_handle(),
}
}
@ -105,6 +107,16 @@ impl Render for AccordionStory {
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(
Checkbox::new("disabled")
.label("Disabled")
@ -129,6 +141,7 @@ impl Render for AccordionStory {
.bordered(self.bordered)
.with_size(self.size)
.disabled(self.disabled)
.multiple(self.multiple)
.item(|this|
this.open(self.open_ixs.contains(&0))
.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::{
div, prelude::FluentBuilder as _, rems, AnyElement, App, Div, ElementId,
@ -80,18 +80,8 @@ impl Sizable for Accordion {
impl RenderOnce for Accordion {
fn render(self, _window: &mut Window, _cx: &mut App) -> impl IntoElement {
let mut open_ixs: Vec<usize> = Vec::new();
let 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);
}
});
let open_ixs = Rc::new(RefCell::new(HashSet::new()));
let is_multiple = self.multiple;
self.base
.id(self.id)
@ -100,36 +90,37 @@ impl RenderOnce for Accordion {
.into_iter()
.enumerate()
.map(|(ix, accordion)| {
let state = Rc::clone(&state);
if accordion.open {
open_ixs.borrow_mut().insert(ix);
}
accordion
.index(ix)
.with_size(self.size)
.bordered(self.bordered)
.when(self.disabled, |this| this.disabled(true))
.on_toggle_click(move |_, _, _| {
state.set(Some(ix));
.disabled(self.disabled)
.on_toggle_click({
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(
self.on_toggle_click.filter(|_| !self.disabled),
move |this, on_toggle_click| {
let open_ixs = Rc::clone(&open_ixs);
this.on_click(move |_, window, cx| {
let mut open_ixs = open_ixs.clone();
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);
}
}
}
let open_ixs: Vec<usize> = open_ixs.borrow().iter().map(|&ix| ix).collect();
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.
#[derive(IntoElement)]
pub struct AccordionItem {
index: usize,
icon: Option<Icon>,
title: AnyElement,
content: AnyElement,
@ -154,6 +146,7 @@ pub struct AccordionItem {
impl AccordionItem {
pub fn new() -> Self {
Self {
index: 0,
icon: None,
title: 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 {
self.icon = Some(icon.into());
self
@ -230,7 +228,7 @@ impl RenderOnce for AccordionItem {
.text_size(text_size)
.child(
h_flex()
.id("accordion-title")
.id(self.index)
.justify_between()
.map(|this| match self.size {
Size::XSmall => this.py_0().px_1p5(),
@ -274,17 +272,14 @@ impl RenderOnce for AccordionItem {
.xsmall()
.text_color(cx.theme().muted_foreground),
)
})
.when_some(
self.on_toggle_click.filter(|_| !self.disabled),
|this, on_toggle_click| {
this.on_click({
move |_, window, cx| {
on_toggle_click(&!self.open, window, cx);
}
.when_some(self.on_toggle_click, |this, on_toggle_click| {
this.on_click({
move |_, window, cx| {
on_toggle_click(&!self.open, window, cx);
}
})
})
},
),
}),
)
.when(self.open, |this| {
this.child(