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}