radix_leptos_primitives/components/form_validation/
controls.rs

1use 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/// Form Validation System - Comprehensive validation with real-time feedback
9#[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/// Form Error Summary component
49#[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        // Test component creation without runtime
104        let errors: HashMap<String, String> = HashMap::new();
105        assert!(errors.is_empty());
106    }
107
108    #[test]
109    fn test_form_error_summary_creation() {
110        // Test component creation without runtime
111        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}