1use crate::styles::Style;
6use crate::theme::tokens::Color;
7use crate::theme::{use_style, use_theme};
8use dioxus::prelude::*;
9
10#[derive(Default, Clone, PartialEq)]
12pub enum TextSize {
13 ExtraSmall,
14 Small,
15 #[default]
16 Base,
17 Large,
18 ExtraLarge,
19 H1,
20 H2,
21 H3,
22 H4,
23}
24
25#[derive(Default, Clone, PartialEq)]
27pub enum TextWeight {
28 #[default]
29 Normal,
30 Medium,
31 Semibold,
32 Bold,
33}
34
35#[derive(Default, Clone, PartialEq)]
37pub enum TextColor {
38 #[default]
39 Default,
40 Muted,
41 Primary,
42 Secondary,
43 Destructive,
44 Success,
45 Warning,
46 Inverse,
47 Custom(Color),
48}
49
50#[derive(Props, Clone, PartialEq)]
52pub struct LabelProps {
53 pub children: Element,
55 #[props(default)]
57 pub size: TextSize,
58 #[props(default)]
60 pub weight: TextWeight,
61 #[props(default)]
63 pub color: TextColor,
64 #[props(default)]
66 pub style: Option<String>,
67 #[props(default)]
69 pub class: Option<String>,
70 #[props(default)]
72 pub as_element: LabelElement,
73 #[props(default)]
75 pub align: TextAlign,
76 #[props(default)]
78 pub truncate: bool,
79 #[props(default)]
81 pub html_for: Option<String>,
82 #[props(default)]
84 pub line_clamp: Option<u8>,
85}
86
87#[derive(Default, Clone, PartialEq)]
89pub enum LabelElement {
90 #[default]
91 Span,
92 P,
93 Div,
94 Label,
95 H1,
96 H2,
97 H3,
98 H4,
99 H5,
100 H6,
101 Strong,
102 Em,
103 Small,
104}
105
106#[derive(Default, Clone, PartialEq)]
108pub enum TextAlign {
109 #[default]
110 Left,
111 Center,
112 Right,
113 Justify,
114}
115
116impl TextAlign {
117 fn as_str(&self) -> &'static str {
118 match self {
119 TextAlign::Left => "left",
120 TextAlign::Center => "center",
121 TextAlign::Right => "right",
122 TextAlign::Justify => "justify",
123 }
124 }
125}
126
127#[component]
142pub fn Label(props: LabelProps) -> Element {
143 let _theme = use_theme();
144
145 let size = props.size.clone();
146 let weight = props.weight.clone();
147 let color = props.color.clone();
148 let align = props.align.clone();
149 let truncate = props.truncate;
150 let line_clamp = props.line_clamp;
151
152 let style = use_style(move |t| {
154 let typography_size = match size {
155 TextSize::ExtraSmall => "xs",
156 TextSize::Small => "sm",
157 TextSize::Base => "base",
158 TextSize::Large => "lg",
159 TextSize::ExtraLarge => "xl",
160 TextSize::H1 => "h1",
161 TextSize::H2 => "h2",
162 TextSize::H3 => "h3",
163 TextSize::H4 => "h4",
164 };
165
166 let base = Style::new()
167 .text(&t.typography, typography_size)
168 .text_align(align.as_str());
169
170 let base = match weight {
172 TextWeight::Normal => base.font_weight(400),
173 TextWeight::Medium => base.font_weight(500),
174 TextWeight::Semibold => base.font_weight(600),
175 TextWeight::Bold => base.font_weight(700),
176 };
177
178 let base = match &color {
180 TextColor::Default => base.text_color(&t.colors.foreground),
181 TextColor::Muted => base.text_color(&t.colors.muted_foreground),
182 TextColor::Primary => base.text_color(&t.colors.primary),
183 TextColor::Secondary => base.text_color(&t.colors.secondary_foreground),
184 TextColor::Destructive => base.text_color(&t.colors.destructive),
185 TextColor::Success => base.text_color(&t.colors.success),
186 TextColor::Warning => base.text_color(&t.colors.warning),
187 TextColor::Inverse => base.text_color(&t.colors.background),
188 TextColor::Custom(c) => base.text_color(c),
189 };
190
191 let base = if truncate {
193 Style {
194 overflow: Some("hidden".into()),
195 white_space: Some("nowrap".into()),
196 text_decoration: Some("ellipsis".into()),
197 ..base
198 }
199 } else {
200 base
201 };
202
203 let base = if let Some(_clamp) = line_clamp {
205 Style {
206 overflow: Some("hidden".into()),
207 ..base
208 }
209 } else {
210 base
211 };
212
213 base.build()
214 });
215
216 let final_style = if let Some(custom) = &props.style {
218 format!("{} {}", style(), custom)
219 } else {
220 style()
221 };
222
223 let class = props.class.clone().unwrap_or_default();
224 let html_for = props.html_for.clone();
225
226 match props.as_element {
228 LabelElement::Span => rsx! {
229 span { style: "{final_style}", class: "{class}", {props.children} }
230 },
231 LabelElement::P => rsx! {
232 p { style: "{final_style}", class: "{class}", {props.children} }
233 },
234 LabelElement::Div => rsx! {
235 div { style: "{final_style}", class: "{class}", {props.children} }
236 },
237 LabelElement::Label => rsx! {
238 label { style: "{final_style}", class: "{class}", r#for: html_for, {props.children} }
239 },
240 LabelElement::H1 => rsx! {
241 h1 { style: "{final_style}", class: "{class}", {props.children} }
242 },
243 LabelElement::H2 => rsx! {
244 h2 { style: "{final_style}", class: "{class}", {props.children} }
245 },
246 LabelElement::H3 => rsx! {
247 h3 { style: "{final_style}", class: "{class}", {props.children} }
248 },
249 LabelElement::H4 => rsx! {
250 h4 { style: "{final_style}", class: "{class}", {props.children} }
251 },
252 LabelElement::H5 => rsx! {
253 h5 { style: "{final_style}", class: "{class}", {props.children} }
254 },
255 LabelElement::H6 => rsx! {
256 h6 { style: "{final_style}", class: "{class}", {props.children} }
257 },
258 LabelElement::Strong => rsx! {
259 strong { style: "{final_style}", class: "{class}", {props.children} }
260 },
261 LabelElement::Em => rsx! {
262 em { style: "{final_style}", class: "{class}", {props.children} }
263 },
264 LabelElement::Small => rsx! {
265 small { style: "{final_style}", class: "{class}", {props.children} }
266 },
267 }
268}
269
270#[component]
272pub fn Heading(
273 children: Element,
274 #[props(default)] level: HeadingLevel,
275 #[props(default)] weight: TextWeight,
276 #[props(default)] color: TextColor,
277) -> Element {
278 let size = match level {
279 HeadingLevel::H1 => TextSize::H1,
280 HeadingLevel::H2 => TextSize::H2,
281 HeadingLevel::H3 => TextSize::H3,
282 HeadingLevel::H4 => TextSize::H4,
283 };
284
285 let element = match level {
286 HeadingLevel::H1 => LabelElement::H1,
287 HeadingLevel::H2 => LabelElement::H2,
288 HeadingLevel::H3 => LabelElement::H3,
289 HeadingLevel::H4 => LabelElement::H4,
290 };
291
292 rsx! {
293 Label {
294 size: size,
295 weight: weight,
296 color: color,
297 as_element: element,
298 {children}
299 }
300 }
301}
302
303#[derive(Default, Clone, PartialEq)]
305pub enum HeadingLevel {
306 H1,
307 H2,
308 H3,
309 #[default]
310 H4,
311}
312
313#[component]
315pub fn MutedText(children: Element, #[props(default)] size: TextSize) -> Element {
316 rsx! {
317 Label {
318 size: size,
319 color: TextColor::Muted,
320 {children}
321 }
322 }
323}