Skip to main content

orbital_base_components/form/rules/
date_picker.rs

1use std::ops::Deref;
2
3use leptos::prelude::*;
4
5use crate::form::{format_datetime, DatetimeFormat, OrbitalDateTime};
6
7use super::{FieldValidationState, Rule};
8
9#[derive(Debug, Default, PartialEq, Clone, Copy)]
10pub enum DatePickerRuleTrigger {
11    #[default]
12    Blur,
13    Change,
14    Select,
15}
16
17pub struct DatePickerRule(Rule<Option<OrbitalDateTime>, DatePickerRuleTrigger>);
18
19impl DatePickerRule {
20    pub fn required(required: Signal<bool>) -> Self {
21        Self::validator(move |value, name| {
22            if required.get_untracked() && value.is_none() {
23                let message = name.get_untracked().map_or_else(
24                    || String::from("Please select date!"),
25                    |name| format!("Please select {name}!"),
26                );
27                Err(FieldValidationState::Error(message))
28            } else {
29                Ok(())
30            }
31        })
32    }
33
34    pub fn required_with_message(required: Signal<bool>, message: Signal<String>) -> Self {
35        Self::validator(move |value, _| {
36            if required.get_untracked() && value.is_none() {
37                Err(FieldValidationState::Error(message.get_untracked()))
38            } else {
39                Ok(())
40            }
41        })
42    }
43
44    pub fn min_date(min: Signal<Option<OrbitalDateTime>>) -> Self {
45        Self::validator(move |value, _| {
46            let Some(value) = value else {
47                return Ok(());
48            };
49            let Some(min) = min.get_untracked() else {
50                return Ok(());
51            };
52            if value.start_of_day() < min.start_of_day() {
53                let bound = format_datetime(min.start_of_day(), DatetimeFormat::IsoDate);
54                Err(FieldValidationState::Error(format!(
55                    "Date must be on or after {bound}"
56                )))
57            } else {
58                Ok(())
59            }
60        })
61    }
62
63    pub fn min_date_with_message(
64        min: Signal<Option<OrbitalDateTime>>,
65        message: Signal<String>,
66    ) -> Self {
67        Self::validator(move |value, _| {
68            let Some(value) = value else {
69                return Ok(());
70            };
71            let Some(min) = min.get_untracked() else {
72                return Ok(());
73            };
74            if value.start_of_day() < min.start_of_day() {
75                Err(FieldValidationState::Error(message.get_untracked()))
76            } else {
77                Ok(())
78            }
79        })
80    }
81
82    pub fn max_date(max: Signal<Option<OrbitalDateTime>>) -> Self {
83        Self::validator(move |value, _| {
84            let Some(value) = value else {
85                return Ok(());
86            };
87            let Some(max) = max.get_untracked() else {
88                return Ok(());
89            };
90            if value.start_of_day() > max.start_of_day() {
91                let bound = format_datetime(max.start_of_day(), DatetimeFormat::IsoDate);
92                Err(FieldValidationState::Error(format!(
93                    "Date must be on or before {bound}"
94                )))
95            } else {
96                Ok(())
97            }
98        })
99    }
100
101    pub fn max_date_with_message(
102        max: Signal<Option<OrbitalDateTime>>,
103        message: Signal<String>,
104    ) -> Self {
105        Self::validator(move |value, _| {
106            let Some(value) = value else {
107                return Ok(());
108            };
109            let Some(max) = max.get_untracked() else {
110                return Ok(());
111            };
112            if value.start_of_day() > max.start_of_day() {
113                Err(FieldValidationState::Error(message.get_untracked()))
114            } else {
115                Ok(())
116            }
117        })
118    }
119
120    pub fn validator(
121        f: impl Fn(&Option<OrbitalDateTime>, Signal<Option<String>>) -> Result<(), FieldValidationState>
122            + Send
123            + Sync
124            + 'static,
125    ) -> Self {
126        Self(Rule::validator(f))
127    }
128
129    pub fn with_trigger(self, trigger: DatePickerRuleTrigger) -> Self {
130        Self(Rule::with_trigger(self.0, trigger))
131    }
132}
133
134impl Deref for DatePickerRule {
135    type Target = Rule<Option<OrbitalDateTime>, DatePickerRuleTrigger>;
136
137    fn deref(&self) -> &Self::Target {
138        &self.0
139    }
140}