radix_leptos_primitives/components/
toggle.rs1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::children::Children;
4use leptos::prelude::*;
5
6#[component]
10pub fn Toggle(
11 #[prop(optional)] class: Option<String>,
12 #[prop(optional)] style: Option<String>,
13 #[prop(optional)] children: Option<Children>,
14 #[prop(optional)] variant: Option<ToggleVariant>,
15 #[prop(optional)] size: Option<ToggleSize>,
16 #[prop(optional)] pressed: Option<bool>,
17 #[prop(optional)] default_pressed: Option<bool>,
18 #[prop(optional)] disabled: Option<bool>,
19 #[prop(optional)] on_pressed_change: Option<Callback<bool>>,
20 #[prop(optional)] on_click: Option<Callback<()>>,
21) -> impl IntoView {
22 let variant = variant.unwrap_or_default();
23 let size = size.unwrap_or_default();
24 let disabled = disabled.unwrap_or(false);
25 let (is_pressed, set_is_pressed) =
26 signal(pressed.unwrap_or_else(|| default_pressed.unwrap_or(false)));
27
28 if let Some(external_pressed) = pressed {
30 Effect::new(move |_| {
31 set_is_pressed.set(external_pressed);
32 });
33 }
34
35 if let Some(on_pressed_change) = on_pressed_change {
37 Effect::new(move |_| {
38 on_pressed_change.run(is_pressed.get());
39 });
40 }
41
42 let class = merge_classes(vec![
43 "toggle",
44 &variant.to_class(),
45 &size.to_class(),
46 class.as_deref().unwrap_or(""),
47 ]);
48
49 let handle_click = move |_| {
50 if !disabled {
51 set_is_pressed.update(|pressed| *pressed = !*pressed);
52 if let Some(on_click) = on_click {
53 on_click.run(());
54 }
55 }
56 };
57
58 let handle_keydown = move |ev: web_sys::KeyboardEvent| {
59 if !disabled && (ev.key() == "Enter" || ev.key() == " ") {
60 ev.prevent_default();
61 set_is_pressed.update(|pressed| *pressed = !*pressed);
62 if let Some(on_click) = on_click {
63 on_click.run(());
64 }
65 }
66 };
67
68 view! {
69 <button
70 class=class
71 style=style
72 disabled=disabled
73 on:click=handle_click
74 on:keydown=handle_keydown
75 aria-pressed=is_pressed.get()
76 type="button"
77 >
78 {children.map(|c| c())}
79 </button>
80 }
81}
82
83#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
85pub enum ToggleVariant {
86 #[default]
87 Default,
88 Outline,
89 Ghost,
90 Destructive,
91}
92
93impl ToggleVariant {
94 pub fn to_class(&self) -> &'static str {
95 match self {
96 ToggleVariant::Default => "variant-default",
97 ToggleVariant::Outline => "variant-outline",
98 ToggleVariant::Ghost => "variant-ghost",
99 ToggleVariant::Destructive => "variant-destructive",
100 }
101 }
102}
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
106pub enum ToggleSize {
107 #[default]
108 Default,
109 Small,
110 Large,
111}
112
113impl ToggleSize {
114 pub fn to_class(&self) -> &'static str {
115 match self {
116 ToggleSize::Default => "size-default",
117 ToggleSize::Small => "size-small",
118 ToggleSize::Large => "size-large",
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::utils::{merge_classes, generate_id};
126 use crate::{ToggleSize, ToggleVariant};
127 use wasm_bindgen_test::*;
128
129 wasm_bindgen_test_configure!(run_in_browser);
130
131 #[test]
133 fn test_toggle_creation() {}
134
135 #[test]
136 fn test_toggle_with_class() {}
137
138 #[test]
139 fn test_toggle_with_style() {}
140
141 #[test]
142 fn test_toggle_default_variant() {}
143
144 #[test]
145 fn test_toggle_outline_variant() {}
146
147 #[test]
148 fn test_toggle_ghost_variant() {}
149
150 #[test]
151 fn test_toggle_destructive_variant() {}
152
153 #[test]
154 fn test_toggle_default_size() {}
155
156 #[test]
157 fn test_toggle_small_size() {}
158
159 #[test]
160 fn test_toggle_large_size() {}
161
162 #[test]
163 fn test_toggle_pressed() {}
164
165 #[test]
166 fn test_toggle_default_pressed() {}
167
168 #[test]
169 fn test_toggledisabled() {}
170
171 #[test]
172 fn test_toggle_on_pressed_change() {}
173
174 #[test]
175 fn test_toggle_on_click() {}
176
177 #[test]
179 fn test_toggle_variant_default() {
180 let variant = ToggleVariant::default();
181 assert_eq!(variant, ToggleVariant::Default);
182 }
183
184 #[test]
185 fn test_toggle_variant_default_class() {
186 let variant = ToggleVariant::Default;
187 assert_eq!(variant.to_class(), "variant-default");
188 }
189
190 #[test]
191 fn test_toggle_variant_outline_class() {
192 let variant = ToggleVariant::Outline;
193 assert_eq!(variant.to_class(), "variant-outline");
194 }
195
196 #[test]
197 fn test_toggle_variant_ghost_class() {
198 let variant = ToggleVariant::Ghost;
199 assert_eq!(variant.to_class(), "variant-ghost");
200 }
201
202 #[test]
203 fn test_toggle_variant_destructive_class() {
204 let variant = ToggleVariant::Destructive;
205 assert_eq!(variant.to_class(), "variant-destructive");
206 }
207
208 #[test]
210 fn test_toggle_size_default() {
211 let size = ToggleSize::default();
212 assert_eq!(size, ToggleSize::Default);
213 }
214
215 #[test]
216 fn test_toggle_size_default_class() {
217 let size = ToggleSize::Default;
218 assert_eq!(size.to_class(), "size-default");
219 }
220
221 #[test]
222 fn test_toggle_size_small_class() {
223 let size = ToggleSize::Small;
224 assert_eq!(size.to_class(), "size-small");
225 }
226
227 #[test]
228 fn test_toggle_size_large_class() {
229 let size = ToggleSize::Large;
230 assert_eq!(size.to_class(), "size-large");
231 }
232
233 #[test]
235 fn test_merge_classes_empty() {
236 let result = merge_classes(Vec::new());
237 assert_eq!(result, "");
238 }
239
240 #[test]
241 fn test_merge_classes_single() {
242 let result = merge_classes(vec!["class1"]);
243 assert_eq!(result, "class1");
244 }
245
246 #[test]
247 fn test_merge_classes_multiple() {
248 let result = merge_classes(vec!["class1", "class2", "class3"]);
249 assert_eq!(result, "class1 class2 class3");
250 }
251
252 #[test]
253 fn test_merge_classes_with_empty() {
254 let result = merge_classes(vec!["class1", "", "class3"]);
255 assert_eq!(result, "class1 class3");
256 }
257
258 #[test]
260 fn test_toggle_property_based() {
261 use proptest::prelude::*;
262 proptest!(|(____class in ".*", __style in ".*")| {
263
264 });
265 }
266}