nightshade_api/web/
disclosure.rs1use leptos::prelude::*;
5
6#[component]
10pub fn Disclosure(
11 #[prop(into)] title: String,
12 #[prop(optional)] open: Option<RwSignal<bool>>,
13 #[prop(default = true)] default_open: bool,
14 children: ChildrenFn,
15) -> impl IntoView {
16 let open = open.unwrap_or_else(|| RwSignal::new(default_open));
17 let children = StoredValue::new(children);
18 view! {
19 <div class="nightshade-disclosure">
20 <button
21 class="nightshade-disclosure-header"
22 aria-expanded=move || open.get().to_string()
23 on:click=move |_| open.update(|value| *value = !*value)
24 >
25 <span class="nightshade-disclosure-caret" class:open=move || open.get()>
26 "\u{25b8}"
27 </span>
28 <span class="nightshade-disclosure-title">{title}</span>
29 </button>
30 <Show when=move || open.get() fallback=|| ()>
31 <div class="nightshade-disclosure-body">{children.with_value(|render| render())}</div>
32 </Show>
33 </div>
34 }
35}
36
37#[derive(Clone, Copy)]
38struct AccordionContext(RwSignal<Option<String>>);
39
40#[component]
43pub fn Accordion(
44 #[prop(into, optional)] default_open: Option<String>,
45 children: Children,
46) -> impl IntoView {
47 let active = RwSignal::new(default_open);
48 provide_context(AccordionContext(active));
49 view! { <div class="nightshade-accordion">{children()}</div> }
50}
51
52#[component]
55pub fn AccordionItem(
56 #[prop(into)] id: String,
57 #[prop(into)] title: String,
58 children: ChildrenFn,
59) -> impl IntoView {
60 let context = use_context::<AccordionContext>().map(|context| context.0);
61 let id = StoredValue::new(id);
62 let children = StoredValue::new(children);
63 let is_open = move || {
64 context.is_some_and(|active| active.get().as_deref() == Some(id.get_value().as_str()))
65 };
66 let toggle = move |_| {
67 if let Some(active) = context {
68 let key = id.get_value();
69 active.update(|current| {
70 *current = if current.as_deref() == Some(key.as_str()) {
71 None
72 } else {
73 Some(key)
74 };
75 });
76 }
77 };
78 view! {
79 <div class="nightshade-disclosure">
80 <button
81 class="nightshade-disclosure-header"
82 aria-expanded=move || is_open().to_string()
83 on:click=toggle
84 >
85 <span class="nightshade-disclosure-caret" class:open=is_open>
86 "\u{25b8}"
87 </span>
88 <span class="nightshade-disclosure-title">{title}</span>
89 </button>
90 <Show when=is_open fallback=|| ()>
91 <div class="nightshade-disclosure-body">{children.with_value(|render| render())}</div>
92 </Show>
93 </div>
94 }
95}