radix_leptos_primitives/components/
label.rs1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::children::Children;
4use leptos::prelude::*;
5
6#[component]
8pub fn Label(
9 #[prop(optional)] class: Option<String>,
10 #[prop(optional)] style: Option<String>,
11 #[prop(optional)] children: Option<Children>,
12 #[prop(optional)] for_id: Option<String>,
13 #[prop(optional)] required: Option<bool>,
14 #[prop(optional)] disabled: Option<bool>,
15 #[prop(optional)] size: Option<LabelSize>,
16 #[prop(optional)] variant: Option<LabelVariant>,
17 #[prop(optional)] on_click: Option<Callback<()>>,
18) -> impl IntoView {
19 let for_id = for_id.unwrap_or_default();
20 let required = required.unwrap_or(false);
21 let disabled = disabled.unwrap_or(false);
22 let size = size.unwrap_or_default();
23 let variant = variant.unwrap_or_default();
24
25 let class = merge_classes(vec![
26 "label",
27 &size.to_class(),
28 &variant.to_class(),
29 class.as_deref().unwrap_or(""),
30 ]);
31
32 let handle_click = move |_| {
33 if !disabled {
34 if let Some(callback) = on_click {
35 callback.run(());
36 }
37 }
38 };
39
40 view! {
41 <label
42 class=class
43 style=style
44 for=for_id
45 aria-required=required
46 aria-disabled=disabled
47 on:click=handle_click
48 >
49 {children.map(|c| c())}
50 </label>
51 }
52}
53
54#[component]
56pub fn LabelText(
57 #[prop(optional)] class: Option<String>,
58 #[prop(optional)] style: Option<String>,
59 #[prop(optional)] children: Option<Children>,
60 #[prop(optional)] text: Option<String>,
61 #[prop(optional)] required: Option<bool>,
62) -> impl IntoView {
63 let text = text.unwrap_or_default();
64 let required = required.unwrap_or(false);
65
66 let class = merge_classes(vec!["label-text"]);
67
68 view! {
69 <span class=class style=style>
70 {text}
71 {if required { "*" } else { "" }}
72 {children.map(|c| c())}
73 </span>
74 }
75}
76
77#[component]
79pub fn LabelDescription(
80 #[prop(optional)] class: Option<String>,
81 #[prop(optional)] style: Option<String>,
82 #[prop(optional)] children: Option<Children>,
83 #[prop(optional)] description: Option<String>,
84) -> impl IntoView {
85 let description = description.unwrap_or_default();
86
87 let class = merge_classes(vec!["label-description", class.as_deref().unwrap_or("")]);
88
89 view! {
90 <div
91 class=class
92 style=style
93 role="text"
94 aria-label="Label description"
95 >
96 {children.map(|c| c())}
97 </div>
98 }
99}
100
101#[component]
103pub fn LabelError(
104 #[prop(optional)] class: Option<String>,
105 #[prop(optional)] style: Option<String>,
106 #[prop(optional)] children: Option<Children>,
107 #[prop(optional)] error: Option<String>,
108 #[prop(optional)] visible: Option<bool>,
109) -> impl IntoView {
110 let error = error.unwrap_or_default();
111 let visible = visible.unwrap_or(false);
112
113 let class = merge_classes(vec!["label-error"]);
114
115 view! {
116 <div class=class style=style aria-live="polite">
117 {if visible { error } else { "".to_string() }}
118 {children.map(|c| c())}
119 </div>
120 }
121}
122
123#[component]
125pub fn LabelGroup(
126 #[prop(optional)] class: Option<String>,
127 #[prop(optional)] style: Option<String>,
128 #[prop(optional)] children: Option<Children>,
129 #[prop(optional)] orientation: Option<LabelOrientation>,
130 #[prop(optional)] spacing: Option<LabelSpacing>,
131) -> impl IntoView {
132 let orientation = orientation.unwrap_or_default();
133 let spacing = spacing.unwrap_or_default();
134
135 let class = merge_classes(vec![
136 "label-group",
137 &orientation.to_class(),
138 &spacing.to_class(),
139 class.as_deref().unwrap_or(""),
140 ]);
141
142 view! {
143 <div
144 class=class
145 style=style
146 role="group"
147 aria-label="Label group"
148 data-orientation=orientation.to_string()
149 data-spacing=spacing.to_string()
150 >
151 {children.map(|c| c())}
152 </div>
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
158pub enum LabelSize {
159 #[default]
160 Small,
161 Medium,
162 Large,
163}
164
165impl LabelSize {
166 pub fn to_class(&self) -> &'static str {
167 match self {
168 LabelSize::Small => "size-small",
169 LabelSize::Medium => "size-medium",
170 LabelSize::Large => "size-large",
171 }
172 }
173
174 pub fn to_string(&self) -> &'static str {
175 match self {
176 LabelSize::Small => "small",
177 LabelSize::Medium => "medium",
178 LabelSize::Large => "large",
179 }
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
185pub enum LabelVariant {
186 #[default]
187 Default,
188 Primary,
189 Secondary,
190 Success,
191 Warning,
192 Error,
193}
194
195impl LabelVariant {
196 pub fn to_class(&self) -> &'static str {
197 match self {
198 LabelVariant::Default => "variant-default",
199 LabelVariant::Primary => "variant-primary",
200 LabelVariant::Secondary => "variant-secondary",
201 LabelVariant::Success => "variant-success",
202 LabelVariant::Warning => "variant-warning",
203 LabelVariant::Error => "variant-error",
204 }
205 }
206
207 pub fn to_string(&self) -> &'static str {
208 match self {
209 LabelVariant::Default => "default",
210 LabelVariant::Primary => "primary",
211 LabelVariant::Secondary => "secondary",
212 LabelVariant::Success => "success",
213 LabelVariant::Warning => "warning",
214 LabelVariant::Error => "error",
215 }
216 }
217}
218
219#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
221pub enum LabelOrientation {
222 #[default]
223 Horizontal,
224 Vertical,
225}
226
227impl LabelOrientation {
228 pub fn to_class(&self) -> &'static str {
229 match self {
230 LabelOrientation::Horizontal => "orientation-horizontal",
231 LabelOrientation::Vertical => "orientation-vertical",
232 }
233 }
234
235 pub fn to_string(&self) -> &'static str {
236 match self {
237 LabelOrientation::Horizontal => "horizontal",
238 LabelOrientation::Vertical => "vertical",
239 }
240 }
241}
242
243#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
245pub enum LabelSpacing {
246 #[default]
247 Tight,
248 Normal,
249 Loose,
250}
251
252impl LabelSpacing {
253 pub fn to_class(&self) -> &'static str {
254 match self {
255 LabelSpacing::Tight => "spacing-tight",
256 LabelSpacing::Normal => "spacing-normal",
257 LabelSpacing::Loose => "spacing-loose",
258 }
259 }
260
261 pub fn to_string(&self) -> &'static str {
262 match self {
263 LabelSpacing::Tight => "tight",
264 LabelSpacing::Normal => "normal",
265 LabelSpacing::Loose => "loose",
266 }
267 }
268}
269
270#[cfg(test)]
271mod tests {
272 use proptest::prelude::*;
273 use wasm_bindgen_test::*;
274
275 wasm_bindgen_test_configure!(run_in_browser);
276
277 #[test]
279 fn test_label_creation() {}
280 #[test]
281 fn test_label_with_class() {}
282 #[test]
283 fn test_label_with_style() {}
284 #[test]
285 fn test_label_for_id() {}
286 #[test]
287 fn test_labelrequired() {}
288 #[test]
289 fn test_labeldisabled() {}
290 #[test]
291 fn test_label_size() {}
292 #[test]
293 fn test_label_variant() {}
294 #[test]
295 fn test_label_on_click() {}
296
297 #[test]
299 fn test_label_text_creation() {}
300 #[test]
301 fn test_label_text_with_class() {}
302 #[test]
303 fn test_label_text_text() {}
304 #[test]
305 fn test_label_textrequired() {}
306
307 #[test]
309 fn test_label_description_creation() {}
310 #[test]
311 fn test_label_description_with_class() {}
312 #[test]
313 fn test_label_description_description() {}
314
315 #[test]
317 fn test_label_error_creation() {}
318 #[test]
319 fn test_label_error_with_class() {}
320 #[test]
321 fn test_label_error_error() {}
322 #[test]
323 fn test_label_errorvisible() {}
324
325 #[test]
327 fn test_label_group_creation() {}
328 #[test]
329 fn test_label_group_with_class() {}
330 #[test]
331 fn test_label_group_orientation() {}
332 #[test]
333 fn test_label_group_spacing() {}
334
335 #[test]
337 fn test_label_size_default() {}
338 #[test]
339 fn test_label_size_small() {}
340 #[test]
341 fn test_label_size_medium() {}
342 #[test]
343 fn test_label_size_large() {}
344
345 #[test]
347 fn test_label_variant_default() {}
348 #[test]
349 fn test_label_variant_primary() {}
350 #[test]
351 fn test_label_variant_secondary() {}
352 #[test]
353 fn test_label_variant_success() {}
354 #[test]
355 fn test_label_variant_warning() {}
356 #[test]
357 fn test_label_variant_error() {}
358
359 #[test]
361 fn test_label_orientation_default() {}
362 #[test]
363 fn test_label_orientation_horizontal() {}
364 #[test]
365 fn test_label_orientation_vertical() {}
366
367 #[test]
369 fn test_label_spacing_default() {}
370 #[test]
371 fn test_label_spacing_tight() {}
372 #[test]
373 fn test_label_spacing_normal() {}
374 #[test]
375 fn test_label_spacing_loose() {}
376
377 #[test]
379 fn test_merge_classes_empty() {}
380 #[test]
381 fn test_merge_classes_single() {}
382 #[test]
383 fn test_merge_classes_multiple() {}
384 #[test]
385 fn test_merge_classes_with_empty() {}
386
387 #[test]
389 fn test_label_property_based() {
390 proptest!(|(____class in ".*", __style in ".*")| {
391
392 });
393 }
394
395 #[test]
396 fn test_label_accessibility_validation() {
397 proptest!(|(__for_id in ".*", _required: bool, _disabled: bool)| {
398
399 });
400 }
401
402 #[test]
403 fn test_label_variant_validation() {
404 proptest!(|(____variant in ".*")| {
405
406 });
407 }
408
409 #[test]
411 fn test_label_accessibility() {}
412 #[test]
413 fn test_label_form_integration() {}
414 #[test]
415 fn test_label_validation_workflow() {}
416 #[test]
417 fn test_label_error_display() {}
418 #[test]
419 fn test_label_responsive_behavior() {}
420
421 #[test]
423 fn test_label_large_forms() {}
424 #[test]
425 fn test_label_render_performance() {}
426 #[test]
427 fn test_label_memory_usage() {}
428 #[test]
429 fn test_label_validation_performance() {}
430}