checkbox.rs
1 use crate::use_unique_id; 2 use dioxus::prelude::*; 3 use lucide_dioxus::Check; 4 5 /// Checkbox size options 6 #[derive(Clone, Copy, PartialEq, Eq, Debug, Default)] 7 pub enum CheckboxSize { 8 Small, 9 #[default] 10 Medium, 11 Large, 12 } 13 14 /// Props for the Checkbox component 15 #[derive(Props, Clone, PartialEq)] 16 pub struct CheckboxProps { 17 /// Whether the checkbox is checked 18 #[props(default = Signal::new(false))] 19 pub checked: Signal<bool>, 20 21 /// Callback for when the checkbox is toggled 22 #[props(default)] 23 pub on_checked_change: Option<EventHandler<bool>>, 24 25 /// Whether the checkbox is disabled 26 #[props(default)] 27 pub disabled: bool, 28 29 /// Size of the checkbox 30 #[props(default)] 31 pub size: CheckboxSize, 32 33 /// Optional ID for the checkbox 34 #[props(default)] 35 pub id: Option<String>, 36 37 /// Name attribute for form submission 38 #[props(default)] 39 pub name: Option<String>, 40 41 /// Accessible label for the checkbox 42 #[props(default)] 43 pub aria_label: Option<String>, 44 45 /// Optional children (usually the indicator) 46 #[props(default)] 47 pub children: Element, 48 49 #[props(extends = GlobalAttributes)] 50 pub attributes: Vec<Attribute>, 51 } 52 53 /// A styled checkbox component that can be toggled on or off 54 #[component] 55 pub fn Checkbox(props: CheckboxProps) -> Element { 56 // Generate unique ID if not provided 57 let checkbox_id = use_unique_id(); 58 let id = props.id.clone().unwrap_or(checkbox_id()); 59 60 // Determine size-specific classes 61 let (size_class, icon_size) = match props.size { 62 CheckboxSize::Small => ("h-4 w-4", "h-3 w-3"), 63 CheckboxSize::Medium => ("h-5 w-5", "h-4 w-4"), 64 CheckboxSize::Large => ("h-6 w-6", "h-5 w-5"), 65 }; 66 67 // Build checkbox wrapper classes 68 let checkbox_class = format!( 69 "inline-flex items-center justify-center rounded border-2 transition-colors {} {} {}", 70 size_class, 71 if (props.checked)() { 72 "bg-primary border-primary" 73 } else { 74 "bg-background border-input hover:bg-accent/10" 75 }, 76 if props.disabled { 77 "cursor-not-allowed opacity-50" 78 } else { 79 "cursor-pointer" 80 } 81 ); 82 83 // Handle checkbox change 84 let mut checked = props.checked; 85 let on_change = move |_| { 86 if !props.disabled 87 && let Some(handler) = &props.on_checked_change 88 { 89 let new_state = !checked(); 90 checked.set(new_state); 91 handler.call(new_state); 92 } 93 }; 94 95 rsx! { 96 div { 97 class: checkbox_class, 98 role: "checkbox", 99 "aria-checked": (props.checked)().to_string(), 100 id: id.clone(), 101 onclick: on_change, 102 tabindex: if !props.disabled { "0" } else { "-1" }, 103 104 // Render indicator when checked 105 if (props.checked)() { 106 div { class: format!("flex items-center justify-center {}", icon_size), 107 108 Check { class: "text-primary-foreground" } 109 } 110 } 111 112 // Hidden input for form submission 113 if let Some(name) = &props.name { 114 input { 115 r#type: "checkbox", 116 id: format!("{}-input", id), 117 name: name.clone(), 118 checked: (props.checked)(), 119 disabled: props.disabled, 120 class: "sr-only", 121 } 122 } 123 } 124 } 125 }