Skip to main content

yew_nav_link/components/
icon.rs

1//! # `NavIcon`
2//!
3//! Icon wrapper for embedding icons in navigation items.
4//! Renders an `<i>` element with `aria-hidden="true"` and a configurable size
5//! class.
6//!
7//! # Example
8//!
9//! ```rust
10//! use yew::prelude::*;
11//! use yew_nav_link::{
12//!     NavItem, NavLink, NavList,
13//!     components::{NavIcon, NavIconSize}
14//! };
15//! use yew_router::prelude::*;
16//!
17//! # #[derive(Clone, PartialEq, Routable)]
18//! # enum Route {
19//! #     #[at("/")]
20//! #     Home,
21//! # }
22//! #[component]
23//! fn Nav() -> Html {
24//!     html! {
25//!         <NavList>
26//!             <NavItem>
27//!                 <NavLink<Route> to={Route::Home}>
28//!                     <NavIcon name="home" size={NavIconSize::Small} />
29//!                     { " Home" }
30//!                 </NavLink<Route>>
31//!             </NavItem>
32//!         </NavList>
33//!     }
34//! }
35//! ```
36//!
37//! # CSS Classes
38//!
39//! | Class | Condition |
40//! |-------|-----------|
41//! | `nav-icon` | Always applied |
42//! | `nav-icon-sm` | Size is `Small` |
43//! | `nav-icon-md` | Size is `Medium` (default) |
44//! | `nav-icon-lg` | Size is `Large` |
45//! | `nav-link-with-icon` | Applied to `NavLinkWithIcon` |
46//!
47//! # Props
48//!
49//! **`NavIcon`:**
50//!
51//! | Prop | Type | Default | Description |
52//! |------|------|---------|-------------|
53//! | `name` | `Option<&'static str>` | `None` | Icon name |
54//! | `size` | `NavIconSize` | `Medium` | Size variant |
55//! | `classes` | `Classes` | — | Additional CSS classes |
56//! | `children` | `Children` | — | Content when `name` is `None` |
57//!
58//! **`NavLinkWithIcon`:**
59//!
60//! | Prop | Type | Default | Description |
61//! |------|------|---------|-------------|
62//! | `icon` | `NavIconSize` | — | Size of the icon (required) |
63//! | `classes` | `Classes` | — | Additional CSS classes |
64//! | `children` | `Children` | — | Content to wrap |
65
66use yew::prelude::*;
67
68/// Properties for the [`NavIcon`] component.
69///
70/// | Prop | Type | Default | Description |
71/// |------|------|---------|-------------|
72/// | `name` | `Option<&'static str>` | `None` | Icon name |
73/// | `size` | `NavIconSize` | `Medium` | Size variant |
74/// | `classes` | `Classes` | — | Additional CSS classes |
75/// | `children` | `Children` | — | Content when `name` is `None` |
76#[derive(Properties, Clone, PartialEq, Debug)]
77pub struct NavIconProps {
78    /// Additional CSS classes applied to the icon.
79    #[prop_or_default]
80    pub classes: Classes,
81
82    /// Optional icon name rendered as text content.
83    #[prop_or_default]
84    pub name: Option<&'static str>,
85
86    /// Size variant of the icon.
87    #[prop_or_default]
88    pub size: NavIconSize,
89
90    /// Content rendered inside the icon when `name` is `None`.
91    #[prop_or_default]
92    pub children: Children
93}
94
95/// Available size variants for [`NavIcon`].
96#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub enum NavIconSize {
98    /// Small icon (`nav-icon-sm`).
99    Small,
100    /// Medium icon, the default size (`nav-icon-md`).
101    #[default]
102    Medium,
103    /// Large icon (`nav-icon-lg`).
104    Large
105}
106
107impl NavIconSize {
108    const fn as_class(self) -> &'static str {
109        match self {
110            Self::Small => "nav-icon-sm",
111            Self::Medium => "nav-icon-md",
112            Self::Large => "nav-icon-lg"
113        }
114    }
115}
116
117/// Icon component for embedding icons within navigation elements.
118///
119/// Renders an `<i>` element with `aria-hidden="true"` and a size class.
120///
121/// # CSS Classes
122///
123/// - `nav-icon` - Always applied
124/// - `nav-icon-sm`, `nav-icon-md`, `nav-icon-lg` - Size variant
125#[function_component]
126pub fn NavIcon(props: &NavIconProps) -> Html {
127    let mut classes = props.classes.clone();
128    classes.push("nav-icon");
129    classes.push(props.size.as_class());
130
131    if let Some(name) = props.name {
132        html! {
133            <i class={classes} aria-hidden="true">
134                { name }
135            </i>
136        }
137    } else {
138        html! {
139            <i class={classes} aria-hidden="true">
140                { for props.children.iter() }
141            </i>
142        }
143    }
144}
145
146/// Properties for the [`NavLinkWithIcon`] component.
147///
148/// | Prop | Type | Default | Description |
149/// |------|------|---------|-------------|
150/// | `icon` | `NavIconSize` | — | Size of the icon (required) |
151/// | `classes` | `Classes` | — | Additional CSS classes |
152/// | `children` | `Children` | — | Content to wrap |
153#[derive(Properties, Clone, PartialEq, Debug)]
154pub struct NavLinkWithIconProps {
155    /// Additional CSS classes applied to the wrapper.
156    #[prop_or_default]
157    pub classes: Classes,
158
159    /// Size of the associated icon.
160    pub icon: NavIconSize,
161
162    /// Content rendered inside the span.
163    pub children: Children
164}
165
166/// Wraps content alongside an icon within a navigation link.
167///
168/// # CSS Classes
169///
170/// - `nav-link-with-icon` - Always applied
171#[function_component]
172pub fn NavLinkWithIcon(props: &NavLinkWithIconProps) -> Html {
173    let mut classes = props.classes.clone();
174    classes.push("nav-link-with-icon");
175
176    html! {
177        <span {classes}>
178            { for props.children.iter() }
179        </span>
180    }
181}
182
183#[cfg(test)]
184mod tests {
185    use super::*;
186
187    #[test]
188    fn nav_icon_props_default() {
189        let props = NavIconProps {
190            classes:  Classes::default(),
191            name:     None,
192            size:     NavIconSize::default(),
193            children: Children::new(vec![])
194        };
195
196        assert!(props.name.is_none());
197        assert_eq!(props.size, NavIconSize::Medium);
198    }
199
200    #[test]
201    fn nav_icon_with_name() {
202        let props = NavIconProps {
203            classes:  Classes::default(),
204            name:     Some("home"),
205            size:     NavIconSize::Small,
206            children: Children::new(vec![])
207        };
208
209        assert_eq!(props.name, Some("home"));
210        assert_eq!(props.size, NavIconSize::Small);
211    }
212
213    #[test]
214    fn nav_icon_size_variants() {
215        assert_eq!(NavIconSize::Small.as_class(), "nav-icon-sm");
216        assert_eq!(NavIconSize::Medium.as_class(), "nav-icon-md");
217        assert_eq!(NavIconSize::Large.as_class(), "nav-icon-lg");
218    }
219
220    #[test]
221    fn nav_icon_default() {
222        assert_eq!(NavIconSize::default(), NavIconSize::Medium);
223    }
224}