textiler_core/components/
system.rs1use std::ops::Deref;
4
5use strum::IntoEnumIterator;
6use web_sys::HtmlElement;
7use yew::{classes, Classes, function_component, html, Html, Properties, use_effect_with};
8use yew::html::{Children, ImplicitClone, IntoPropValue};
9
10use crate::hooks::{use_sx, use_theme};
11use crate::style::{Color, Variant};
12use crate::theme::sx::Sx;
13
14#[derive(Default, Debug, Clone, PartialEq, Properties)]
15pub struct StylingBoxProps {
16 #[prop_or_default]
17 pub sx: Sx,
18 #[prop_or_default]
19 pub variant: VariantProp,
20 #[prop_or_default]
21 pub color: ColorProp,
22 #[prop_or_else(|| "div".to_string())]
23 pub component: String,
24 #[prop_or_else(|| classes!("box"))]
25 pub class: Classes,
26 #[prop_or_default]
27 pub disabled: bool,
28 #[prop_or_default]
29 pub children: Children,
30}
31
32#[function_component]
33pub fn StylingBox(props: &StylingBoxProps) -> Html {
34 let sx = use_sx(props.sx.clone());
35 let theme = use_theme();
36 let mut classes = classes!(sx);
37 classes.extend(props.class.clone());
38 classes.extend(classes!(format!("{}-system", theme.prefix)));
39
40 let html_ref = yew::use_node_ref();
41 {
42 let html_ref = html_ref.clone();
43 use_effect_with(
44 (props.variant, props.color, props.disabled, html_ref),
45 |(variant, color, disabled, node)| {
46 info!("setting attributes for color and variant for node: {node:?}");
47 let element: HtmlElement = node
48 .cast::<HtmlElement>()
49 .expect("should be an html element");
50 if let Some(variant) = **variant {
51 element
52 .set_attribute("variant", &variant.to_string())
53 .expect("could not set variant attribute");
54 }
55 if let Some(color) = **color {
56 element
57 .set_attribute("color", &color.to_string())
58 .expect("could not set color attribute");
59 }
60 if *disabled {
61 element
62 .set_attribute("disabled", "")
63 .expect("could not set color attribute");
64 } else {
65 let _ = element.remove_attribute("disabled");
66 }
67 },
68 );
69 }
70
71 html! {
72 <@{props.component.clone()} class={classes} ref={html_ref}>
73 { for props.children.clone() }
74 </@>
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Default)]
79pub struct VariantProp(Option<Variant>);
80
81impl IntoPropValue<VariantProp> for &str {
82 fn into_prop_value(self) -> VariantProp {
83 for var in Variant::iter() {
84 if var.as_ref().to_lowercase() == self {
85 return VariantProp(Some(var));
86 }
87 }
88 panic!("no variant named {}", self)
89 }
90}
91
92impl IntoPropValue<VariantProp> for Variant {
93 fn into_prop_value(self) -> VariantProp {
94 VariantProp(Some(self))
95 }
96}
97
98impl Deref for VariantProp {
99 type Target = Option<Variant>;
100
101 fn deref(&self) -> &Self::Target {
102 &self.0
103 }
104}
105
106impl ImplicitClone for VariantProp {}
107
108#[derive(Debug, Clone, Copy, PartialEq, Default)]
109pub struct ColorProp(Option<Color>);
110
111impl IntoPropValue<ColorProp> for &str {
112 fn into_prop_value(self) -> ColorProp {
113 for var in Color::iter() {
114 if var.as_ref().to_lowercase() == self {
115 return ColorProp(Some(var));
116 }
117 }
118 panic!("no Color named {}", self)
119 }
120}
121
122impl IntoPropValue<ColorProp> for Color {
123 fn into_prop_value(self) -> ColorProp {
124 ColorProp(Some(self))
125 }
126}
127
128impl Deref for ColorProp {
129 type Target = Option<Color>;
130
131 fn deref(&self) -> &Self::Target {
132 &self.0
133 }
134}
135
136impl ImplicitClone for ColorProp {}
137
138#[cfg(test)]
139mod tests {
140 use yew::ServerRenderer;
141
142 use super::*;
143
144 #[tokio::test]
145 async fn styled_box() {
146 let renderer = ServerRenderer::<StylingBox>::new();
147 let s = renderer.render().await;
148 println!("{s}");
149 }
150}