vertigo_forms/tabs/
mod.rs1use std::rc::Rc;
2use vertigo::{
3 AttrGroup, Computed, Css, DomNode, Reactive, ToComputed, bind, component, css, dom, dom_element,
4};
5
6#[derive(Clone)]
7pub struct Tab<K> {
8 pub key: K,
9 pub name: String,
10 pub render: Rc<dyn Fn(&K) -> DomNode>,
11}
12
13#[derive(Clone)]
14pub struct TabsParams {
15 pub header_css: Css,
16 pub header_item_css: Css,
17 pub header_item_add_css: Css,
18 pub header_active_item_add_css: Css,
19 pub content_css: Css,
20 pub container_css: Css,
21}
22
23impl Default for TabsParams {
24 fn default() -> Self {
25 Self {
26 header_css: css! {"
28 display: flex;
29 flex-wrap: wrap;
30 gap: 10px;
31 margin: 0px;
32 padding: 0px;
33 "},
34 header_item_css: css! {"
35 cursor: pointer;
36 "},
37 header_item_add_css: Css::default(),
38 header_active_item_add_css: Css::default(),
39 content_css: Css::default(),
40 container_css: Css::default(),
41 }
42 }
43}
44
45#[component]
48pub fn Tabs<R, K>(current_tab: R, tabs: Vec<Tab<K>>, params: TabsParams)
49where
50 R: Reactive<K> + ToComputed<K> + Clone + 'static,
51 K: Clone + PartialEq + 'static,
52{
53 let current_computed = current_tab.to_computed();
54
55 dom! {
56 <div css={¶ms.container_css}>
57 <TabsHeader
58 {¤t_tab}
59 tabs={tabs.clone()}
60 params={params.clone()}
61 />
62 <TabsContent
63 current_tab={current_computed}
64 tabs={tabs}
65 {params}
66 />
67 </div>
68 }
69}
70
71#[component]
73pub fn TabsHeader<R, K>(
74 current_tab: R,
75 tabs: Vec<Tab<K>>,
76 params: TabsParams,
77 h: AttrGroup,
79) where
80 R: Reactive<K> + ToComputed<K> + Clone + 'static,
81 K: Clone + PartialEq + 'static,
82{
83 let header_item_css = params.header_item_css + params.header_item_add_css;
84 let header_active_item_add_css = params.header_active_item_add_css;
85
86 current_tab
87 .to_computed()
88 .render_value(move |current_tab_val| {
89 let header = dom_element! { <ul css={¶ms.header_css} {..h.clone()} /> };
90
91 tabs.iter().for_each(|tab| {
92 let on_click = bind!(current_tab, tab | _ | current_tab.set(tab.key.clone()));
93 let header_item_css = if current_tab_val == tab.key {
94 &header_item_css + &header_active_item_add_css
95 } else {
96 header_item_css.clone()
97 };
98 let item_css = css!("display: block;");
99 header.add_child(dom! {
100 <li css={item_css}>
101 <a css={header_item_css} on_click={on_click}>{&tab.name}</a>
102 </li>
103 });
104 });
105
106 header.into()
107 })
108}
109
110#[component]
112pub fn TabsContent<K>(
113 current_tab: Computed<K>,
114 tabs: Vec<Tab<K>>,
115 params: TabsParams,
116 c: AttrGroup,
118) where
119 K: Clone + PartialEq + 'static,
120{
121 current_tab.render_value(move |current_tab| {
122 render_tab_content(¤t_tab, ¤t_tab, &tabs, ¶ms, &c)
123 })
124}
125
126#[component]
130pub fn TabsContentMapped<K>(
131 current_tab: Computed<K>,
132 tabs: Vec<Tab<K>>,
133 tab_map: Rc<dyn Fn(K) -> K>,
134 params: TabsParams,
135 c: AttrGroup,
137) where
138 K: Clone + PartialEq + 'static,
139{
140 current_tab.render_value(move |current_tab| {
141 render_tab_content(
142 ¤t_tab,
143 &tab_map(current_tab.clone()),
144 &tabs,
145 ¶ms,
146 &c,
147 )
148 })
149}
150
151fn render_tab_content<K: PartialEq + Clone>(
152 current_tab: &K,
153 effective_tab: &K,
154 tabs: &[Tab<K>],
155 params: &TabsParams,
156 c: &AttrGroup,
157) -> DomNode {
158 let inner = match tabs.iter().find(|tab| &tab.key == effective_tab).cloned() {
159 Some(tab) => (tab.render)(current_tab),
160 _ => dom! { <p>"Non-existent tab set"</p> },
161 };
162
163 dom! {
164 <div css={params.content_css.clone()} {..c}>
165 {inner}
166 </div>
167 }
168}