1use crate::styles::Style;
6use crate::theme::tokens::Color;
7use crate::theme::{use_style, use_theme};
8use dioxus::prelude::*;
9
10#[derive(Default, Copy, Clone, PartialEq)]
12pub enum IconSize {
13 ExtraSmall, Small, #[default]
16 Medium, Large, ExtraLarge, }
20
21impl IconSize {
22 pub fn to_px(&self) -> u16 {
23 match self {
24 IconSize::ExtraSmall => 12,
25 IconSize::Small => 16,
26 IconSize::Medium => 20,
27 IconSize::Large => 24,
28 IconSize::ExtraLarge => 32,
29 }
30 }
31}
32
33#[derive(Default, Clone, PartialEq)]
35pub enum IconColor {
36 #[default]
37 Current, Primary,
39 Secondary,
40 Muted,
41 Destructive,
42 Success,
43 Warning,
44 Inverse,
45 Custom(Color),
46}
47
48#[derive(Props, Clone, PartialEq)]
50pub struct IconProps {
51 pub name: String,
53 #[props(default)]
55 pub size: IconSize,
56 #[props(default)]
58 pub color: IconColor,
59 #[props(default)]
61 pub style: Option<String>,
62 #[props(default)]
64 pub class: Option<String>,
65 #[props(default)]
67 pub aria_label: Option<String>,
68 #[props(default)]
70 pub flip_h: bool,
71 #[props(default)]
73 pub flip_v: bool,
74 #[props(default)]
76 pub rotate: u16,
77}
78
79#[component]
98pub fn Icon(props: IconProps) -> Element {
99 let _theme = use_theme();
100
101 let size = props.size.clone();
102 let color = props.color.clone();
103 let flip_h = props.flip_h;
104 let flip_v = props.flip_v;
105 let rotate = props.rotate;
106
107 let style = use_style(move |t| {
109 let base = Style::new()
110 .inline_flex()
111 .items_center()
112 .justify_center()
113 .w_px(size.to_px())
114 .h_px(size.to_px())
115 .transition("color 150ms ease");
116
117 let base = match &color {
119 IconColor::Current => base,
120 IconColor::Primary => base.text_color(&t.colors.primary),
121 IconColor::Secondary => base.text_color(&t.colors.secondary_foreground),
122 IconColor::Muted => base.text_color(&t.colors.muted_foreground),
123 IconColor::Destructive => base.text_color(&t.colors.destructive),
124 IconColor::Success => base.text_color(&t.colors.success),
125 IconColor::Warning => base.text_color(&t.colors.warning),
126 IconColor::Inverse => base.text_color(&t.colors.background),
127 IconColor::Custom(c) => base.text_color(c),
128 };
129
130 let mut transform = String::new();
132
133 if flip_h {
134 transform.push_str("scaleX(-1) ");
135 }
136 if flip_v {
137 transform.push_str("scaleY(-1) ");
138 }
139 if rotate != 0 {
140 transform.push_str(&format!("rotate({}deg)", rotate));
141 }
142
143 if !transform.is_empty() {
144 Style {
145 transform: Some(transform.trim().to_string()),
146 ..base
147 }
148 .build()
149 } else {
150 base.build()
151 }
152 });
153
154 let final_style = if let Some(custom) = &props.style {
156 format!("{} {}", style(), custom)
157 } else {
158 style()
159 };
160
161 let class = props.class.clone().unwrap_or_default();
162 let aria_label = props.aria_label.clone();
163
164 let svg_content = get_icon_svg(&props.name);
166 let px = size.to_px();
167 let view_box = get_icon_viewbox(&props.name);
168
169 rsx! {
170 svg {
171 style: "{final_style}",
172 class: "{class}",
173 width: "{px}",
174 height: "{px}",
175 view_box: "{view_box}",
176 fill: "currentColor",
177 role: if aria_label.is_some() { "img" } else { "presentation" },
178 dangerous_inner_html: "{svg_content}",
179 }
180 }
181}
182
183fn get_icon_svg(name: &str) -> String {
185 match name {
186 "check" => r#"<path d="M20 6L9 17l-5-5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
188 "x" | "close" => r#"<path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
189 "plus" => r#"<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
190 "minus" => r#"<path d="M5 12h14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
191 "arrow-left" => r#"<path d="M19 12H5M12 19l-7-7 7-7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
192 "arrow-right" => r#"<path d="M5 12h14M12 5l7 7-7 7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
193 "arrow-up" => r#"<path d="M12 19V5M5 12l7-7 7 7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
194 "arrow-down" => r#"<path d="M12 5v14M19 12l-7 7-7-7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
195 "chevron-left" => r#"<path d="M15 18l-6-6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
196 "chevron-right" => r#"<path d="M9 18l6-6-6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
197 "chevron-up" => r#"<path d="M18 15l-6-6-6 6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
198 "chevron-down" => r#"<path d="M6 9l6 6 6-6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
199 "menu" => r#"<path d="M3 12h18M3 6h18M3 18h18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
200 "search" => r#"<circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" fill="none"/><path d="M21 21l-4.35-4.35" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
201 "user" => r#"<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="12" cy="7" r="4" stroke="currentColor" stroke-width="2" fill="none"/>"#,
202 "settings" => r#"<circle cx="12" cy="12" r="3" stroke="currentColor" stroke-width="2" fill="none"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-2 2 2 2 0 01-2-2v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83 0 2 2 0 010-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 01-2-2 2 2 0 012-2h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 010-2.83 2 2 0 012.83 0l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 012-2 2 2 0 012 2v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 0 2 2 0 010 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 012 2 2 2 0 01-2 2h-.09a1.65 1.65 0 00-1.51 1z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
203 "home" => r#"<path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><polyline points="9 22 9 12 15 12 15 22" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
204 "bell" => r#"<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M13.73 21a2 2 0 01-3.46 0" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
205 "heart" => r#"<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
206 "star" => r#"<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
207 "trash" => r#"<polyline points="3 6 5 6 21 6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M19 6v14a2 2 0 01-2 2H7a2 2 0 01-2-2V6m3 0V4a2 2 0 012-2h4a2 2 0 012 2v2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
208 "edit" => r#"<path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
209 "copy" => r#"<rect x="9" y="9" width="13" height="13" rx="2" ry="2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
210 "external-link" => r#"<path d="M18 13v6a2 2 0 01-2 2H5a2 2 0 01-2-2V8a2 2 0 012-2h6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><polyline points="15 3 21 3 21 9" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="10" y1="14" x2="21" y2="3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
211 "loading" | "spinner" => r#"<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
212 "info" => r#"<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="16" x2="12" y2="12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="8" x2="12.01" y2="8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
213 "warning" | "alert" => r#"<path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="9" x2="12" y2="13" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="17" x2="12.01" y2="17" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
214 "error" | "alert-circle" => r#"<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="15" y1="9" x2="9" y2="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="9" y1="9" x2="15" y2="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
215 "moon" => r#"<path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
216 "sun" => r#"<circle cx="12" cy="12" r="5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="1" x2="12" y2="3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="21" x2="12" y2="23" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="1" y1="12" x2="3" y2="12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="21" y1="12" x2="23" y2="12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
217 "book" => r#"<path d="M4 19.5A2.5 2.5 0 016.5 17H20" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 014 19.5v-15A2.5 2.5 0 016.5 2z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
219 "layout" => r#"<rect x="3" y="3" width="18" height="18" rx="2" ry="2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="3" y1="9" x2="21" y2="9" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="9" y1="21" x2="9" y2="9" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
220 "sidebar" => r#"<rect x="3" y="3" width="18" height="18" rx="2" ry="2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="9" y1="3" x2="9" y2="21" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
221 "maximize" => r#"<path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
222 "box" => r#"<path d="M21 16V8a2 2 0 00-1-1.73l-7-4a2 2 0 00-2 0l-7 4A2 2 0 003 8v8a2 2 0 001 1.73l7 4a2 2 0 002 0l7-4A2 2 0 0021 16z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><polyline points="3.27 6.96 12 12.01 20.73 6.96" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="22.08" x2="12" y2="12" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
223 "smartphone" => r#"<rect x="5" y="2" width="14" height="20" rx="2" ry="2" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="18" x2="12.01" y2="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
224 "palette" => r#"<circle cx="13.5" cy="6.5" r=".5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="17.5" cy="10.5" r=".5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="8.5" cy="7.5" r=".5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><circle cx="6.5" cy="12.5" r=".5" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><path d="M12 2C6.5 2 2 6.5 2 12s4.5 10 10 10c.926 0 1.648-.746 1.648-1.688 0-.437-.18-.835-.437-1.125-.29-.289-.438-.652-.438-1.042a1.66 1.66 0 011.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.01 17.461 2 12 2z" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
225 "share" => r#"<path d="M4 12v8a2 2 0 002 2h12a2 2 0 002-2v-8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><polyline points="16 6 12 2 8 6" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="12" y1="2" x2="12" y2="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
226 "x-circle" => r#"<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="15" y1="9" x2="9" y2="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><line x1="9" y1="9" x2="15" y2="15" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
227 "play-circle" => r#"<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/><polygon points="10 8 16 12 10 16 10 8" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"/>"#,
228 _ => name,
230 }.to_string()
231}
232
233fn get_icon_viewbox(name: &str) -> &'static str {
235 match name {
236 "check" | "x" | "close" | "plus" | "minus" | "arrow-left" | "arrow-right" | "arrow-up"
237 | "arrow-down" | "chevron-left" | "chevron-right" | "chevron-up" | "chevron-down"
238 | "menu" | "search" | "user" | "settings" | "home" | "bell" | "heart" | "star"
239 | "trash" | "edit" | "copy" | "external-link" | "loading" | "spinner" | "info"
240 | "warning" | "alert" | "error" | "alert-circle" | "moon" | "sun" | "book" | "layout"
241 | "sidebar" | "maximize" | "box" | "smartphone" | "palette" => "0 0 24 24",
242 _ => "0 0 24 24",
244 }
245}
246
247#[derive(Props, Clone, PartialEq)]
249pub struct IconButtonProps {
250 pub icon: String,
251 #[props(default)]
252 pub size: IconSize,
253 #[props(default)]
254 pub color: IconColor,
255 #[props(default)]
256 pub disabled: bool,
257 #[props(default)]
258 pub onclick: Option<EventHandler<MouseEvent>>,
259 #[props(default)]
260 pub aria_label: String,
261 #[props(default)]
262 pub style: Option<String>,
263 #[props(default)]
264 pub class: Option<String>,
265}
266
267#[component]
268pub fn IconButton(props: IconButtonProps) -> Element {
269 let class = format!("icon-button {}", props.class.clone().unwrap_or_default());
270
271 rsx! {
272 button {
273 class: "{class}",
274 style: props.style.clone().unwrap_or_default(),
275 disabled: props.disabled,
276 aria_label: props.aria_label.clone(),
277 onclick: move |e| {
278 if let Some(handler) = &props.onclick {
279 if !props.disabled {
280 handler.call(e);
281 }
282 }
283 },
284 Icon {
285 name: props.icon.clone(),
286 size: props.size.clone(),
287 color: props.color.clone(),
288 }
289 }
290 }
291}