orbital_base_components/form/rules/
date_picker.rs1use 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}