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
40#[derive(Props, Clone, PartialEq)]
42pub struct TagProps {
43 #[props(optional)]
45 pub color: Option<TagColor>,
46 #[props(default)]
48 pub closable: bool,
49 #[props(optional)]
51 pub on_close: Option<EventHandler<()>>,
52 #[props(default)]
54 pub checkable: bool,
55 #[props(optional)]
57 pub checked: Option<bool>,
58 #[props(optional)]
60 pub default_checked: Option<bool>,
61 #[props(optional)]
63 pub on_change: Option<EventHandler<bool>>,
64 #[props(optional)]
66 pub class: Option<String>,
67 #[props(optional)]
69 pub style: Option<String>,
70 pub children: Element,
72}
73
74#[component]
76pub fn Tag(props: TagProps) -> Element {
77 let TagProps {
78 color,
79 closable,
80 on_close,
81 checkable,
82 checked,
83 default_checked,
84 on_change,
85 class,
86 style,
87 children,
88 } = props;
89
90 let is_checked_controlled = checked.is_some();
91 let initial_checked = default_checked.unwrap_or(false);
92 let checked_signal: Signal<bool> = use_signal(|| initial_checked);
93 let current_checked = checked.unwrap_or_else(|| *checked_signal.read());
94
95 let mut class_list = vec!["adui-tag".to_string()];
96 if let Some(color_kind) = color {
97 class_list.push(color_kind.as_class().into());
98 }
99 if checkable {
100 class_list.push("adui-tag-checkable".into());
101 if current_checked {
102 class_list.push("adui-tag-checkable-checked".into());
103 }
104 }
105 if let Some(extra) = class {
106 class_list.push(extra);
107 }
108 let class_attr = class_list.join(" ");
109 let style_attr = style.unwrap_or_default();
110
111 let on_close_cb = on_close;
112 let on_change_cb = on_change;
113 let checked_signal_for_click = checked_signal;
114
115 rsx! {
116 span {
117 class: "{class_attr}",
118 style: "{style_attr}",
119 onclick: move |_| {
120 if checkable {
121 let next = !current_checked;
122 if !is_checked_controlled {
123 let mut sig = checked_signal_for_click;
124 sig.set(next);
125 }
126 if let Some(cb) = on_change_cb {
127 cb.call(next);
128 }
129 }
130 },
131 {children}
132 if closable {
133 button {
134 r#type: "button",
135 class: "adui-tag-close",
136 onclick: move |evt| {
137 evt.stop_propagation();
138 if let Some(cb) = on_close_cb {
139 cb.call(());
140 }
141 },
142 Icon { kind: IconKind::Close, size: 12.0 }
143 }
144 }
145 }
146 }
147}