Skip to main content

ratatui_form/block/
address.rs

1//! Address composite block.
2
3use crate::block::Block;
4use crate::field::{Field, Select, TextInput};
5use crate::validation::rules::Pattern;
6
7/// US state abbreviations.
8const US_STATES: &[(&str, &str)] = &[
9    ("AL", "Alabama"),
10    ("AK", "Alaska"),
11    ("AZ", "Arizona"),
12    ("AR", "Arkansas"),
13    ("CA", "California"),
14    ("CO", "Colorado"),
15    ("CT", "Connecticut"),
16    ("DE", "Delaware"),
17    ("FL", "Florida"),
18    ("GA", "Georgia"),
19    ("HI", "Hawaii"),
20    ("ID", "Idaho"),
21    ("IL", "Illinois"),
22    ("IN", "Indiana"),
23    ("IA", "Iowa"),
24    ("KS", "Kansas"),
25    ("KY", "Kentucky"),
26    ("LA", "Louisiana"),
27    ("ME", "Maine"),
28    ("MD", "Maryland"),
29    ("MA", "Massachusetts"),
30    ("MI", "Michigan"),
31    ("MN", "Minnesota"),
32    ("MS", "Mississippi"),
33    ("MO", "Missouri"),
34    ("MT", "Montana"),
35    ("NE", "Nebraska"),
36    ("NV", "Nevada"),
37    ("NH", "New Hampshire"),
38    ("NJ", "New Jersey"),
39    ("NM", "New Mexico"),
40    ("NY", "New York"),
41    ("NC", "North Carolina"),
42    ("ND", "North Dakota"),
43    ("OH", "Ohio"),
44    ("OK", "Oklahoma"),
45    ("OR", "Oregon"),
46    ("PA", "Pennsylvania"),
47    ("RI", "Rhode Island"),
48    ("SC", "South Carolina"),
49    ("SD", "South Dakota"),
50    ("TN", "Tennessee"),
51    ("TX", "Texas"),
52    ("UT", "Utah"),
53    ("VT", "Vermont"),
54    ("VA", "Virginia"),
55    ("WA", "Washington"),
56    ("WV", "West Virginia"),
57    ("WI", "Wisconsin"),
58    ("WY", "Wyoming"),
59    ("DC", "District of Columbia"),
60];
61
62/// A composite block for US addresses.
63pub struct AddressBlock {
64    prefix: String,
65    title: Option<String>,
66    required: bool,
67}
68
69impl AddressBlock {
70    /// Creates a new address block with the given prefix.
71    pub fn new(prefix: impl Into<String>) -> Self {
72        Self {
73            prefix: prefix.into(),
74            title: None,
75            required: false,
76        }
77    }
78
79    /// Sets the block title.
80    pub fn title(mut self, title: impl Into<String>) -> Self {
81        self.title = Some(title.into());
82        self
83    }
84
85    /// Marks all fields in this block as required.
86    pub fn required(mut self) -> Self {
87        self.required = true;
88        self
89    }
90
91    fn field_id(&self, name: &str) -> String {
92        format!("{}_{}", self.prefix, name)
93    }
94}
95
96impl Block for AddressBlock {
97    fn prefix(&self) -> &str {
98        &self.prefix
99    }
100
101    fn title(&self) -> Option<&str> {
102        self.title.as_deref()
103    }
104
105    fn fields(&self) -> Vec<Box<dyn Field>> {
106        let mut fields: Vec<Box<dyn Field>> = Vec::new();
107
108        // Street Address 1
109        let mut street1 = TextInput::new(self.field_id("street1"), "Street Address")
110            .placeholder("123 Main St");
111        if self.required {
112            street1 = street1.required();
113        }
114        fields.push(Box::new(street1));
115
116        // Street Address 2
117        let street2 = TextInput::new(self.field_id("street2"), "Address Line 2")
118            .placeholder("Apt, Suite, Unit, etc. (optional)");
119        fields.push(Box::new(street2));
120
121        // City
122        let mut city = TextInput::new(self.field_id("city"), "City")
123            .placeholder("City");
124        if self.required {
125            city = city.required();
126        }
127        fields.push(Box::new(city));
128
129        // State
130        let mut state = Select::new(self.field_id("state"), "State");
131        for (abbr, name) in US_STATES {
132            state = state.option(*abbr, format!("{} ({})", name, abbr));
133        }
134        if self.required {
135            state = state.required();
136        }
137        fields.push(Box::new(state));
138
139        // ZIP Code
140        let mut zip = TextInput::new(self.field_id("zip"), "ZIP Code")
141            .placeholder("12345 or 12345-6789")
142            .validator(Box::new(Pattern::zip_code()));
143        if self.required {
144            zip = zip.required();
145        }
146        fields.push(Box::new(zip));
147
148        fields
149    }
150}