vertigo_forms/tabs/
mod.rs1use std::rc::Rc;
2use vertigo::{Computed, Css, DomElement, DomNode, Reactive, ToComputed, bind, css, dom};
3
4#[derive(Clone)]
5pub struct Tab<K> {
6 pub key: K,
7 pub name: String,
8 pub render: Rc<dyn Fn(&K) -> DomNode>,
9}
10
11#[derive(Clone)]
12pub struct TabsParams {
13 pub header_css: Css,
14 pub header_item_css: Css,
15 pub header_item_add_css: Css,
16 pub header_active_item_add_css: Css,
17 pub content_css: Css,
18 pub container_css: Css,
19}
20
21impl Default for TabsParams {
22 fn default() -> Self {
23 Self {
24 header_css: css! {"
26 display: flex;
27 flex-wrap: wrap;
28 gap: 10px;
29 margin: 0px;
30 padding: 0px;
31 "},
32 header_item_css: css! {"
33 cursor: pointer;
34 "},
35 header_item_add_css: Css::default(),
36 header_active_item_add_css: Css::default(),
37 content_css: Css::default(),
38 container_css: Css::default(),
39 }
40 }
41}
42
43pub struct Tabs<R: Reactive<K>, K: Clone> {
45 pub current_tab: R,
46 pub tabs: Vec<Tab<K>>,
47 pub params: TabsParams,
48}
49
50impl<R, K> Tabs<R, K>
51where
52 R: Reactive<K> + ToComputed<K> + Clone + 'static,
53 K: Clone + PartialEq + 'static,
54{
55 pub fn into_component(self) -> Self {
56 self
57 }
58
59 pub fn mount(self) -> DomNode {
60 let Self {
61 current_tab,
62 tabs,
63 params,
64 } = self;
65
66 let current_computed = current_tab.to_computed();
67
68 dom! {
69 <div css={¶ms.container_css}>
70 <TabsHeader
71 {¤t_tab}
72 tabs={tabs.clone()}
73 params={params.clone()}
74 />
75 <TabsContent
76 current_tab={current_computed}
77 tabs={tabs}
78 {params}
79 />
80 </div>
81 }
82 }
83}
84
85pub struct TabsHeader<R: Reactive<K>, K: Clone> {
87 pub current_tab: R,
88 pub tabs: Vec<Tab<K>>,
89 pub params: TabsParams,
90}
91
92impl<R, K> TabsHeader<R, K>
93where
94 R: Reactive<K> + ToComputed<K> + Clone + 'static,
95 K: Clone + PartialEq + 'static,
96{
97 pub fn into_component(self) -> Self {
98 self
99 }
100
101 pub fn mount(self) -> DomNode {
102 let Self {
103 current_tab,
104 tabs,
105 params,
106 } = self;
107
108 let header_item_css = params.header_item_css + params.header_item_add_css;
109 let header_active_item_add_css = params.header_active_item_add_css;
110
111 current_tab
113 .to_computed()
114 .render_value(move |current_tab_val| {
115 let header = DomElement::new("ul").css(params.header_css.clone());
116
117 tabs.iter().for_each(|tab| {
118 let on_click = bind!(current_tab, tab | _ | current_tab.set(tab.key.clone()));
119 let header_item_css = if current_tab_val == tab.key {
120 &header_item_css + &header_active_item_add_css
121 } else {
122 header_item_css.clone()
123 };
124 let item_css = css!("display: block;");
125 header.add_child(dom! {
126 <li css={item_css}>
127 <a css={header_item_css} on_click={on_click}>{&tab.name}</a>
128 </li>
129 });
130 });
131
132 header.into()
133 })
134 }
135}
136
137pub struct TabsContent<K: Clone> {
139 pub current_tab: Computed<K>,
140 pub tabs: Vec<Tab<K>>,
141 pub params: TabsParams,
142}
143
144impl<K> TabsContent<K>
145where
146 K: Clone + PartialEq + 'static,
147{
148 pub fn into_component(self) -> Self {
149 self
150 }
151
152 pub fn mount(self) -> DomNode {
153 let Self {
154 current_tab,
155 tabs,
156 params,
157 } = self;
158
159 current_tab.render_value(move |current_tab| {
160 render_tab_content(¤t_tab, ¤t_tab, &tabs, ¶ms)
161 })
162 }
163}
164
165pub struct TabsContentMapped<K: Clone> {
169 pub current_tab: Computed<K>,
170 pub tabs: Vec<Tab<K>>,
171 pub tab_map: Rc<dyn Fn(K) -> K>,
172 pub params: TabsParams,
173}
174
175impl<K> TabsContentMapped<K>
176where
177 K: Clone + PartialEq + 'static,
178{
179 pub fn into_component(self) -> Self {
180 self
181 }
182
183 pub fn mount(self) -> DomNode {
184 let Self {
185 current_tab,
186 tabs,
187 tab_map,
188 params,
189 } = self;
190
191 current_tab.render_value(move |current_tab| {
192 render_tab_content(¤t_tab, &tab_map(current_tab.clone()), &tabs, ¶ms)
193 })
194 }
195}
196
197fn render_tab_content<K: PartialEq + Clone>(
198 current_tab: &K,
199 effective_tab: &K,
200 tabs: &[Tab<K>],
201 params: &TabsParams,
202) -> DomNode {
203 let inner = match tabs.iter().find(|tab| &tab.key == effective_tab).cloned() {
204 Some(tab) => (tab.render)(current_tab),
205 _ => dom! { <p>"Non-existent tab set"</p> },
206 };
207
208 dom! {
209 <div css={params.content_css.clone()}>
210 {inner}
211 </div>
212 }
213}