1use maud::{html, Markup};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum InputType {
8 Text,
9 Email,
10 Password,
11 Url,
12 Tel,
13 Search,
14 Number,
15}
16
17impl InputType {
18 fn html_type(&self) -> &'static str {
19 match self {
20 Self::Text => "text",
21 Self::Email => "email",
22 Self::Password => "password",
23 Self::Url => "url",
24 Self::Tel => "tel",
25 Self::Search => "search",
26 Self::Number => "number",
27 }
28 }
29}
30
31#[derive(Debug, Clone)]
33pub struct Props {
34 pub name: String,
36 pub input_type: InputType,
38 pub placeholder: String,
40 pub value: String,
42 pub id: String,
44 pub disabled: bool,
46 pub required: bool,
48 pub invalid: bool,
50 pub readonly: bool,
52}
53
54impl Default for Props {
55 fn default() -> Self {
56 Self {
57 name: String::new(),
58 input_type: InputType::Text,
59 placeholder: String::new(),
60 value: String::new(),
61 id: String::new(),
62 disabled: false,
63 required: false,
64 invalid: false,
65 readonly: false,
66 }
67 }
68}
69
70pub fn render(props: Props) -> Markup {
72 use maud::PreEscaped;
73
74 let input_type = props.input_type.html_type();
75 let mut html_str = format!(
76 r#"<input class="mui-input" type="{}" name="{}" id="{}" placeholder="{}" value="{}""#,
77 input_type,
78 &props.name,
79 &props.id,
80 &props.placeholder,
81 &props.value
82 );
83
84 if props.required {
85 html_str.push_str(" required");
86 }
87 if props.disabled {
88 html_str.push_str(" disabled");
89 }
90 if props.readonly {
91 html_str.push_str(" readonly");
92 }
93 if props.invalid {
94 html_str.push_str(r#" aria-invalid="true""#);
95 }
96
97 html_str.push_str(" />");
98
99 PreEscaped(html_str).into()
100}
101
102pub fn showcase() -> Markup {
104 html! {
105 div.mui-showcase__grid {
106 section {
108 h2 { "Profile" }
109 p.mui-showcase__caption { "A typical sign-up form using text, email, and password inputs." }
110 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:24rem;" {
111 label style="display:flex;flex-direction:column;gap:0.25rem;font-size:0.875rem;font-weight:500;" {
112 "Full Name"
113 (render(Props {
114 name: "fullname".into(),
115 id: "demo-fullname".into(),
116 input_type: InputType::Text,
117 placeholder: "Jane Smith".into(),
118 required: true,
119 ..Default::default()
120 }))
121 }
122 label style="display:flex;flex-direction:column;gap:0.25rem;font-size:0.875rem;font-weight:500;" {
123 "Email Address"
124 (render(Props {
125 name: "email".into(),
126 id: "demo-email".into(),
127 input_type: InputType::Email,
128 placeholder: "jane@example.com".into(),
129 required: true,
130 ..Default::default()
131 }))
132 }
133 label style="display:flex;flex-direction:column;gap:0.25rem;font-size:0.875rem;font-weight:500;" {
134 "Password"
135 (render(Props {
136 name: "password".into(),
137 id: "demo-password".into(),
138 input_type: InputType::Password,
139 placeholder: "At least 8 characters".into(),
140 required: true,
141 ..Default::default()
142 }))
143 }
144 }
145 }
146
147 section {
149 h2 { "Input Types" }
150 p.mui-showcase__caption { "Each HTML input type rendered with a contextual placeholder." }
151 div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(14rem,1fr));gap:0.75rem;" {
152 div style="display:flex;flex-direction:column;gap:0.25rem;" {
153 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Text" }
154 (render(Props {
155 name: "type-text".into(),
156 input_type: InputType::Text,
157 placeholder: "Your name".into(),
158 ..Default::default()
159 }))
160 }
161 div style="display:flex;flex-direction:column;gap:0.25rem;" {
162 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Email" }
163 (render(Props {
164 name: "type-email".into(),
165 input_type: InputType::Email,
166 placeholder: "you@example.com".into(),
167 ..Default::default()
168 }))
169 }
170 div style="display:flex;flex-direction:column;gap:0.25rem;" {
171 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Password" }
172 (render(Props {
173 name: "type-password".into(),
174 input_type: InputType::Password,
175 placeholder: "Secret".into(),
176 ..Default::default()
177 }))
178 }
179 div style="display:flex;flex-direction:column;gap:0.25rem;" {
180 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "URL" }
181 (render(Props {
182 name: "type-url".into(),
183 input_type: InputType::Url,
184 placeholder: "https://example.com".into(),
185 ..Default::default()
186 }))
187 }
188 div style="display:flex;flex-direction:column;gap:0.25rem;" {
189 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Phone" }
190 (render(Props {
191 name: "type-tel".into(),
192 input_type: InputType::Tel,
193 placeholder: "+1 (555) 000-0100".into(),
194 ..Default::default()
195 }))
196 }
197 div style="display:flex;flex-direction:column;gap:0.25rem;" {
198 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Search" }
199 (render(Props {
200 name: "type-search".into(),
201 input_type: InputType::Search,
202 placeholder: "Search articles...".into(),
203 ..Default::default()
204 }))
205 }
206 div style="display:flex;flex-direction:column;gap:0.25rem;" {
207 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Number" }
208 (render(Props {
209 name: "type-number".into(),
210 input_type: InputType::Number,
211 placeholder: "42".into(),
212 ..Default::default()
213 }))
214 }
215 }
216 }
217
218 section {
220 h2 { "States" }
221 p.mui-showcase__caption { "Default, populated, invalid, disabled, and read-only." }
222 div style="display:flex;flex-direction:column;gap:0.75rem;max-width:24rem;" {
223 div style="display:flex;flex-direction:column;gap:0.25rem;" {
224 span style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Default" }
225 (render(Props {
226 name: "state-default".into(),
227 placeholder: "Enter a value...".into(),
228 ..Default::default()
229 }))
230 }
231 div style="display:flex;flex-direction:column;gap:0.25rem;" {
232 label for="state-value" style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "With value" }
233 (render(Props {
234 name: "state-value".into(),
235 id: "state-value".into(),
236 value: "jane@example.com".into(),
237 input_type: InputType::Email,
238 ..Default::default()
239 }))
240 }
241 div style="display:flex;flex-direction:column;gap:0.25rem;" {
242 label for="state-invalid" style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Invalid" }
243 (render(Props {
244 name: "state-invalid".into(),
245 id: "state-invalid".into(),
246 invalid: true,
247 value: "not-an-email".into(),
248 input_type: InputType::Email,
249 ..Default::default()
250 }))
251 span style="font-size:0.75rem;color:var(--mui-destructive,#ef4444);" { "Please enter a valid email address." }
252 }
253 div style="display:flex;flex-direction:column;gap:0.25rem;" {
254 label for="state-disabled" style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Disabled" }
255 (render(Props {
256 name: "state-disabled".into(),
257 id: "state-disabled".into(),
258 disabled: true,
259 value: "Cannot edit".into(),
260 ..Default::default()
261 }))
262 }
263 div style="display:flex;flex-direction:column;gap:0.25rem;" {
264 label for="state-readonly" style="font-size:0.75rem;color:var(--mui-muted-fg,#888);" { "Read-only" }
265 (render(Props {
266 name: "state-readonly".into(),
267 id: "state-readonly".into(),
268 readonly: true,
269 value: "user-9a3f2b".into(),
270 ..Default::default()
271 }))
272 }
273 }
274 }
275 }
276 }
277}