1use hypersig::html::{El, Node, details, div, summary};
4use hypersig::style::value::{
5 AlignItems, BorderBottomStyle, Cursor, Display, FontWeight, JustifyContent, ListStyleType, px,
6 raw, rem,
7};
8use hypersig::style::{Condition, PseudoClass, Style};
9
10use crate::id::check_name;
11use crate::look::{self, icon};
12use crate::token;
13
14#[derive(Debug)]
37pub struct Accordion<S> {
38 name: Option<&'static str>,
39 items: Vec<(String, Node<S>)>,
40}
41
42impl<S> Accordion<S> {
43 #[must_use]
45 pub fn new() -> Self {
46 Self {
47 name: None,
48 items: Vec::new(),
49 }
50 }
51
52 #[must_use]
63 pub fn exclusive(name: &'static str) -> Self {
64 check_name(name);
65 Self {
66 name: Some(name),
67 items: Vec::new(),
68 }
69 }
70
71 #[must_use]
73 pub fn item(mut self, title: impl Into<String>, content: impl Into<Node<S>>) -> Self {
74 self.items.push((title.into(), content.into()));
75 self
76 }
77
78 #[must_use]
80 pub fn render(self) -> El<S> {
81 let name = self.name;
82 div().children(self.items.into_iter().map(|(title, content)| {
83 let section = details().css(
84 Style::new()
85 .border_bottom_width(px(1))
86 .border_bottom_style(BorderBottomStyle::Solid)
87 .border_bottom_color(token::border().var())
88 .group(),
89 );
90 let section = match name {
91 Some(name) => section.attr("name", name),
92 None => section,
93 };
94 section
95 .child(
96 summary().css(summary_style()).text(title).child(
97 icon(look::icon::CHEVRON_DOWN, rem(1)).css(
98 Style::new()
99 .color(token::muted_foreground().var())
100 .merge(look::transition())
101 .on(
102 Condition::Group(PseudoClass::Open),
103 Style::new().rotate(raw("180deg")),
104 ),
105 ),
106 ),
107 )
108 .child(
109 div()
110 .css(look::small_text().padding_bottom(rem(1)))
111 .child(content),
112 )
113 }))
114 }
115}
116
117impl<S> Default for Accordion<S> {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123impl<S> From<Accordion<S>> for El<S> {
124 fn from(accordion: Accordion<S>) -> Self {
125 accordion.render()
126 }
127}
128
129impl<S> From<Accordion<S>> for Node<S> {
130 fn from(accordion: Accordion<S>) -> Self {
131 accordion.render().into()
132 }
133}
134
135fn summary_style() -> Style {
136 Style::new()
138 .display(Display::Flex)
139 .align_items(AlignItems::Center)
140 .justify_content(JustifyContent::SpaceBetween)
141 .gap(rem(1))
142 .padding_block(rem(1))
143 .list_style_type(ListStyleType::None)
144 .font_weight(FontWeight::Number(500.0))
145 .cursor(Cursor::Pointer)
146 .border_radius(px(6))
147 .merge(look::small_text())
148 .merge(look::focus_ring())
149 .on(
150 PseudoClass::Hover,
151 Style::new()
152 .text_decoration_line(hypersig::style::value::TextDecorationLine::Underline),
153 )
154}