Skip to main content

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    ///
13    /// # Errors
14    ///
15    /// Returns `Err` if rendering the child elements fails.
16    fn child_elements(&self) -> Element;
17
18    /// Default title text for the SVG element.
19    const TITLE: Option<&'static str> = None;
20    /// Default width of the SVG element in pixels.
21    const WIDTH: Option<u32> = None;
22    /// Default height of the SVG element in pixels.
23    const HEIGHT: Option<u32> = None;
24    /// Default fill color of the SVG element.
25    const FILL: Option<&'static str> = None;
26    /// Default stroke color of the SVG element.
27    const STROKE: Option<&'static str> = None;
28    /// Default view box string (e.g., "0 0 24 24").
29    const VIEW_BOX: Option<&'static str> = None;
30}
31
32/// Props for the `Icon` component.
33///
34/// All fields are optional except `icon`. When a field is not provided,
35/// the default value from the associated `IconShape` implementation is used.
36#[derive(Clone, PartialEq, Eq, Props)]
37pub struct IconProps<T: IconShape> {
38    /// The icon shape implementation that provides SVG child elements.
39    pub icon: T,
40
41    /// Optional title text rendered as a `<title>` element inside the SVG for accessibility.
42    #[props(default = None)]
43    pub title: Option<&'static str>,
44
45    /// Optional CSS class name(s) applied to the `<svg>` element.
46    #[props(default = None)]
47    pub class: Option<&'static str>,
48
49    /// Optional inline CSS styles applied to the `<svg>` element.
50    #[props(default = None)]
51    pub style: Option<&'static str>,
52
53    /// Optional width of the SVG element in pixels.
54    /// Falls back to `T::WIDTH` if not set.
55    #[props(default = None)]
56    pub width: Option<u32>,
57
58    /// Optional height of the SVG element in pixels.
59    /// Falls back to `T::HEIGHT` if not set.
60    #[props(default = None)]
61    pub height: Option<u32>,
62
63    /// Optional fill color for the SVG element.
64    /// Falls back to `T::FILL`, then to `"currentColor"` if not set.
65    #[props(default = None)]
66    pub fill: Option<&'static str>,
67
68    /// Optional stroke color for the SVG element.
69    /// Falls back to `T::STROKE` if not set.
70    #[props(default = None)]
71    pub stroke: Option<&'static str>,
72
73    /// Optional view box attribute (e.g., `"0 0 24 24"`).
74    /// Falls back to `T::VIEW_BOX`, then to `"0 0 16 16"` if not set.
75    #[props(default = None)]
76    pub view_box: Option<&'static str>,
77
78    /// Optional XML namespace override.
79    /// Defaults to `"http://www.w3.org/2000/svg"` if not set.
80    #[props(default = None)]
81    pub xmlns: Option<&'static str>,
82}
83
84/// # Errors
85///
86/// Returns `Err` if rendering the icon fails.
87#[component]
88pub fn Icon<T: IconShape>(props: IconProps<T>) -> Element {
89    rsx! {
90        svg {
91            class: props.class,
92            style: props.style,
93            width: if props.width.is_some(){ props.width } else { T::WIDTH },
94            height: if props.height.is_some() { props.height } else { T::HEIGHT },
95            view_box: if props.view_box.is_some() {
96                props.view_box
97            } else if T::VIEW_BOX.is_some() {
98               T::VIEW_BOX
99            } else {
100                "0 0 16 16"
101            },
102            xmlns: props.xmlns.unwrap_or("http://www.w3.org/2000/svg"),
103            fill: if props.fill.is_some() {
104                props.fill
105            } else if T::FILL.is_some() {
106                T::FILL
107            } else {
108                "currentColor"
109            },
110            stroke: props.stroke,
111
112            if let Some(title_text) = props.title {
113                title {{ title_text }}
114            } else if let Some(title_text) = T::TITLE {
115                title {{ title_text }}
116            }
117
118            {props.icon.child_elements()}
119        }
120    }
121}