1use dioxus::prelude::*;
2
3#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
5pub enum IconKind {
6 Plus,
7 Minus,
8 Check,
9 Close,
10 #[default]
11 Info,
12 Question,
13 ArrowRight,
14 ArrowLeft,
15 ArrowUp,
16 ArrowDown,
17 Search,
18 Copy,
19 Edit,
20 Loading,
21 Eye,
22 EyeInvisible,
23}
24
25#[derive(Props, Clone, PartialEq)]
27pub struct IconProps {
28 #[props(default)]
29 pub kind: IconKind,
30 #[props(default = 20.0)]
31 pub size: f32,
32 #[props(optional)]
33 pub color: Option<String>,
34 #[props(optional)]
35 pub rotate: Option<f32>,
36 #[props(default)]
37 pub spin: bool,
38 #[props(optional)]
39 pub class: Option<String>,
40 #[props(optional)]
41 pub aria_label: Option<String>,
42 #[props(optional)]
43 pub view_box: Option<String>,
44 #[props(optional)]
46 pub custom: Option<Element>,
47}
48
49#[component]
51pub fn Icon(props: IconProps) -> Element {
52 let IconProps {
53 kind,
54 size,
55 color,
56 rotate,
57 spin,
58 class,
59 aria_label,
60 view_box,
61 custom,
62 } = props;
63
64 let def = icon_def(kind);
65 let mut class_list = vec!["adui-icon".to_string()];
66 if spin || matches!(kind, IconKind::Loading) {
67 class_list.push("adui-icon-spin".into());
68 }
69 if let Some(extra) = class.as_ref() {
70 class_list.push(extra.clone());
71 }
72 let class_attr = class_list.join(" ");
73
74 let style = format!(
75 "width:{size}px;height:{size}px;{}",
76 rotate
77 .map(|deg| format!("transform:rotate({deg}deg);"))
78 .unwrap_or_default()
79 );
80
81 let stroke_color = color.clone().unwrap_or_else(|| "currentColor".into());
82
83 let aria_text = aria_label.unwrap_or_else(|| format!("{:?}", kind));
84 let view_box_attr = view_box.unwrap_or_else(|| def.view_box.to_string());
85
86 rsx! {
87 svg {
88 class: "{class_attr}",
89 style: "{style}",
90 width: "{size}",
91 height: "{size}",
92 view_box: "{view_box_attr}",
93 fill: if def.fill { stroke_color.clone() } else { "none".into() },
94 stroke: if def.fill { "none" } else { stroke_color.as_str() },
95 stroke_width: "1.6",
96 stroke_linecap: "round",
97 stroke_linejoin: "round",
98 role: "img",
99 "aria-label": aria_text.clone(),
100 "aria-hidden": if aria_text.is_empty() { "true" } else { "false" },
101 if let Some(content) = custom {
102 {content}
103 } else {
104 {def.paths.iter().map(|d| {
105 let fill = if def.fill { "currentColor" } else { "none" };
106 rsx!(path { d: "{d}", fill: "{fill}" })
107 })}
108 }
109 }
110 }
111}
112
113struct IconDef {
114 view_box: &'static str,
115 fill: bool,
116 paths: &'static [&'static str],
117}
118
119fn icon_def(kind: IconKind) -> IconDef {
120 match kind {
121 IconKind::Plus => IconDef {
122 view_box: "0 0 24 24",
123 fill: false,
124 paths: &["M12 5v14", "M5 12h14"],
125 },
126 IconKind::Minus => IconDef {
127 view_box: "0 0 24 24",
128 fill: false,
129 paths: &["M5 12h14"],
130 },
131 IconKind::Check => IconDef {
132 view_box: "0 0 24 24",
133 fill: false,
134 paths: &["M5 13l4 4 10-10"],
135 },
136 IconKind::Close => IconDef {
137 view_box: "0 0 24 24",
138 fill: false,
139 paths: &["M6 6l12 12", "M6 18L18 6"],
140 },
141 IconKind::Info => IconDef {
142 view_box: "0 0 24 24",
143 fill: false,
144 paths: &[
145 "M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Z",
146 "M12 10v6",
147 "M12 8h.01",
148 ],
149 },
150 IconKind::Question => IconDef {
151 view_box: "0 0 24 24",
152 fill: false,
153 paths: &[
154 "M12 4a8 8 0 1 0 0 16 8 8 0 0 0 0-16Z",
155 "M9.5 9.5a2.5 2.5 0 0 1 5 0c0 1.667-1.5 2-2 3",
156 "M12 16h.01",
157 ],
158 },
159 IconKind::ArrowRight => IconDef {
160 view_box: "0 0 24 24",
161 fill: false,
162 paths: &["M5 12h14", "M13 6l6 6-6 6"],
163 },
164 IconKind::ArrowLeft => IconDef {
165 view_box: "0 0 24 24",
166 fill: false,
167 paths: &["M19 12H5", "M11 6l-6 6 6 6"],
168 },
169 IconKind::ArrowUp => IconDef {
170 view_box: "0 0 24 24",
171 fill: false,
172 paths: &["M12 19V5", "M6 11l6-6 6 6"],
173 },
174 IconKind::ArrowDown => IconDef {
175 view_box: "0 0 24 24",
176 fill: false,
177 paths: &["M12 5v14", "M18 13l-6 6-6-6"],
178 },
179 IconKind::Search => IconDef {
180 view_box: "0 0 24 24",
181 fill: false,
182 paths: &["M11 4a7 7 0 1 0 0 14 7 7 0 0 0 0-14Z", "M21 21l-4.35-4.35"],
183 },
184 IconKind::Copy => IconDef {
185 view_box: "0 0 24 24",
186 fill: false,
187 paths: &[
188 "M9 9V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-4",
189 "M5 9h8a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2Z",
190 ],
191 },
192 IconKind::Edit => IconDef {
193 view_box: "0 0 24 24",
194 fill: false,
195 paths: &[
196 "M4 20h4l10.5-10.5a2.121 2.121 0 0 0-3-3L5 17v3Z",
197 "M14.5 6.5l3 3",
198 ],
199 },
200 IconKind::Loading => IconDef {
201 view_box: "0 0 24 24",
202 fill: false,
203 paths: &[
204 "M12 2v4",
205 "M12 18v4",
206 "M4.93 4.93l2.83 2.83",
207 "M16.24 16.24l2.83 2.83",
208 "M2 12h4",
209 "M18 12h4",
210 "M4.93 19.07l2.83-2.83",
211 "M16.24 7.76l2.83-2.83",
212 ],
213 },
214 IconKind::Eye => IconDef {
215 view_box: "0 0 24 24",
216 fill: false,
217 paths: &[
218 "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8Z",
219 "M12 9a3 3 0 1 0 0 6 3 3 0 0 0 0-6Z",
220 ],
221 },
222 IconKind::EyeInvisible => IconDef {
223 view_box: "0 0 24 24",
224 fill: false,
225 paths: &[
226 "M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94",
227 "M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19",
228 "M14.12 14.12a3 3 0 1 1-4.24-4.24",
229 "M1 1l22 22",
230 ],
231 },
232 }
233}