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)]
32pub struct TabsProps {
33 pub active: Signal<usize>,
35 #[props(default)]
37 pub pills: bool,
38 #[props(default)]
40 pub color: Option<Color>,
41 #[props(default)]
43 pub class: String,
44 pub children: Element,
46}
47
48#[component]
49pub fn Tabs(props: TabsProps) -> Element {
50 let style = if props.pills { "nav-pills" } else { "nav-tabs" };
51 let nav_class = if props.class.is_empty() {
52 format!("nav {style}")
53 } else {
54 format!("nav {style} {}", props.class)
55 };
56
57 rsx! {
58 div {
59 ul { class: "{nav_class}", role: "tablist",
60 {props.children}
61 }
62 }
63 }
64}
65
66#[derive(Clone, PartialEq, Props)]
70pub struct TabProps {
71 pub label: String,
73 #[props(default)]
75 pub icon: String,
76 pub index: usize,
78 pub active: Signal<usize>,
80 #[props(default)]
82 pub class: String,
83 pub children: Element,
85}
86
87#[component]
88pub fn Tab(props: TabProps) -> Element {
89 let is_active = *props.active.read() == props.index;
90 let mut active_signal = props.active;
91
92 let btn_class = if is_active {
93 "nav-link active"
94 } else {
95 "nav-link"
96 };
97
98 let pane_class = if is_active {
99 "tab-pane fade show active"
100 } else {
101 "tab-pane fade"
102 };
103
104 let pane_class = if props.class.is_empty() {
105 pane_class.to_string()
106 } else {
107 format!("{pane_class} {}", props.class)
108 };
109
110 let index = props.index;
111
112 rsx! {
113 li { class: "nav-item", role: "presentation",
115 button {
116 class: "{btn_class}",
117 r#type: "button",
118 role: "tab",
119 "aria-selected": if is_active { "true" } else { "false" },
120 onclick: move |_| active_signal.set(index),
121 if !props.icon.is_empty() {
122 i { class: "bi bi-{props.icon} me-1" }
123 }
124 "{props.label}"
125 }
126 }
127 div { class: "{pane_class}", role: "tabpanel",
129 {props.children}
130 }
131 }
132}
133
134#[derive(Clone, PartialEq, Props)]
149pub struct TabListProps {
150 pub active: Signal<usize>,
152 pub tabs: Vec<TabDef>,
154 #[props(default)]
156 pub pills: bool,
157 #[props(default)]
159 pub class: String,
160 #[props(default)]
162 pub content_class: String,
163}
164
165#[component]
166pub fn TabList(props: TabListProps) -> Element {
167 let current = *props.active.read();
168 let mut active_signal = props.active;
169 let style = if props.pills { "nav-pills" } else { "nav-tabs" };
170
171 let nav_class = if props.class.is_empty() {
172 format!("nav {style}")
173 } else {
174 format!("nav {style} {}", props.class)
175 };
176
177 let content_class = if props.content_class.is_empty() {
178 "tab-content".to_string()
179 } else {
180 format!("tab-content {}", props.content_class)
181 };
182
183 rsx! {
184 ul { class: "{nav_class}", role: "tablist",
185 for (i, tab) in props.tabs.iter().enumerate() {
186 li { class: "nav-item", role: "presentation",
187 button {
188 class: if current == i { "nav-link active" } else { "nav-link" },
189 r#type: "button",
190 role: "tab",
191 "aria-selected": if current == i { "true" } else { "false" },
192 onclick: move |_| active_signal.set(i),
193 if let Some(ref icon) = tab.icon {
194 i { class: "bi bi-{icon} me-1" }
195 }
196 "{tab.label}"
197 }
198 }
199 }
200 }
201 div { class: "{content_class}",
202 for (i, tab) in props.tabs.iter().enumerate() {
203 div {
204 class: if current == i { "tab-pane fade show active" } else { "tab-pane fade" },
205 role: "tabpanel",
206 if current == i {
207 {tab.content.clone()}
208 }
209 }
210 }
211 }
212 }
213}