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"));