dioxus_icon_component/lib.rs
1// Copyright (c) 2026 Xu Shaohua <shaohua@biofan.org>. All rights reserved.
2// Use of this source is governed by Apache-2.0 License
3// that can be found in the LICENSE file.
4
5use dioxus::prelude::*;
6
7/// This trait is used to override `IconProps`.
8///
9/// Implements this trait when adding a new real icon.
10pub trait IconShape: Clone + PartialEq + 'static {
11 /// Returns the SVG child elements (paths, circles, etc.) that define the icon shape.
12 fn child_elements(&self) -> Element;
13
14 /// Default title text for the SVG element.
15 const TITLE: Option<&'static str> = None;
16 /// Default width of the SVG element in pixels.
17 const WIDTH: Option<u32> = None;
18 /// Default height of the SVG element in pixels.
19 const HEIGHT: Option<u32> = None;
20 /// Default fill color of the SVG element.
21 const FILL: Option<&'static str> = None;
22 /// Default stroke color of the SVG element.
23 const STROKE: Option<&'static str> = None;
24 /// Default view box string (e.g., "0 0 24 24").
25 const VIEW_BOX: Option<&'static str> = None;
26}
27
28/// Props for the `Icon` component.
29///
30/// All fields are optional except `icon`. When a field is not provided,
31/// the default value from the associated `IconShape` implementation is used.
32#[derive(Clone, PartialEq, Props)]
33pub struct IconProps<T: IconShape> {
34 /// The icon shape implementation that provides SVG child elements.
35 pub icon: T,
36
37 /// Optional title text rendered as a `<title>` element inside the SVG for accessibility.
38 #[props(default = None)]
39 pub title: Option<&'static str>,
40
41 /// Optional CSS class name(s) applied to the `<svg>` element.
42 #[props(default = None)]
43 pub class: Option<&'static str>,
44
45 /// Optional inline CSS styles applied to the `<svg>` element.
46 #[props(default = None)]
47 pub style: Option<&'static str>,
48
49 /// Optional width of the SVG element in pixels.
50 /// Falls back to `T::WIDTH` if not set.
51 #[props(default = None)]
52 pub width: Option<u32>,
53
54 /// Optional height of the SVG element in pixels.
55 /// Falls back to `T::HEIGHT` if not set.
56 #[props(default = None)]
57 pub height: Option<u32>,
58
59 /// Optional fill color for the SVG element.
60 /// Falls back to `T::FILL`, then to `"currentColor"` if not set.
61 #[props(default = None)]
62 pub fill: Option<&'static str>,
63
64 /// Optional stroke color for the SVG element.
65 /// Falls back to `T::STROKE` if not set.
66 #[props(default = None)]
67 pub stroke: Option<&'static str>,
68
69 /// Optional view box attribute (e.g., `"0 0 24 24"`).
70 /// Falls back to `T::VIEW_BOX`, then to `"0 0 16 16"` if not set.
71 #[props(default = None)]
72 pub view_box: Option<&'static str>,
73
74 /// Optional XML namespace override.
75 /// Defaults to `"http://www.w3.org/2000/svg"` if not set.
76 #[props(default = None)]
77 pub xmlns: Option<&'static str>,
78}
79
80#[allow(non_snake_case)]
81pub fn Icon<T: IconShape>(props: IconProps<T>) -> Element {
82 rsx! {
83 svg {
84 class: props.class,
85 style: props.style,
86 width: if props.width.is_some(){ props.width } else { T::WIDTH },
87 height: if props.height.is_some() { props.height } else { T::HEIGHT },
88 view_box: if props.view_box.is_some() {
89 props.view_box
90 } else if T::VIEW_BOX.is_some() {
91 T::VIEW_BOX
92 } else {
93 "0 0 16 16"
94 },
95 xmlns: props.xmlns.unwrap_or("http://www.w3.org/2000/svg"),
96 fill: if props.fill.is_some() {
97 props.fill
98 } else if T::FILL.is_some() {
99 T::FILL
100 } else {
101 "currentColor"
102 },
103 stroke: props.stroke,
104
105 if let Some(title_text) = props.title {
106 title {{ title_text }}
107 } else if let Some(title_text) = T::TITLE {
108 title {{ title_text }}
109 }
110
111 {props.icon.child_elements()}
112 }
113 }
114}