dioxus_ui_system/atoms/
checkbox.rs1use crate::styles::Style;
6use crate::theme::{use_style, use_theme};
7use dioxus::prelude::*;
8
9#[derive(Props, Clone, PartialEq)]
11pub struct CheckboxProps {
12 #[props(default)]
14 pub checked: bool,
15 #[props(default)]
17 pub onchange: Option<EventHandler<bool>>,
18 #[props(default)]
20 pub disabled: bool,
21 #[props(default)]
23 pub label: Option<String>,
24 #[props(default)]
26 pub style: Option<String>,
27 #[props(default)]
29 pub class: Option<String>,
30}
31
32#[component]
34pub fn Checkbox(props: CheckboxProps) -> Element {
35 let _theme = use_theme();
36 let mut is_checked = use_signal(|| props.checked);
37 let mut is_hovered = use_signal(|| false);
38 let mut is_focused = use_signal(|| false);
39
40 use_effect(move || {
42 is_checked.set(props.checked);
43 });
44
45 let checked = is_checked();
46 let disabled = props.disabled;
47 let cursor_style = if disabled { "not-allowed" } else { "pointer" };
48 let opacity_style = if disabled { "0.5" } else { "1" };
49
50 let checkbox_style = use_style(move |t| {
51 let base = Style::new()
52 .w_px(20)
53 .h_px(20)
54 .rounded(&t.radius, "sm")
55 .border(1, &t.colors.border)
56 .flex()
57 .items_center()
58 .justify_center()
59 .cursor("pointer")
60 .transition("all 150ms ease");
61
62 let styled = if checked {
63 base.bg(&t.colors.primary).border_color(&t.colors.primary)
64 } else {
65 base.bg(&t.colors.background)
66 };
67
68 let styled = if is_hovered() && !disabled && !checked {
69 styled.border_color(&t.colors.primary)
70 } else {
71 styled
72 };
73
74 let styled = if is_focused() && !disabled {
75 Style {
76 box_shadow: Some(format!("0 0 0 2px {}", t.colors.ring.to_rgba())),
77 ..styled
78 }
79 } else {
80 styled
81 };
82
83 let styled = if disabled {
84 styled.opacity(0.5)
85 } else {
86 styled
87 };
88
89 styled.build()
90 });
91
92 let checkmark_style = use_style(|t| {
93 Style::new()
94 .w_px(12)
95 .h_px(12)
96 .text_color(&t.colors.primary_foreground)
97 .build()
98 });
99
100 let handle_click = move |_| {
101 if !disabled {
102 let new_checked = !is_checked();
103 is_checked.set(new_checked);
104 if let Some(handler) = &props.onchange {
105 handler.call(new_checked);
106 }
107 }
108 };
109
110 let checkbox_element = rsx! {
111 button {
112 r#type: "button",
113 role: "checkbox",
114 aria_checked: "{checked}",
115 disabled: disabled,
116 style: "{checkbox_style} {props.style.clone().unwrap_or_default()}",
117 class: "{props.class.clone().unwrap_or_default()}",
118 onclick: handle_click,
119 onmouseenter: move |_| if !disabled { is_hovered.set(true) },
120 onmouseleave: move |_| is_hovered.set(false),
121 onfocus: move |_| is_focused.set(true),
122 onblur: move |_| is_focused.set(false),
123
124 if checked {
125 svg {
127 view_box: "0 0 24 24",
128 fill: "none",
129 stroke: "currentColor",
130 stroke_width: "3",
131 stroke_linecap: "round",
132 stroke_linejoin: "round",
133 style: "{checkmark_style}",
134 polyline { points: "20 6 9 17 4 12" }
135 }
136 }
137 }
138 };
139
140 let label_element = if let Some(label_text) = props.label.clone() {
141 rsx! {
142 label {
143 style: "display: flex; align-items: center; gap: 8px; cursor: {cursor_style}; opacity: {opacity_style};",
144 {checkbox_element}
145 span { "{label_text}" }
146 }
147 }
148 } else {
149 checkbox_element
150 };
151
152 rsx! {
153 {label_element}
154 }
155}