dioxus_bootstrap_css/
tabs.rs1use dioxus::prelude::*;
2
3use crate::types::Color;
4
5#[derive(Clone, PartialEq)]
7pub struct TabDef {
8 pub label: String,
10 pub icon: Option<String>,
12 pub content: Element,
14}
15
16#[derive(Clone, PartialEq, Props)]
55pub struct TabsProps {
56 pub active: Signal<usize>,
58 #[props(default)]
60 pub pills: bool,
61 #[props(default)]
63 pub color: Option<Color>,
64 #[props(default)]
66 pub fill: bool,
67 #[props(default)]
69 pub justified: bool,
70 #[props(default)]
72 pub vertical: bool,
73 #[props(default)]
75 pub class: String,
76 pub children: Element,
78}
79
80#[component]
81pub fn Tabs(props: TabsProps) -> Element {
82 let style = if props.pills { "nav-pills" } else { "nav-tabs" };
83 let mut nav_classes = vec![format!("nav {style}")];
84 if props.fill {
85 nav_classes.push("nav-fill".to_string());
86 }
87 if props.justified {
88 nav_classes.push("nav-justified".to_string());
89 }
90 if props.vertical {
91 nav_classes.push("flex-column".to_string());
92 }
93 if !props.class.is_empty() {
94 nav_classes.push(props.class.clone());
95 }
96 let nav_class = nav_classes.join(" ");
97
98 rsx! {
99 div {
100 ul { class: "{nav_class}", role: "tablist",
101 {props.children}
102 }
103 }
104 }
105}
106
107#[derive(Clone, PartialEq, Props)]
111pub struct TabProps {
112 pub label: String,
114 #[props(default)]
116 pub icon: String,
117 pub index: usize,
119 pub active: Signal<usize>,
121 #[props(default)]
123 pub class: String,
124 pub children: Element,
126}
127
128#[component]
129pub fn Tab(props: TabProps) -> Element {
130 let is_active = *props.active.read() == props.index;
131 let mut active_signal = props.active;
132
133 let btn_class = if is_active {
134 "nav-link active"
135 } else {
136 "nav-link"
137 };
138
139 let pane_class = if is_active {
140 "tab-pane fade show active"
141 } else {
142 "tab-pane fade"
143 };
144
145 let pane_class = if props.class.is_empty() {
146 pane_class.to_string()
147 } else {
148 format!("{pane_class} {}", props.class)
149 };
150
151 let index = props.index;
152
153 rsx! {
154 li { class: "nav-item", role: "presentation",
156 button {
157 class: "{btn_class}",
158 r#type: "button",
159 role: "tab",
160 "aria-selected": if is_active { "true" } else { "false" },
161 onclick: move |_| active_signal.set(index),
162 if !props.icon.is_empty() {
163 i { class: "bi bi-{props.icon} me-1" }
164 }
165 "{props.label}"
166 }
167 }
168 div { class: "{pane_class}", role: "tabpanel",
170 {props.children}
171 }
172 }
173}
174
175#[derive(Clone, PartialEq, Props)]
190pub struct TabListProps {
191 pub active: Signal<usize>,
193 pub tabs: Vec<TabDef>,
195 #[props(default)]
197 pub pills: bool,
198 #[props(default)]
200 pub class: String,
201 #[props(default)]
203 pub content_class: String,
204}
205
206#[component]
207pub fn TabList(props: TabListProps) -> Element {
208 let current = *props.active.read();
209 let mut active_signal = props.active;
210 let style = if props.pills { "nav-pills" } else { "nav-tabs" };
211
212 let nav_class = if props.class.is_empty() {
213 format!("nav {style}")
214 } else {
215 format!("nav {style} {}", props.class)
216 };
217
218 let content_class = if props.content_class.is_empty() {
219 "tab-content".to_string()
220 } else {
221 format!("tab-content {}", props.content_class)
222 };
223
224 rsx! {
225 ul { class: "{nav_class}", role: "tablist",
226 for (i, tab) in props.tabs.iter().enumerate() {
227 li { class: "nav-item", role: "presentation",
228 button {
229 class: if current == i { "nav-link active" } else { "nav-link" },
230 r#type: "button",
231 role: "tab",
232 "aria-selected": if current == i { "true" } else { "false" },
233 onclick: move |_| active_signal.set(i),
234 if let Some(ref icon) = tab.icon {
235 i { class: "bi bi-{icon} me-1" }
236 }
237 "{tab.label}"
238 }
239 }
240 }
241 }
242 div { class: "{content_class}",
243 for (i, tab) in props.tabs.iter().enumerate() {
244 div {
245 class: if current == i { "tab-pane fade show active" } else { "tab-pane fade" },
246 role: "tabpanel",
247 if current == i {
248 {tab.content.clone()}
249 }
250 }
251 }
252 }
253 }
254}