icon.rs
⎇
Raw
1//! The `Icon` component: renders a glyph from the central [`crate::icons`]
2//! registry (Material Symbols via Iconify) as inline SVG.
3
4use leptos::prelude::*;
5
6use crate::icons::IconName;
7
8/// Folder-shaped glyphs get the accent color; everything else stays muted.
9fn icon_class(name: IconName) -> &'static str {
10 if name.is_folder() {
11 "ic-folder"
12 } else {
13 "ic-muted"
14 }
15}
16
17/// The icon markup, as a plain function rather than a component.
18///
19/// Prefer this over [`Icon`] inside long lists. A `#[component]` call costs a
20/// reactive owner per instance, which is measurable at a thousand rows; a
21/// plain function returning a view costs nothing extra.
22///
23/// `inner_html` rather than a `NodeRef` with an `on_load` callback, for the
24/// same reason: `on_load` is an effect, so a thousand icons meant a thousand
25/// effects. The registry bodies are raw SVG markup (`<path …/>`) and are
26/// injected as-is: the strings are `const`, vendored from the Iconify API,
27/// and never user input.
28pub fn icon_svg(name: IconName, extra: &str) -> impl IntoView + use<> {
29 let mut cls = String::from("icon ");
30 cls.push_str(icon_class(name));
31 if !extra.is_empty() {
32 cls.push(' ');
33 cls.push_str(extra);
34 }
35 view! {
36 <svg
37 class=cls
38 viewBox="0 0 24 24"
39 fill="currentColor"
40 aria-hidden="true"
41 inner_html=name.body()
42 />
43 }
44}
45
46#[component]
47pub fn Icon(name: IconName, #[prop(optional)] class: Option<String>) -> impl IntoView {
48 icon_svg(name, class.as_deref().unwrap_or(""))
49}
50