Skip to main content

shadow_design/
checkbox.rs

1use crate::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, PartialEq, Debug, Default)]
5pub enum CheckboxSize {
6    Sm,
7    #[default]
8    Md,
9    Lg,
10}
11
12impl CheckboxSize {
13    pub fn scss_class(&self) -> &'static str {
14        match self {
15            CheckboxSize::Sm => "sd-checkbox__box--sm",
16            CheckboxSize::Md => "sd-checkbox__box--md",
17            CheckboxSize::Lg => "sd-checkbox__box--lg",
18        }
19    }
20
21    pub fn icon_size(&self) -> u32 {
22        match self {
23            CheckboxSize::Sm => 11,
24            CheckboxSize::Md => 13,
25            CheckboxSize::Lg => 16,
26        }
27    }
28}
29
30#[component]
31pub fn Checkbox(
32    checked: Signal<bool>,
33    #[props(default)] label: Option<String>,
34    #[props(default)] description: Option<String>,
35    #[props(default)] disabled: bool,
36    #[props(default)] error: Option<String>,
37    #[props(default)] indeterminate: bool,
38    #[props(default)] size: CheckboxSize,
39    #[props(default)] glow: Option<bool>,
40    #[props(default)] class: String,
41) -> Element {
42    let is_checked = *checked.read();
43
44    #[cfg(feature = "theme")]
45    let theme_val = crate::theme::use_theme()();
46    #[cfg(feature = "theme")]
47    let active_color_class = match &theme_val.primary_color {
48        crate::theme::PrimaryColor::Violet => "sd-checkbox__box--checked".to_string(),
49        crate::theme::PrimaryColor::Obsidian => "sd-checkbox__box--checked-obsidian".to_string(),
50        crate::theme::PrimaryColor::Crimson => "sd-checkbox__box--checked-crimson".to_string(),
51        crate::theme::PrimaryColor::Cyan => "sd-checkbox__box--checked-cyan".to_string(),
52        crate::theme::PrimaryColor::Emerald => "sd-checkbox__box--checked-emerald".to_string(),
53        crate::theme::PrimaryColor::Custom { .. } => {
54            format!("sd-checkbox__box--checked-{}", theme_val.primary_color.css_prefix())
55        }
56    };
57    #[cfg(feature = "theme")]
58    let glow_class = if is_checked || indeterminate {
59        theme_val.resolve_elevation_class("checkbox", glow)
60    } else {
61        String::new()
62    };
63    #[cfg(feature = "theme")]
64    let base_override = theme_val.resolve_component_base("checkbox").unwrap_or("");
65    #[cfg(feature = "theme")]
66    let class_modifiers = theme_val.resolve_class_modifiers("checkbox", &class);
67
68    #[cfg(not(feature = "theme"))]
69    let base_override = "";
70    #[cfg(not(feature = "theme"))]
71    let class_modifiers = String::new();
72    #[cfg(not(feature = "theme"))]
73    let active_color_class = "sd-checkbox__box--checked".to_string();
74    #[cfg(not(feature = "theme"))]
75    let glow_class = if glow == Some(true) || (glow.is_none() && (is_checked || indeterminate)) {
76        "sd-checkbox__box--glow".to_string()
77    } else {
78        String::new()
79    };
80
81    let checked_class = if is_checked || indeterminate {
82        active_color_class.as_str()
83    } else {
84        ""
85    };
86
87    let error_class = if error.is_some() {
88        "sd-checkbox__box--error"
89    } else {
90        ""
91    };
92
93    let box_size = size.scss_class();
94    let is_disabled = disabled;
95    let disabled_class = if is_disabled {
96        "sd-checkbox--disabled"
97    } else {
98        ""
99    };
100
101    rsx! {
102        div {
103            class: "sd-checkbox {base_override} {disabled_class} {class_modifiers} {class}",
104            onclick: move |_| {
105                if !is_disabled {
106                    let next = !*checked.read();
107                    *checked.write() = next;
108                }
109            },
110            div {
111                class: "sd-checkbox__box {box_size} {checked_class} {glow_class} {error_class}",
112                if indeterminate {
113                    span { style: "display: block; width: 10px; height: 2px; background: currentColor; border-radius: 9999px;" }
114                } else if is_checked {
115                    Icon { kind: IconKind::Check, size: size.icon_size() }
116                }
117            }
118            if label.is_some() || description.is_some() || error.is_some() {
119                div { class: "sd-checkbox__label",
120                    if let Some(lbl) = label {
121                        span { class: "sd-checkbox__title", "{lbl}" }
122                    }
123                    if let Some(desc) = description {
124                        span { class: "sd-checkbox__description", "{desc}" }
125                    }
126                    if let Some(err) = error {
127                        span { class: "sd-checkbox__error-text", "{err}" }
128                    }
129                }
130            }
131        }
132    }
133}