├── .gitignore
├── Cargo.toml
├── styled
├── Cargo.toml
├── src
│ └── lib.rs
└── README.md
└── styled_macro
├── Cargo.toml
├── src
└── lib.rs
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /target
2 | Cargo.lock
3 | .cargo
4 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [workspace]
2 | resolver = "3"
3 |
4 | members = ["styled", "styled_macro"]
5 |
--------------------------------------------------------------------------------
/styled/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "styled"
3 | version = "0.3.2"
4 | edition = "2024"
5 | description = "Scoped styles for your Leptos components"
6 | license = "APL-1.0"
7 | keywords = ["leptos", "scoped", "styles", "styling", "CSS"]
8 | categories = ["wasm", "web-programming"]
9 | readme = "README.md"
10 | repository = "https://github.com/eboody/styled"
11 |
12 | [dependencies]
13 | stylist = { version = "0.13.0" }
14 | styled_macro = "0.3"
15 |
--------------------------------------------------------------------------------
/styled_macro/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "styled_macro"
3 | version = "0.3.2"
4 | edition = "2024"
5 | description = "Scoped styles for your Leptos components"
6 | license = "APL-1.0"
7 | keywords = ["leptos", "scoped", "styles", "styling", "CSS"]
8 | categories = ["wasm", "web-programming"]
9 | readme = "README.md"
10 | repository = "https://github.com/eboody/styled"
11 |
12 | [dependencies]
13 | proc-macro-error = "1.0.4"
14 | proc-macro2 = "1.0.95"
15 | quote = "1.0.40"
16 |
17 | [lib]
18 | proc-macro = true
19 |
--------------------------------------------------------------------------------
/styled/src/lib.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | collections::hash_map::DefaultHasher,
3 | hash::{Hash, Hasher},
4 | sync::atomic::{AtomicUsize, Ordering},
5 | };
6 |
7 | pub use stylist::{Result, Style, style};
8 |
9 | #[macro_export]
10 | macro_rules! view {
11 | ($styles:expr, $($tokens:tt)*) => {{
12 |
13 | let style = $styles;
14 |
15 | let $crate::StyleInfo { class_name, style_string } = $crate::get_style_info(style);
16 |
17 | ::leptos::view! {
18 |
19 |
20 | $($tokens)*
21 |
22 | }
23 | }};
24 | }
25 |
26 | /// Global counter as fallback for unique class generation
27 | static COUNTER: AtomicUsize = AtomicUsize::new(0);
28 |
29 | #[derive(Clone)]
30 | pub struct StyleInfo {
31 | pub class_name: String,
32 | pub style_string: String,
33 | }
34 |
35 | fn generate_callsite_hash(file: &str, line: u32) -> u64 {
36 | let mut hasher = DefaultHasher::new();
37 | file.hash(&mut hasher);
38 | line.hash(&mut hasher);
39 | COUNTER.fetch_add(1, Ordering::Relaxed).hash(&mut hasher);
40 | hasher.finish()
41 | }
42 |
43 | pub fn get_style_info(styles_result: Result
60 | #remaining
61 | }
62 | }
63 | }
64 | } else {
65 | quote! {
66 | ::leptos::view! {
67 | #remaining
68 | }
69 | }
70 | };
71 |
72 | expanded.into()
73 | }
74 |
--------------------------------------------------------------------------------
/styled/README.md:
--------------------------------------------------------------------------------
1 | # Styled: Easy Styling for Leptos Components
2 |
3 | If you're looking for an easy way to apply scoped styles to your [Leptos](https://github.com/leptos-rs/leptos) components, Styled is the Leptos macro you need. With Styled, you can apply high-level selectors like button or div to specific components, keeping your markup clean and organized.
4 |
5 | ## Installation
6 |
7 | Use cargo add in your project root
8 |
9 | ```bash
10 | cargo add styled stylist
11 | ```
12 |
13 |
14 | ## Usage
15 |
16 | First create a basic Leptos component. This will serve as the foundation for this little guide.
17 |
18 | ```rust
19 | #[component]
20 | pub fn MyComponent() -> impl IntoView{
21 | view! {
22 | "hello"
23 | }
24 | }
25 | ```
26 |
27 |
28 | Next, import the style macro, powered by an awesome crate called [Stylist](https://github.com/futursolo/stylist-rs), to create your styles.
29 | Just add this to the top of your file.
30 |
31 | ```rust
32 | use styled::style;
33 | ```
34 |
35 |
36 | You can then use the `style` macro to create a Result containing your styles. Let's modify our component:
37 |
38 | ```rust
39 | #[component]
40 | pub fn MyComponent() -> impl IntoView{
41 |
42 | let styles = style!(
43 | div {
44 | background-color: red;
45 | color: white;
46 | }
47 | );
48 |
49 | view! {
50 | "hello"
51 | }
52 | }
53 | ```
54 |
55 | Now, let's apply those styles with our `styled::view!` macro!
56 |
57 | ```rust
58 | #[component]
59 | pub fn MyComponent() -> impl IntoView {
60 |
61 | let styles = style!(
62 | div {
63 | background-color: red;
64 | color: white;
65 | }
66 | );
67 |
68 | styled::view! {
69 | styles,
70 | "This text should be red with white text."
71 | }
72 | }
73 | ```
74 |
75 | Now we can define another component that also uses the div CSS selector but it's styles will only apply to the elements inside of it's enclosing `styled::view!` macro.
76 |
77 | ```rust
78 | #[component]
79 | pub fn AnotherComponent() -> impl IntoView {
80 |
81 | // note were using a plain div selector and it wont clash with MyComponent's div style!
82 | let styles = style!(
83 | div {
84 | background-color: blue;
85 | color: gray;
86 | }
87 | );
88 |
89 | styled::view! {
90 | styles,
91 | "This text should be blue with gray text."
92 | }
93 | }
94 | ```
95 |
96 | ## Longer Example
97 |
98 | ```rust
99 | // /src/components/button.rs
100 |
101 | use crate::theme::get_theme;
102 | use leptos::prelude::*;
103 | use styled::style;
104 |
105 | #[derive(PartialEq)]
106 | pub enum Variant {
107 | PRIMARY,
108 | SECONDARY,
109 | ALERT,
110 | DISABLED,
111 | }
112 |
113 | impl Variant {
114 | pub fn is(&self, variant: &Variant) -> bool {
115 | self == variant
116 | }
117 | }
118 |
119 | struct ButtonColors {
120 | text: String,
121 | background: String,
122 | border: String,
123 | }
124 |
125 | fn get_colors(variant: &Variant) -> ButtonColors {
126 | let theme = get_theme().unwrap();
127 | match variant {
128 | Variant::PRIMARY => ButtonColors {
129 | text: theme.white(),
130 | background: theme.black(),
131 | border: theme.transparent(),
132 | },
133 | Variant::SECONDARY => ButtonColors {
134 | text: theme.black(),
135 | background: theme.white(),
136 | border: theme.gray.lightest(),
137 | },
138 | Variant::ALERT => ButtonColors {
139 | text: theme.white(),
140 | background: theme.red(),
141 | border: theme.transparent(),
142 | },
143 | Variant::DISABLED => ButtonColors {
144 | text: theme.white(),
145 | background: theme.red(),
146 | border: theme.transparent(),
147 | },
148 | }
149 | }
150 |
151 | #[component]
152 | pub fn Button(variant: Variant) -> impl IntoView {
153 | let disabled = variant.is(&Variant::DISABLED);
154 |
155 | let styles = styles(&variant);
156 |
157 | styled::view! {
158 | styles,
159 | "Button"
160 | }
161 | }
162 |
163 | fn styles<'a>(variant: &Variant) -> styled::Result {
164 | let colors = get_colors(variant);
165 |
166 | style!(
167 | button {
168 | color: ${colors.text};
169 | background-color: ${colors.background};
170 | border: 1px solid ${colors.border};
171 | outline: none;
172 | height: 48px;
173 | min-width: 154px;
174 | font-size: 14px;
175 | font-weight: 700;
176 | text-align: center;
177 | box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
178 | position: relative;
179 | box-sizing: border-box;
180 | vertical-align: middle;
181 | text-align: center;
182 | text-overflow: ellipsis;
183 | text-transform: uppercase;
184 | overflow: hidden;
185 | cursor: pointer;
186 | transition: box-shadow 0.2s;
187 | margin: 10px;
188 | }
189 |
190 | & button:active {
191 | transform: scale(0.99);
192 | }
193 |
194 |
195 | & button::-moz-focus-inner {
196 | border: none;
197 | }
198 |
199 | & button::before {
200 | content: "";
201 | position: absolute;
202 | top: 0;
203 | bottom: 0;
204 | left: 0;
205 | right: 0;
206 | background-color: rgb(255, 255, 255);
207 | opacity: 0;
208 | transition: opacity 0.2s;
209 | }
210 |
211 | & button::after {
212 | content: "";
213 | position: absolute;
214 | left: 50%;
215 | top: 50%;
216 | border-radius: 50%;
217 | padding: 50%;
218 | background-color: ${colors.text};
219 | opacity: 0;
220 | transform: translate(-50%, -50%) scale(1);
221 | transition: opacity 1s, transform 0.5s;
222 | }
223 |
224 | & button:hover,
225 | & button:focus {
226 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
227 | }
228 |
229 | & button:hover::before {
230 | opacity: 0.08;
231 | }
232 |
233 | & button:hover:focus::before {
234 | opacity: 0.3;
235 | }
236 |
237 | & button:active {
238 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
239 | }
240 |
241 | & button:active::after {
242 | opacity: 0.32;
243 | transform: translate(-50%, -50%) scale(0);
244 | transition: transform 0s;
245 | }
246 |
247 | & button:disabled {
248 | color: rgba(0, 0, 0, 0.28);
249 | background-color: rgba(0, 0, 0, 0.12);
250 | box-shadow: none;
251 | cursor: initial;
252 | }
253 |
254 | & button:disabled::before {
255 | opacity: 0;
256 | }
257 |
258 | & button:disabled::after {
259 | opacity: 0;
260 | }
261 |
262 | )
263 | }
264 | ```
265 |
266 |
267 | ```rust
268 | // /src/theme/mod.rs
269 | use csscolorparser::Color;
270 |
271 | pub fn get_theme() -> Result {
272 | let theme = Theme {
273 | teal: Colors {
274 | main: Color::from_html("#6FDDDB")?,
275 | darker: Color::from_html("#2BB4B2")?,
276 | lighter: Color::from_html("#7EE1DF")?,
277 | lightest: Color::from_html("#B2EDEC")?,
278 | },
279 | pink: Colors {
280 | main: Color::from_html("#E93EF5")?,
281 | darker: Color::from_html("#C70BD4")?,
282 | lighter: Color::from_html("#F5A4FA")?,
283 | lightest: Color::from_html("#FCE1FD")?,
284 | },
285 | green: Colors {
286 | main: Color::from_html("#54D072")?,
287 | darker: Color::from_html("#30AF4F")?,
288 | lighter: Color::from_html("#82DD98")?,
289 | lightest: Color::from_html("#B4EAC1")?,
290 | },
291 | purple: Colors {
292 | main: Color::from_html("#8C18FB")?,
293 | darker: Color::from_html("#7204DB")?,
294 | lighter: Color::from_html("#B162FC")?,
295 | lightest: Color::from_html("#D0A1FD")?,
296 | },
297 | yellow: Colors {
298 | main: Color::from_html("#E1E862")?,
299 | darker: Color::from_html("#BAC31D")?,
300 | lighter: Color::from_html("#EFF3AC")?,
301 | lightest: Color::from_html("#FAFBE3")?,
302 | },
303 | gray: Colors {
304 | main: Color::from_html("#4a4a4a")?,
305 | darker: Color::from_html("#3d3d3d")?,
306 | lighter: Color::from_html("#939393")?,
307 | lightest: Color::from_html("#c4c4c4")?,
308 | },
309 | red: Color::from_html("#FF5854")?,
310 | black: Color::from_html("#000000")?,
311 | white: Color::from_html("#FFFFFF")?,
312 | transparent: Color::from_html("transparent")?,
313 | };
314 |
315 | Ok(theme)
316 | }
317 |
318 | pub struct Theme {
319 | pub teal: Colors,
320 | pub pink: Colors,
321 | pub green: Colors,
322 | pub purple: Colors,
323 | pub yellow: Colors,
324 | pub gray: Colors,
325 | pub red: Color,
326 | pub black: Color,
327 | pub white: Color,
328 | pub transparent: Color,
329 | }
330 |
331 | pub struct Colors {
332 | pub main: Color,
333 | pub darker: Color,
334 | pub lighter: Color,
335 | pub lightest: Color,
336 | }
337 |
338 | impl Colors {
339 | pub fn main(&self) -> String {
340 | self.main.to_hex_string()
341 | }
342 | pub fn darker(&self) -> String {
343 | self.darker.to_hex_string()
344 | }
345 | pub fn lighter(&self) -> String {
346 | self.lighter.to_hex_string()
347 | }
348 | pub fn lightest(&self) -> String {
349 | self.lightest.to_hex_string()
350 | }
351 | }
352 |
353 | impl Theme {
354 | pub fn red(&self) -> String {
355 | self.red.to_hex_string()
356 | }
357 | pub fn black(&self) -> String {
358 | self.black.to_hex_string()
359 | }
360 | pub fn white(&self) -> String {
361 | self.white.to_hex_string()
362 | }
363 | pub fn transparent(&self) -> String {
364 | self.transparent.to_hex_string()
365 | }
366 | }
367 | ```
368 |
369 |
370 | ```rust
371 | // /src/app.rs
372 |
373 | #[component]
374 | fn HomePage() -> impl IntoView {
375 | // note that this is the default view macro
376 | view! {
377 |
378 |
379 |
380 | }
381 | }
382 | ```
383 |
--------------------------------------------------------------------------------
/styled_macro/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Styled: Easy Styling for Leptos Components
3 |
4 | If you're looking for an easy way to apply scoped styles to your [Leptos](https://github.com/leptos-rs/leptos) components, Styled is the Leptos macro you need. With Styled, you can apply high-level selectors like button or div to specific components, keeping your markup clean and organized.
5 |
6 | ## Installation
7 |
8 | Use cargo add in your project root
9 |
10 | ```bash
11 | cargo add styled stylist
12 | ```
13 |
14 |
15 | ## Usage
16 |
17 | First create a basic Leptos component. This will serve as the foundation for this little guide.
18 |
19 | ```rust
20 | #[component]
21 | pub fn MyComponent() -> impl IntoView{
22 | view! {
23 | "hello"
24 | }
25 | }
26 | ```
27 |
28 |
29 | Next, import the style macro, powered by an awesome crate called [Stylist](https://github.com/futursolo/stylist-rs), to create your styles.
30 | Just add this to the top of your file.
31 |
32 | ```rust
33 | use styled::style;
34 | ```
35 |
36 |
37 | You can then use the `style` macro to create a Result containing your styles. Let's modify our component:
38 |
39 | ```rust
40 | #[component]
41 | pub fn MyComponent() -> impl IntoView{
42 |
43 | let styles = style!(
44 | div {
45 | background-color: red;
46 | color: white;
47 | }
48 | );
49 |
50 | view! {
51 | "hello"
52 | }
53 | }
54 | ```
55 |
56 | Now, let's apply those styles with our `styled::view!` macro!
57 |
58 | ```rust
59 | #[component]
60 | pub fn MyComponent() -> impl IntoView {
61 |
62 | let styles = style!(
63 | div {
64 | background-color: red;
65 | color: white;
66 | }
67 | );
68 |
69 | styled::view! {
70 | styles,
71 | "This text should be red with white text."
72 | }
73 | }
74 | ```
75 |
76 | Now we can define another component that also uses the div CSS selector but it's styles will only apply to the elements inside of it's enclosing `styled::view!` macro.
77 |
78 | ```rust
79 | #[component]
80 | pub fn AnotherComponent() -> impl IntoView {
81 |
82 | // note were using a plain div selector and it wont clash with MyComponent's div style!
83 | let styles = style!(
84 | div {
85 | background-color: blue;
86 | color: gray;
87 | }
88 | );
89 |
90 | styled::view! {
91 | styles,
92 | "This text should be blue with gray text."
93 | }
94 | }
95 | ```
96 |
97 | ## Longer Example
98 |
99 | ```rust
100 | // /src/components/button.rs
101 |
102 | use crate::theme::get_theme;
103 | use leptos::prelude::*;
104 | use styled::style;
105 |
106 | #[derive(PartialEq)]
107 | pub enum Variant {
108 | PRIMARY,
109 | SECONDARY,
110 | ALERT,
111 | DISABLED,
112 | }
113 |
114 | impl Variant {
115 | pub fn is(&self, variant: &Variant) -> bool {
116 | self == variant
117 | }
118 | }
119 |
120 | struct ButtonColors {
121 | text: String,
122 | background: String,
123 | border: String,
124 | }
125 |
126 | fn get_colors(variant: &Variant) -> ButtonColors {
127 | let theme = get_theme().unwrap();
128 | match variant {
129 | Variant::PRIMARY => ButtonColors {
130 | text: theme.white(),
131 | background: theme.black(),
132 | border: theme.transparent(),
133 | },
134 | Variant::SECONDARY => ButtonColors {
135 | text: theme.black(),
136 | background: theme.white(),
137 | border: theme.gray.lightest(),
138 | },
139 | Variant::ALERT => ButtonColors {
140 | text: theme.white(),
141 | background: theme.red(),
142 | border: theme.transparent(),
143 | },
144 | Variant::DISABLED => ButtonColors {
145 | text: theme.white(),
146 | background: theme.red(),
147 | border: theme.transparent(),
148 | },
149 | }
150 | }
151 |
152 | #[component]
153 | pub fn Button(variant: Variant) -> impl IntoView {
154 | let disabled = variant.is(&Variant::DISABLED);
155 |
156 | let styles = styles(&variant);
157 |
158 | styled::view! {
159 | styles,
160 | "Button"
161 | }
162 | }
163 |
164 | fn styles<'a>(variant: &Variant) -> styled::Result {
165 | let colors = get_colors(variant);
166 |
167 | style!(
168 | button {
169 | color: ${colors.text};
170 | background-color: ${colors.background};
171 | border: 1px solid ${colors.border};
172 | outline: none;
173 | height: 48px;
174 | min-width: 154px;
175 | font-size: 14px;
176 | font-weight: 700;
177 | text-align: center;
178 | box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
179 | position: relative;
180 | box-sizing: border-box;
181 | vertical-align: middle;
182 | text-align: center;
183 | text-overflow: ellipsis;
184 | text-transform: uppercase;
185 | overflow: hidden;
186 | cursor: pointer;
187 | transition: box-shadow 0.2s;
188 | margin: 10px;
189 | }
190 |
191 | & button:active {
192 | transform: scale(0.99);
193 | }
194 |
195 |
196 | & button::-moz-focus-inner {
197 | border: none;
198 | }
199 |
200 | & button::before {
201 | content: "";
202 | position: absolute;
203 | top: 0;
204 | bottom: 0;
205 | left: 0;
206 | right: 0;
207 | background-color: rgb(255, 255, 255);
208 | opacity: 0;
209 | transition: opacity 0.2s;
210 | }
211 |
212 | & button::after {
213 | content: "";
214 | position: absolute;
215 | left: 50%;
216 | top: 50%;
217 | border-radius: 50%;
218 | padding: 50%;
219 | background-color: ${colors.text};
220 | opacity: 0;
221 | transform: translate(-50%, -50%) scale(1);
222 | transition: opacity 1s, transform 0.5s;
223 | }
224 |
225 | & button:hover,
226 | & button:focus {
227 | box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.2), 0 4px 5px 0 rgba(0, 0, 0, 0.14), 0 1px 10px 0 rgba(0, 0, 0, 0.12);
228 | }
229 |
230 | & button:hover::before {
231 | opacity: 0.08;
232 | }
233 |
234 | & button:hover:focus::before {
235 | opacity: 0.3;
236 | }
237 |
238 | & button:active {
239 | box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
240 | }
241 |
242 | & button:active::after {
243 | opacity: 0.32;
244 | transform: translate(-50%, -50%) scale(0);
245 | transition: transform 0s;
246 | }
247 |
248 | & button:disabled {
249 | color: rgba(0, 0, 0, 0.28);
250 | background-color: rgba(0, 0, 0, 0.12);
251 | box-shadow: none;
252 | cursor: initial;
253 | }
254 |
255 | & button:disabled::before {
256 | opacity: 0;
257 | }
258 |
259 | & button:disabled::after {
260 | opacity: 0;
261 | }
262 |
263 | )
264 | }
265 | ```
266 |
267 |
268 | ```rust
269 | // /src/theme/mod.rs
270 | use csscolorparser::Color;
271 |
272 | pub fn get_theme() -> Result {
273 | let theme = Theme {
274 | teal: Colors {
275 | main: Color::from_html("#6FDDDB")?,
276 | darker: Color::from_html("#2BB4B2")?,
277 | lighter: Color::from_html("#7EE1DF")?,
278 | lightest: Color::from_html("#B2EDEC")?,
279 | },
280 | pink: Colors {
281 | main: Color::from_html("#E93EF5")?,
282 | darker: Color::from_html("#C70BD4")?,
283 | lighter: Color::from_html("#F5A4FA")?,
284 | lightest: Color::from_html("#FCE1FD")?,
285 | },
286 | green: Colors {
287 | main: Color::from_html("#54D072")?,
288 | darker: Color::from_html("#30AF4F")?,
289 | lighter: Color::from_html("#82DD98")?,
290 | lightest: Color::from_html("#B4EAC1")?,
291 | },
292 | purple: Colors {
293 | main: Color::from_html("#8C18FB")?,
294 | darker: Color::from_html("#7204DB")?,
295 | lighter: Color::from_html("#B162FC")?,
296 | lightest: Color::from_html("#D0A1FD")?,
297 | },
298 | yellow: Colors {
299 | main: Color::from_html("#E1E862")?,
300 | darker: Color::from_html("#BAC31D")?,
301 | lighter: Color::from_html("#EFF3AC")?,
302 | lightest: Color::from_html("#FAFBE3")?,
303 | },
304 | gray: Colors {
305 | main: Color::from_html("#4a4a4a")?,
306 | darker: Color::from_html("#3d3d3d")?,
307 | lighter: Color::from_html("#939393")?,
308 | lightest: Color::from_html("#c4c4c4")?,
309 | },
310 | red: Color::from_html("#FF5854")?,
311 | black: Color::from_html("#000000")?,
312 | white: Color::from_html("#FFFFFF")?,
313 | transparent: Color::from_html("transparent")?,
314 | };
315 |
316 | Ok(theme)
317 | }
318 |
319 | pub struct Theme {
320 | pub teal: Colors,
321 | pub pink: Colors,
322 | pub green: Colors,
323 | pub purple: Colors,
324 | pub yellow: Colors,
325 | pub gray: Colors,
326 | pub red: Color,
327 | pub black: Color,
328 | pub white: Color,
329 | pub transparent: Color,
330 | }
331 |
332 | pub struct Colors {
333 | pub main: Color,
334 | pub darker: Color,
335 | pub lighter: Color,
336 | pub lightest: Color,
337 | }
338 |
339 | impl Colors {
340 | pub fn main(&self) -> String {
341 | self.main.to_hex_string()
342 | }
343 | pub fn darker(&self) -> String {
344 | self.darker.to_hex_string()
345 | }
346 | pub fn lighter(&self) -> String {
347 | self.lighter.to_hex_string()
348 | }
349 | pub fn lightest(&self) -> String {
350 | self.lightest.to_hex_string()
351 | }
352 | }
353 |
354 | impl Theme {
355 | pub fn red(&self) -> String {
356 | self.red.to_hex_string()
357 | }
358 | pub fn black(&self) -> String {
359 | self.black.to_hex_string()
360 | }
361 | pub fn white(&self) -> String {
362 | self.white.to_hex_string()
363 | }
364 | pub fn transparent(&self) -> String {
365 | self.transparent.to_hex_string()
366 | }
367 | }
368 | ```
369 |
370 |
371 | ```rust
372 | // /src/app.rs
373 |
374 | #[component]
375 | fn HomePage() -> impl IntoView {
376 | // note that this is the default view macro
377 | view! {
378 |
379 |
380 |
381 | }
382 | }
383 | ```
384 |
--------------------------------------------------------------------------------