Skip to main content

maud_ui/primitives/
native_select.rs

1//! NativeSelect component — styled native `<select>` matching Input appearance.
2use maud::{html, Markup, PreEscaped};
3
4pub struct NativeOption {
5    pub value: String,
6    pub label: String,
7    pub disabled: bool,
8}
9
10pub struct NativeSelectProps {
11    pub name: String,
12    pub id: String,
13    pub options: Vec<NativeOption>,
14    pub selected: Option<String>,
15    pub disabled: bool,
16    pub placeholder: Option<String>,
17}
18
19/// SVG chevron-down (lucide icon, 15x15)
20const CHEVRON_DOWN: &str = r#"<svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m6 9 6 6 6-6"/></svg>"#;
21
22pub fn render(props: NativeSelectProps) -> Markup {
23    html! {
24        div.mui-native-select {
25            select.mui-native-select__select
26                name=(props.name)
27                id=(props.id)
28                disabled[props.disabled]
29            {
30                @if let Some(placeholder) = props.placeholder {
31                    option value="" disabled selected hidden { (placeholder) }
32                }
33                @for option in props.options {
34                    option
35                        value=(option.value.clone())
36                        selected[props.selected.as_ref() == Some(&option.value)]
37                        disabled[option.disabled]
38                    {
39                        (option.label)
40                    }
41                }
42            }
43            span.mui-native-select__chevron aria-hidden="true" { (PreEscaped(CHEVRON_DOWN)) }
44        }
45    }
46}
47
48pub fn showcase() -> Markup {
49    html! {
50        div.mui-showcase__grid {
51            div {
52                p.mui-showcase__caption { "Country" }
53                div class="mui-field" {
54                    label class="mui-label" for="country-select" { "Country" }
55                    (render(NativeSelectProps {
56                        name: "country".to_string(),
57                        id: "country-select".to_string(),
58                        options: vec![
59                            NativeOption { value: "us".to_string(), label: "United States".to_string(), disabled: false },
60                            NativeOption { value: "gb".to_string(), label: "United Kingdom".to_string(), disabled: false },
61                            NativeOption { value: "ca".to_string(), label: "Canada".to_string(), disabled: false },
62                            NativeOption { value: "de".to_string(), label: "Germany".to_string(), disabled: false },
63                            NativeOption { value: "fr".to_string(), label: "France".to_string(), disabled: false },
64                            NativeOption { value: "jp".to_string(), label: "Japan".to_string(), disabled: false },
65                            NativeOption { value: "au".to_string(), label: "Australia".to_string(), disabled: false },
66                            NativeOption { value: "za".to_string(), label: "South Africa".to_string(), disabled: false },
67                        ],
68                        selected: None,
69                        disabled: false,
70                        placeholder: Some("Select a country\u{2026}".to_string()),
71                    }))
72                }
73            }
74
75            div {
76                p.mui-showcase__caption { "Currency" }
77                div class="mui-field" {
78                    label class="mui-label" for="currency-select" { "Currency" }
79                    (render(NativeSelectProps {
80                        name: "currency".to_string(),
81                        id: "currency-select".to_string(),
82                        options: vec![
83                            NativeOption { value: "USD".to_string(), label: "USD \u{2014} US Dollar".to_string(), disabled: false },
84                            NativeOption { value: "EUR".to_string(), label: "EUR \u{2014} Euro".to_string(), disabled: false },
85                            NativeOption { value: "GBP".to_string(), label: "GBP \u{2014} British Pound".to_string(), disabled: false },
86                            NativeOption { value: "JPY".to_string(), label: "JPY \u{2014} Japanese Yen".to_string(), disabled: false },
87                            NativeOption { value: "ZAR".to_string(), label: "ZAR \u{2014} South African Rand".to_string(), disabled: false },
88                        ],
89                        selected: Some("USD".to_string()),
90                        disabled: false,
91                        placeholder: None,
92                    }))
93                }
94            }
95
96            div {
97                p.mui-showcase__caption { "Disabled" }
98                div class="mui-field" {
99                    label class="mui-label mui-label--disabled" for="timezone-select" { "Timezone" }
100                    (render(NativeSelectProps {
101                        name: "timezone".to_string(),
102                        id: "timezone-select".to_string(),
103                        options: vec![
104                            NativeOption { value: "utc".to_string(), label: "UTC +00:00".to_string(), disabled: false },
105                            NativeOption { value: "est".to_string(), label: "EST -05:00".to_string(), disabled: false },
106                            NativeOption { value: "pst".to_string(), label: "PST -08:00".to_string(), disabled: false },
107                        ],
108                        selected: Some("utc".to_string()),
109                        disabled: true,
110                        placeholder: None,
111                    }))
112                }
113            }
114        }
115    }
116}