yew_bootstrap/icons/
mod.rs

1//! # Icons
2//!
3//! All icons are available as a constant, thanks to the build-executed script:
4//! ```
5//! # use yew::html;
6//! # use yew_bootstrap::icons::BI;
7//! let icon = BI::HEART;
8//! # let result =
9//! html!{
10//!     <h1>{"I"} {icon} {BI::GEAR}</h1>
11//! }
12//! # ;
13//! ```
14//!
15//! # Required CSS
16//!
17//! These icons require the inclusion of a CSS file from Bootstrap (`bootstrap-icons.css`). This file can be added by:
18//!
19//! 1. Using [`BIFiles::cdn()`](crate::icons::BIFiles::cdn) (easiest, see below)
20//! 2. Using [`BIFiles::copy()`](crate::icons::BIFiles::copy) - in build-executed code (recommended, see below)
21//! 3. Copying it yourself from the Bootstrap website
22//! 4. Accessing the data via [`BIFiles::FILES`](crate::icons::BIFiles::FILES) and delivering it yourself
23//!
24//! # 1. Quickstart - Using CDN
25//!
26//! Call `BIFiles::cdn()` inside the `html!{}` returned by your application.
27//!
28//! # 2. Recommended - Automatically Copying Files
29//!
30//! This is copies the files to the `dist` directory, which is recommended.
31//!
32//! It is shown in `/examples/icons`.
33//!
34//! A copy of `bootstrap-icons` is included and should change only rarely. `trunk` does not add a hash to generated files,
35//! and thus a change in those files won't be detected by `trunk`.
36//!
37//! 1. `Cargo.toml`
38//!
39//!    Add the build-executed binary.
40//!
41//!    ```toml
42//!    [[bin]]
43//!    name = "copy-bootstrap-icons"
44//!    ```
45//!
46//! 2. `src/bin/copy-bootstrap-icons.rs`
47//!
48//!    Create the program to copy the files.
49//!
50//!    ```no_run
51//!    use std::path::PathBuf;
52//!    use yew_bootstrap::icons::BIFiles;
53//!
54//!    fn main() -> Result<(), std::io::Error> {
55//!        let path = PathBuf::from(
56//!            std::env::var("TRUNK_STAGING_DIR").expect("Environment variable TRUNK_STAGING_DIR"),
57//!        )
58//!        .join(BIFiles::NAME);
59//!        if !path.is_dir() {
60//!            std::fs::create_dir(&path)?;
61//!        }
62//!        BIFiles::copy(&path)
63//!    }
64//!    ```
65//!
66//! 3. `index.html`
67//!
68//!    Set base reference, link the required CSS and specify your WASM program[^1].
69//!
70//!    [^1]: Since we'll be writing a build-executed program, there are now two binaries and trunk needs to know which is your WASM binary.
71//!
72//!    ```html
73//!    <base data-trunk-public-url />
74//!    <link rel="stylesheet" href="bootstrap-icons-v1.11.3/bootstrap-icons.css" />
75//!    <link data-trunk rel="rust" data-bin="name-of-app" />
76//!    ```
77//!
78//! 4. `Trunk.toml`
79//!
80//!    Add a hook to run the build-executed program.
81//!
82//!    ```toml
83//!    [[hooks]]
84//!    stage = "build"
85//!    command = "cargo"
86//!    command_arguments = ["run", "--bin", "copy-bootstrap-icons"]
87//!    ```
88//!
89#![forbid(unsafe_code)]
90#![deny(missing_docs)]
91#![warn(clippy::pedantic)]
92
93use core::hash::{Hash, Hasher};
94use yew::html::{ChildrenRenderer, IntoPropValue};
95use yew::virtual_dom::{VNode, VRaw};
96use yew::{AttrValue, Html, ToHtml};
97
98/// Represents an bootstrap-icon.
99///
100/// This is a transparent wrapper of a `&'static str`, so `Copy` is cheap.
101///
102/// Use [`BI::html`] or the `From` or `IntoPropValue` implementation to generate the actual html.
103///
104/// Search for an icon at <https://icons.getbootstrap.com/#search>.
105// Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
106// Invariant: No two strings at different pointers are equal,
107// Invariant: this is guaranteed due to the fact that it's not possible to create new.
108#[derive(Clone, Copy, Ord, PartialOrd, Eq)]
109#[repr(transparent)]
110pub struct BI(pub(crate) &'static str);
111
112impl BI {
113    /// Returns the `Html` of this icon.
114    #[inline]
115    pub const fn html(self) -> Html {
116        VNode::VRaw(VRaw {
117            html: AttrValue::Static(self.0),
118        })
119    }
120
121    /// Returns the raw html as a str of this icon.
122    #[inline]
123    #[must_use]
124    pub const fn raw_html(self) -> &'static str {
125        self.0
126    }
127}
128
129impl PartialEq for BI {
130    #[inline]
131    fn eq(&self, other: &Self) -> bool {
132        // Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
133        // Invariant: No two strings at different pointers are equal,
134        // Invariant: this is guaranteed due to the fact that it's not possible to create new.
135        // Performance hack: Only check those.
136        self.0.as_ptr() as usize == other.0.as_ptr() as usize && self.0.len() == other.0.len()
137    }
138}
139
140impl Hash for BI {
141    #[inline]
142    fn hash<H: Hasher>(&self, state: &mut H) {
143        // Invariant: All possible strings are different and thus `(ptr,len)` must me different as well.
144        // Invariant: No two strings at different pointers are equal,
145        // Invariant: this is guaranteed due to the fact that it's not possible to create new.
146        // Performance hack: Only hash the ptr to the middle of the string.
147        (self.0.as_ptr() as usize + (self.0.len() >> 1)).hash(state);
148    }
149}
150
151impl From<BI> for Html {
152    #[inline]
153    fn from(value: BI) -> Self {
154        value.html()
155    }
156}
157
158impl From<&BI> for Html {
159    #[allow(clippy::inline_always)]
160    #[inline(always)]
161    fn from(value: &BI) -> Self {
162        Html::from(*value)
163    }
164}
165
166impl IntoPropValue<ChildrenRenderer<VNode>> for BI {
167    #[inline]
168    fn into_prop_value(self) -> ChildrenRenderer<VNode> {
169        self.html().into_prop_value()
170    }
171}
172
173impl IntoPropValue<ChildrenRenderer<VNode>> for &BI {
174    #[allow(clippy::inline_always)]
175    #[inline(always)]
176    fn into_prop_value(self) -> ChildrenRenderer<VNode> {
177        (*self).into_prop_value()
178    }
179}
180
181impl ToHtml for BI {
182    #[allow(clippy::inline_always)]
183    #[inline(always)]
184    fn to_html(&self) -> Html {
185        self.html()
186    }
187}
188
189/// Holds all bootstrap-icons data.
190///
191/// Intended use:
192/// ```
193/// # use yew_bootstrap::icons::BIFiles;
194/// let BIFiles {css, font_woff, font_woff2, license} = BIFiles::FILES;
195/// ```
196/// (That way it will be an error if a file is added/removed.)
197pub struct BIFiles {
198    /// Contents of the file `bootstrap-icons.css`.
199    pub css: &'static str,
200    /// Contents of the file `fonts/bootstrap-icons.woff`.
201    pub font_woff: &'static [u8],
202    /// Contents of the file `fonts/bootstrap-icons.woff2`.
203    pub font_woff2: &'static [u8],
204    /// Contents of the file `fonts/LICENSE`.
205    pub license: &'static str,
206}
207
208/// allows compile time concatenation with other strings to make const 'static str
209macro_rules! version {
210    () => {
211        "v1.11.3"
212    };
213}
214/// provides a resuable path to the bootstrap-icons files that we can make const 'static str with
215macro_rules! path {
216    () => {
217        concat!("../../bootstrap-icons-", version!(), "/")
218    };
219}
220
221impl BIFiles {
222    /// Version of the package.
223    pub const VERSION: &'static str = version!();
224
225    /// Name of the package.
226    pub const NAME: &'static str = concat!("bootstrap-icons-", version!());
227
228    /// All bootstrap-icons files.
229    ///
230    /// Intended use:
231    /// ```
232    /// # use yew_bootstrap::icons::BIFiles;
233    /// let BIFiles {css, font_woff, font_woff2, license} = BIFiles::FILES;
234    /// ```
235    /// (That way it will be an error if a file is added/removed.)
236    pub const FILES: Self = Self {
237        css: include_str!(concat!(path!(), "bootstrap-icons.css")),
238        font_woff: include_bytes!(concat!(path!(), "fonts/bootstrap-icons.woff")),
239        font_woff2: include_bytes!(concat!(path!(), "fonts/bootstrap-icons.woff2")),
240        license: include_str!(concat!(path!(), "fonts/LICENSE")),
241    };
242
243    /// Load the bootstrap-icons files from the official cdn.
244    ///
245    /// Call `BIFiles::cdn()` inside the `html!{}` returned by your application.
246    pub const fn cdn() -> VNode {
247        VNode::VRaw(VRaw {
248            html: AttrValue::Static(concat!(
249                r#"<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@"#,
250                version!(),
251                r#"/font/bootstrap-icons.css">"#
252            )),
253        })
254    }
255
256    /// Copy all bootstrap icons files to the specified directory.
257    ///
258    /// # Errors
259    ///
260    /// Will return an error when there is a problem with creating the directories or writing the files.
261    pub fn copy(to: &std::path::Path) -> Result<(), std::io::Error> {
262        let BIFiles {
263            css,
264            font_woff,
265            font_woff2,
266            license,
267        } = Self::FILES;
268
269        let fonts = to.join("fonts");
270        if !fonts.is_dir() {
271            std::fs::create_dir(&fonts)?;
272        }
273        std::fs::write(to.join("bootstrap-icons.css"), css)?;
274        std::fs::write(fonts.join("bootstrap-icons.woff"), font_woff)?;
275        std::fs::write(fonts.join("bootstrap-icons.woff2"), font_woff2)?;
276        std::fs::write(fonts.join("LICENSE"), license)?;
277
278        Ok(())
279    }
280}
281
282include!(concat!(env!("OUT_DIR"), "/bootstrap_icons_generated.rs"));