Skip to main content

shadow_design/
icon.rs

1use dioxus::prelude::*;
2
3/// Tipo di icona vettoriale geometrica nitida a 1.5px stroke (zero dipendenze).
4#[derive(Clone, Copy, PartialEq, Debug)]
5pub enum IconKind {
6    ChevronRight,
7    ChevronDown,
8    ChevronLeft,
9    ChevronUp,
10    Check,
11    Copy,
12    X,
13    AlertTriangle,
14    AlertCircle,
15    Info,
16    CheckCircle,
17    XCircle,
18    Search,
19    Sparkles,
20    Settings,
21    Code,
22    Layers,
23    Terminal,
24    Palette,
25    Eye,
26    EyeOff,
27    ExternalLink,
28    Sun,
29    Moon,
30    Box,
31    Menu,
32    Spinner,
33}
34
35/// Rappresentazione unificata e fortemente tipizzata per icone SVG in Shadow Design.
36/// Consente di passare sia icone geometriche native (`IconKind`) sia elementi SVG personalizzati.
37#[derive(Clone, PartialEq, Debug)]
38pub enum SvgIcon {
39    /// Icona predefinita dal set geometrico nativo ad alta precisione (1.5px stroke).
40    Kind(IconKind),
41    /// Elemento SVG o markup personalizzato Dioxus.
42    Custom(Element),
43}
44
45impl SvgIcon {
46    /// Crea un'icona dal set nativo `IconKind`.
47    pub fn kind(kind: IconKind) -> Self {
48        Self::Kind(kind)
49    }
50
51    /// Crea un'icona da un elemento SVG personalizzato.
52    pub fn custom(element: Element) -> Self {
53        Self::Custom(element)
54    }
55}
56
57impl From<IconKind> for SvgIcon {
58    fn from(kind: IconKind) -> Self {
59        SvgIcon::Kind(kind)
60    }
61}
62
63impl From<Element> for SvgIcon {
64    fn from(element: Element) -> Self {
65        SvgIcon::Custom(element)
66    }
67}
68
69/// Componente per il rendering polimorfico di `SvgIcon`.
70#[component]
71pub fn RenderSvgIcon(
72    icon: SvgIcon,
73    #[props(default = 16)] size: u32,
74    #[props(default)] class: String,
75) -> Element {
76    match icon {
77        SvgIcon::Kind(kind) => rsx! {
78            Icon { kind, size, class }
79        },
80        SvgIcon::Custom(elem) => rsx! {
81            span {
82                class: "shrink-0 inline-flex items-center justify-center {class}",
83                {elem}
84            }
85        },
86    }
87}
88
89#[component]
90pub fn Icon(
91    kind: IconKind,
92    #[props(default = 16)] size: u32,
93    #[props(default)] class: String,
94) -> Element {
95    let size_str = size.to_string();
96
97    rsx! {
98        svg {
99            xmlns: "http://www.w3.org/2000/svg",
100            width: "{size_str}",
101            height: "{size_str}",
102            view_box: "0 0 24 24",
103            fill: "none",
104            stroke: "currentColor",
105            stroke_width: "1.5",
106            stroke_linecap: "round",
107            stroke_linejoin: "round",
108            class: "shrink-0 {class}",
109            match kind {
110                IconKind::ChevronRight => rsx! {
111                    path { d: "m9 18 6-6-6-6" }
112                },
113                IconKind::ChevronDown => rsx! {
114                    path { d: "m6 9 6 6 6-6" }
115                },
116                IconKind::ChevronLeft => rsx! {
117                    path { d: "m15 18-6-6 6-6" }
118                },
119                IconKind::ChevronUp => rsx! {
120                    path { d: "m18 15-6-6-6 6" }
121                },
122                IconKind::Check => rsx! {
123                    path { d: "M20 6 9 17l-5-5" }
124                },
125                IconKind::Copy => rsx! {
126                    rect { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2" }
127                    path { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2" }
128                },
129                IconKind::X => rsx! {
130                    path { d: "M18 6 6 18" }
131                    path { d: "m6 6 12 12" }
132                },
133                IconKind::AlertTriangle => rsx! {
134                    path { d: "m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z" }
135                    line { x1: "12", x2: "12", y1: "9", y2: "13" }
136                    line { x1: "12", x2: "12.01", y1: "17", y2: "17" }
137                },
138                IconKind::AlertCircle => rsx! {
139                    circle { cx: "12", cy: "12", r: "10" }
140                    line { x1: "12", x2: "12", y1: "8", y2: "12" }
141                    line { x1: "12", x2: "12.01", y1: "16", y2: "16" }
142                },
143                IconKind::Info => rsx! {
144                    circle { cx: "12", cy: "12", r: "10" }
145                    line { x1: "12", x2: "12", y1: "16", y2: "12" }
146                    line { x1: "12", x2: "12.01", y1: "8", y2: "8" }
147                },
148                IconKind::CheckCircle => rsx! {
149                    circle { cx: "12", cy: "12", r: "10" }
150                    path { d: "m9 12 2 2 4-4" }
151                },
152                IconKind::XCircle => rsx! {
153                    circle { cx: "12", cy: "12", r: "10" }
154                    path { d: "m15 9-6 6" }
155                    path { d: "m9 9 6 6" }
156                },
157                IconKind::Search => rsx! {
158                    circle { cx: "11", cy: "11", r: "8" }
159                    path { d: "m21 21-4.3-4.3" }
160                },
161                IconKind::Sparkles => rsx! {
162                    path { d: "m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3Z" }
163                },
164                IconKind::Settings => rsx! {
165                    path { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }
166                    circle { cx: "12", cy: "12", r: "3" }
167                },
168                IconKind::Code => rsx! {
169                    polyline { points: "16 18 22 12 16 6" }
170                    polyline { points: "8 6 2 12 8 18" }
171                },
172                IconKind::Layers => rsx! {
173                    path { d: "m12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.84Z" }
174                    path { d: "m22 12.5-9.42 4.29a2 2 0 0 1-1.16 0L2 12.5" }
175                    path { d: "m22 17.5-9.42 4.29a2 2 0 0 1-1.16 0L2 17.5" }
176                },
177                IconKind::Terminal => rsx! {
178                    polyline { points: "4 17 10 11 4 5" }
179                    line { x1: "12", x2: "20", y1: "19", y2: "19" }
180                },
181                IconKind::Palette => rsx! {
182                    circle { cx: "13.5", cy: "6.5", r: ".5", fill: "currentColor" }
183                    circle { cx: "17.5", cy: "10.5", r: ".5", fill: "currentColor" }
184                    circle { cx: "8.5", cy: "7.5", r: ".5", fill: "currentColor" }
185                    circle { cx: "6.5", cy: "12.5", r: ".5", fill: "currentColor" }
186                    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.125a1.64 1.64 0 0 1 1.668-1.668h1.996c3.051 0 5.555-2.503 5.555-5.554C21.965 6.012 17.461 2 12 2z" }
187                },
188                IconKind::Eye => rsx! {
189                    path { d: "M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" }
190                    circle { cx: "12", cy: "12", r: "3" }
191                },
192                IconKind::EyeOff => rsx! {
193                    path { d: "M9.88 9.88a3 3 0 1 0 4.24 4.24" }
194                    path { d: "M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68" }
195                    path { d: "M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61" }
196                    line { x1: "2", x2: "22", y1: "2", y2: "22" }
197                },
198                IconKind::ExternalLink => rsx! {
199                    path { d: "M15 3h6v6" }
200                    path { d: "M10 14 21 3" }
201                    path { d: "M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" }
202                },
203                IconKind::Sun => rsx! {
204                    circle { cx: "12", cy: "12", r: "4" }
205                    path { d: "M12 2v2" }
206                    path { d: "M12 20v2" }
207                    path { d: "m4.93 4.93 1.41 1.41" }
208                    path { d: "m17.66 17.66 1.41 1.41" }
209                    path { d: "M2 12h2" }
210                    path { d: "M20 12h2" }
211                    path { d: "m6.34 17.66-1.41 1.41" }
212                    path { d: "m19.07 4.93-1.41 1.41" }
213                },
214                IconKind::Moon => rsx! {
215                    path { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" }
216                },
217                IconKind::Box => rsx! {
218                    path { d: "M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z" }
219                    path { d: "m3.3 7 8.7 5 8.7-5" }
220                    path { d: "M12 22V12" }
221                },
222                IconKind::Menu => rsx! {
223                    line { x1: "4", x2: "20", y1: "12", y2: "12" }
224                    line { x1: "4", x2: "20", y1: "6", y2: "6" }
225                    line { x1: "4", x2: "20", y1: "18", y2: "18" }
226                },
227                IconKind::Spinner => rsx! {
228                    path {
229                        class: "animate-spin origin-center",
230                        d: "M21 12a9 9 0 1 1-6.219-8.56"
231                    }
232                },
233            }
234        }
235    }
236}