adui_dioxus/components/
tag.rs1use crate::components::icon::{Icon, IconKind};
2use dioxus::prelude::*;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
6pub enum TagColor {
7 Default,
8 Primary,
9 Success,
10 Warning,
11 Error,
12}
13
14impl TagColor {
15 fn as_class(&self) -> &'static str {
16 match self {
17 TagColor::Default => "adui-tag-default",
18 TagColor::Primary => "adui-tag-primary",
19 TagColor::Success => "adui-tag-success",
20 TagColor::Warning => "adui-tag-warning",
21 TagColor::Error => "adui-tag-error",
22 }
23 }
24}
25
26#[cfg(test)]
27mod tests {
28 use super::*;
29
30 #[test]
31 fn tag_color_class_mapping_is_stable() {
32 assert_eq!(TagColor::Default.as_class(), "adui-tag-default");
33 assert_eq!(TagColor::Primary.as_class(), "adui-tag-primary");
34 assert_eq!(TagColor::Success.as_class(), "adui-tag-success");
35 assert_eq!(TagColor::Warning.as_class(), "adui-tag-warning");
36 assert_eq!(TagColor::Error.as_class(), "adui-tag-error");
37 }
38
39 #[test]
40 fn tag_color_all_variants() {
41 let variants = [
42 TagColor::Default,
43 TagColor::Primary,
44 TagColor::Success,
45 TagColor::Warning,
46 TagColor::Error,
47 ];
48 for variant in variants.iter() {
49 let class = variant.as_class();
50 assert!(!class.is_empty());
51 assert!(class.starts_with("adui-tag-"));
52 }
53 }
54
55 #[test]
56 fn tag_color_equality() {
57 assert_eq!(TagColor::Default, TagColor::Default);
58 assert_eq!(TagColor::Primary, TagColor::Primary);
59 assert_ne!(TagColor::Default, TagColor::Primary);
60 }
61
62 #[test]
63 fn tag_color_clone() {
64 let original = TagColor::Success;
65 let cloned = original;
66 assert_eq!(original, cloned);
67 assert_eq!(original.as_class(), cloned.as_class());
68 }
69
70 #[test]
71 fn tag_props_defaults() {
72 }
78
79 #[test]
80 fn tag_color_debug() {
81 let color = TagColor::Warning;
82 let debug_str = format!("{:?}", color);
83 assert!(debug_str.contains("Warning"));
84 }
85}
86
87#[derive(Props, Clone, PartialEq)]
89pub struct TagProps {
90 #[props(optional)]
92 pub color: Option<TagColor>,
93 #[props(default)]
95 pub closable: bool,
96 #[props(optional)]
98 pub on_close: Option<EventHandler<()>>,
99 #[props(default)]
101 pub checkable: bool,
102 #[props(optional)]
104 pub checked: Option<bool>,
105 #[props(optional)]
107 pub default_checked: Option<bool>,
108 #[props(optional)]
110 pub on_change: Option<EventHandler<bool>>,
111 #[props(optional)]
113 pub class: Option<String>,
114 #[props(optional)]
116 pub style: Option<String>,
117 pub children: Element,
119}
120
121#[component]
123pub fn Tag(props: TagProps) -> Element {
124 let TagProps {
125 color,
126 closable,
127 on_close,
128 checkable,
129 checked,
130 default_checked,
131 on_change,
132 class,
133 style,
134 children,
135 } = props;
136
137 let is_checked_controlled = checked.is_some();
138 let initial_checked = default_checked.unwrap_or(false);
139 let checked_signal: Signal<bool> = use_signal(|| initial_checked);
140 let current_checked = checked.unwrap_or_else(|| *checked_signal.read());
141
142 let mut class_list = vec!["adui-tag".to_string()];
143 if let Some(color_kind) = color {
144 class_list.push(color_kind.as_class().into());
145 }
146 if checkable {
147 class_list.push("adui-tag-checkable".into());
148 if current_checked {
149 class_list.push("adui-tag-checkable-checked".into());
150 }
151 }
152 if let Some(extra) = class {
153 class_list.push(extra);
154 }
155 let class_attr = class_list.join(" ");
156 let style_attr = style.unwrap_or_default();
157
158 let on_close_cb = on_close;
159 let on_change_cb = on_change;
160 let checked_signal_for_click = checked_signal;
161
162 rsx! {
163 span {
164 class: "{class_attr}",
165 style: "{style_attr}",
166 onclick: move |_| {
167 if checkable {
168 let next = !current_checked;
169 if !is_checked_controlled {
170 let mut sig = checked_signal_for_click;
171 sig.set(next);
172 }
173 if let Some(cb) = on_change_cb {
174 cb.call(next);
175 }
176 }
177 },
178 {children}
179 if closable {
180 button {
181 r#type: "button",
182 class: "adui-tag-close",
183 onclick: move |evt| {
184 evt.stop_propagation();
185 if let Some(cb) = on_close_cb {
186 cb.call(());
187 }
188 },
189 Icon { kind: IconKind::Close, size: 12.0 }
190 }
191 }
192 }
193 }
194}