/ libs / ui / src / components / checkbox.rs
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  }