textiler_core/components/
typography.rs1use std::collections::HashMap;
4use std::fmt::Display;
5
6use yew::html::IntoPropValue;
7use yew::{
8 classes, function_component, html, html_nested, Children, ContextProvider, Html, Properties,
9};
10
11use crate::style::{Color, Size, Variant};
12use crate::system::{ColorProp, StylingBox, VariantProp};
13use crate::theme::typography::TypographyLevel;
14use crate::Sx;
15
16pub type TypographyLevelMapping = HashMap<TypographyLevel, String>;
17
18fn default_level_mapping() -> TypographyLevelMapping {
19 use Size::*;
20 use TypographyLevel::*;
21 [
22 (H1, "h1"),
23 (H2, "h2"),
24 (H3, "h3"),
25 (H4, "h4"),
26 (Title { size: Xs }, "p"),
27 (Title { size: Sm }, "p"),
28 (Title { size: Md }, "p"),
29 (Title { size: Lg }, "p"),
30 (Title { size: Xl }, "p"),
31 (Body { size: Xs }, "span"),
32 (Body { size: Sm }, "p"),
33 (Body { size: Md }, "p"),
34 (Body { size: Lg }, "p"),
35 (Body { size: Xl }, "p"),
36 ]
37 .into_iter()
38 .map(|(k, v)| (k, v.to_string()))
39 .collect()
40}
41
42#[derive(Debug, Clone, Properties, PartialEq)]
43pub struct TypographyProps {
44 #[prop_or_else(|| "".to_string())]
45 pub component: String,
46 #[prop_or_default]
47 pub sx: Sx,
48 #[prop_or_default]
49 pub level: TypographyLevel,
50 #[prop_or_default]
51 pub variant: VariantProp,
52 #[prop_or_default]
53 pub color: ColorProp,
54 #[prop_or_else(default_level_mapping)]
55 pub mapping: TypographyLevelMapping,
56 #[prop_or_default]
57 pub children: Children,
58}
59
60#[function_component]
61pub fn Typography(props: &TypographyProps) -> Html {
62 let context = yew::use_context::<TypographyContext>();
63 let TypographyProps {
64 component,
65 sx,
66 children,
67 level,
68 mapping,
69 variant,
70 color,
71 ..
72 } = props;
73
74 let component = yew::use_memo(
75 (
76 component.clone(),
77 context.clone(),
78 level.clone(),
79 mapping.clone(),
80 ),
81 |(comp, ctx, level, mapping)| {
82 if comp.is_empty() {
83 let default = &mapping[level];
84
85 return if ctx.is_some() {
86 if default == "p" {
87 "span".to_string()
88 } else {
89 default.clone()
90 }
91 } else {
92 default.clone()
93 };
94 } else {
95 comp.clone()
96 }
97 },
98 );
99
100 let classes = classes!("typography", level.to_string());
101 let inner = html_nested! {
102 <StylingBox {variant} {color} class={classes} sx={sx.clone()} component={(*component).clone()}>
103 { for props.children.iter() }
104 </StylingBox>
105 };
106
107 match context {
108 Some(context) => {
109 html! {
110 {inner}
111 }
112 }
113 None => {
114 html! {
115 <ContextProvider<TypographyContext> context={TypographyContext::default()}>
116 {inner}
117 </ContextProvider<TypographyContext>>
118 }
119 }
120 }
121}
122
123#[derive(Debug, Clone, PartialEq, Default)]
124struct TypographyContext {}