Skip to main content

lepticons_picker/
copy.rs

1//! Format definitions and clipboard helpers for copying icon code.
2
3use lepticons::{Glyph, LucideGlyph};
4
5/// Code format used by the picker's "copy as" feature.
6#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
7pub enum IconCopyFormat {
8    /// Bare enum variant: `LucideGlyph::Heart`.
9    #[default]
10    Variant,
11    /// Full Leptos component invocation: `<Icon glyph=LucideGlyph::Heart />`.
12    Component,
13    /// Raw SVG markup with the standard 24x24 viewport.
14    Svg,
15}
16
17impl IconCopyFormat {
18    /// All known formats, in display order.
19    pub const ALL: &'static [Self] = &[Self::Variant, Self::Component, Self::Svg];
20
21    /// Short user-facing label for menus and buttons.
22    pub fn label(self) -> &'static str {
23        match self {
24            Self::Variant => "Variant",
25            Self::Component => "Component",
26            Self::Svg => "SVG",
27        }
28    }
29
30    /// Stable identifier suitable for `<option value=...>` mapping.
31    pub fn id(self) -> &'static str {
32        match self {
33            Self::Variant => "variant",
34            Self::Component => "component",
35            Self::Svg => "svg",
36        }
37    }
38
39    /// Parses a format identifier produced by [`Self::id`].
40    pub fn from_id(s: &str) -> Option<Self> {
41        Self::ALL.iter().copied().find(|f| f.id() == s)
42    }
43
44    /// Renders `icon` as code in this format.
45    pub fn render(self, icon: LucideGlyph) -> String {
46        match self {
47            Self::Variant => format!("LucideGlyph::{}", icon.name()),
48            Self::Component => format!("<Icon glyph=LucideGlyph::{} />", icon.name()),
49            Self::Svg => format!(
50                "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"24\" height=\"24\" \
51                 viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" \
52                 stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">{}</svg>",
53                icon.svg()
54            ),
55        }
56    }
57}
58
59/// Best-effort clipboard write. No-op when there is no browser, no
60/// clipboard API, or the browser denies permission. The browser's
61/// promise is fired and forgotten.
62pub(crate) fn copy_to_clipboard(text: &str) {
63    if let Some(window) = web_sys::window() {
64        let clipboard = window.navigator().clipboard();
65        let _ = clipboard.write_text(text);
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn renders_variant() {
75        assert_eq!(
76            IconCopyFormat::Variant.render(LucideGlyph::Heart),
77            "LucideGlyph::Heart"
78        );
79    }
80
81    #[test]
82    fn renders_component() {
83        assert_eq!(
84            IconCopyFormat::Component.render(LucideGlyph::Heart),
85            "<Icon glyph=LucideGlyph::Heart />"
86        );
87    }
88
89    #[test]
90    fn round_trips_id() {
91        for fmt in IconCopyFormat::ALL.iter().copied() {
92            assert_eq!(IconCopyFormat::from_id(fmt.id()), Some(fmt));
93        }
94        assert_eq!(IconCopyFormat::from_id("nope"), None);
95    }
96}