radix_leptos_primitives/components/form_validation/
controls.rs1use crate::utils::{merge_classes, generate_id};
2use leptos::callback::Callback;
3use leptos::prelude::*;
4use std::collections::HashMap;
5
6use super::validation::{FormValidationState, FormError, ValidationMode, FieldError, ErrorType};
7
8#[component]
10pub fn FormValidationProvider(
11 #[prop(optional)] class: Option<String>,
12 #[prop(optional)] style: Option<String>,
13 #[prop(optional)] children: Option<Children>,
14 #[prop(optional)] validation_mode: Option<ValidationMode>,
15 #[prop(optional)] on_validation_change: Option<Callback<FormValidationState>>,
16) -> impl IntoView {
17 let validation_mode = validation_mode.unwrap_or(ValidationMode::OnChange);
18
19 let (validation_state, set_validation_state) = signal(FormValidationState::default());
20 let (field_errors, set_field_errors) = signal(HashMap::<String, FieldError>::new());
21 let (form_errors, set_form_errors) = signal(Vec::<FormError>::new());
22
23 let class = merge_classes(vec![
24 "form-validation-provider",
25 validation_mode.as_str(),
26 class.as_deref().unwrap_or(""),
27 ]);
28
29 let handle_validation_change = move |new_state: FormValidationState| {
30 set_validation_state.set(new_state.clone());
31 if let Some(callback) = on_validation_change {
32 callback.run(new_state);
33 }
34 };
35
36 view! {
37 <div
38 class=class
39 style=style
40 role="form"
41 aria-label="Form with validation"
42 >
43 {children.map(|c| c())}
44 </div>
45 }
46}
47
48#[component]
50pub fn FormErrorSummary(
51 #[prop(optional)] class: Option<String>,
52 #[prop(optional)] style: Option<String>,
53 #[prop(optional)] errors: Option<Vec<FormError>>,
54 #[prop(optional)] show_field_errors: Option<bool>,
55 #[prop(optional)] show_form_errors: Option<bool>,
56) -> impl IntoView {
57 let errors = errors.unwrap_or_default();
58 let show_field_errors = show_field_errors.unwrap_or(true);
59 let show_form_errors = show_form_errors.unwrap_or(true);
60
61 let class = merge_classes(vec![
62 "form-error-summary",
63 class.as_deref().unwrap_or(""),
64 ]);
65
66 view! {
67 <div
68 class=class
69 style=style
70 role="alert"
71 aria-live="polite"
72 aria-label="Form errors"
73 >
74 {if !errors.is_empty() {
75 view! {
76 <div class="error-summary-header">
77 <h3>"Please correct the following errors:"</h3>
78 </div>
79 <ul class="error-summary-list">
80 {errors.into_iter().map(|error| {
81 view! {
82 <li class="error-summary-item">
83 <span class="error-field">{error.field}</span>
84 <span class="error-message">{error.message}</span>
85 </li>
86 }
87 }).collect::<Vec<_>>()}
88 </ul>
89 }.into_any()
90 } else {
91 view! { <div></div> }.into_any()
92 }}
93 </div>
94 }
95}
96
97#[cfg(test)]
98mod controls_tests {
99 use super::*;
100
101 #[test]
102 fn test_form_validation_provider_creation() {
103 let errors: HashMap<String, String> = HashMap::new();
105 assert!(errors.is_empty());
106 }
107
108 #[test]
109 fn test_form_error_summary_creation() {
110 let errors = vec![
112 FormError {
113 field: "email".to_string(),
114 message: "Invalid email format".to_string(),
115 error_type: ErrorType::Validation,
116 }
117 ];
118 assert!(!errors.is_empty());
119 }
120}