biji_ui/components/accordion/
item.rs

1use std::time::Duration;
2
3use leptos::{
4    context::Provider,
5    ev::{click, focus, keydown},
6    prelude::*,
7};
8use leptos_use::use_event_listener;
9
10use crate::{
11    cn,
12    custom_animated_show::CustomAnimatedShow,
13    items::{Focus, ManageFocus, NavigateItems, Toggle},
14};
15
16use super::context::{ItemContext, RootContext};
17
18#[component]
19pub fn Item(
20    #[prop(default = false)] disabled: bool,
21    #[prop(into, optional)] class: String,
22    children: Children,
23) -> impl IntoView {
24    let root_ctx = expect_context::<RootContext>();
25
26    let index = root_ctx.next_index();
27
28    let item_ctx = ItemContext {
29        index,
30        disabled,
31        allow_loop: root_ctx.allow_loop,
32        ..Default::default()
33    };
34
35    root_ctx.upsert_item(index, item_ctx);
36
37    on_cleanup(move || {
38        root_ctx.remove_item(index);
39    });
40
41    view! {
42        <Provider value={item_ctx}>
43            <div
44                class={class}
45                data-index={item_ctx.index}
46                data-state={move || item_ctx.data_state()}
47                data-disabled={item_ctx.disabled}
48                data-highlighted={move || root_ctx.item_in_focus(item_ctx.index)}
49            >
50                {children()}
51            </div>
52        </Provider>
53    }
54}
55
56#[component]
57pub fn ItemToggle(#[prop(into, optional)] class: String, children: Children) -> impl IntoView {
58    let root_ctx = expect_context::<RootContext>();
59    let item_ctx = expect_context::<ItemContext>();
60
61    let trigger_ref = item_ctx.trigger_ref;
62    view! {
63        <ItemToggleEvents>
64            <button
65                node_ref={trigger_ref}
66                class={class}
67                data-index={item_ctx.index}
68                data-state={move || item_ctx.data_state()}
69                data-disabled={item_ctx.disabled}
70                data-highlighted={move || root_ctx.item_in_focus(item_ctx.index)}
71            >
72                {children()}
73            </button>
74        </ItemToggleEvents>
75    }
76}
77
78#[component]
79pub fn ItemToggleEvents(children: Children) -> impl IntoView {
80    let root_ctx = expect_context::<RootContext>();
81    let item_ctx = expect_context::<ItemContext>();
82
83    let _ = use_event_listener(item_ctx.trigger_ref, click, move |_| {
84        item_ctx.toggle();
85    });
86
87    let _ = use_event_listener(item_ctx.trigger_ref, keydown, move |evt| {
88        let key = evt.key();
89        match key.as_str() {
90            "ArrowDown" => {
91                if let Some(item) = root_ctx.navigate_next_item() {
92                    evt.prevent_default();
93                    item.focus();
94                }
95            }
96            "ArrowUp" => {
97                if let Some(item) = root_ctx.navigate_previous_item() {
98                    evt.prevent_default();
99                    item.focus();
100                }
101            }
102            _ => {}
103        };
104    });
105
106    let _ = use_event_listener(item_ctx.trigger_ref, focus, move |_| {
107        root_ctx.set_focus(Some(item_ctx.index));
108    });
109
110    children()
111}
112
113#[component]
114pub fn ItemContent(
115    children: ChildrenFn,
116    /// Optional CSS class to apply to both show and hide classes
117    #[prop(into, optional)]
118    class: String,
119    /// Optional CSS class to apply if `when == true`
120    #[prop(into, optional)]
121    show_class: String,
122    /// Optional CSS class to apply if `when == false`
123    #[prop(into, optional)]
124    hide_class: String,
125    /// The timeout after which the component will be unmounted if `when == false`
126    #[prop(default = Duration::from_millis(200))]
127    hide_delay: Duration,
128) -> impl IntoView {
129    let ctx = expect_context::<ItemContext>();
130
131    // let children = store_value(children);
132    view! {
133        <CustomAnimatedShow
134            when={ctx.open}
135            show_class={cn!(class, show_class)}
136            hide_class={cn!(class, hide_class)}
137            hide_delay={hide_delay}
138        >
139            {children()}
140        </CustomAnimatedShow>
141    }
142}