patternfly_yew/components/form/
select.rs1use crate::ouia;
2use crate::prelude::{Icon, OuiaComponentType, ValidationContext};
3use crate::utils::{Ouia, OuiaSafe};
4use std::fmt::Display;
5use std::rc::Rc;
6use std::str::FromStr;
7use web_sys::HtmlSelectElement;
8use yew::{
9 html::ChildrenRenderer,
10 prelude::*,
11 virtual_dom::{VChild, VComp},
12};
13
14const OUIA: Ouia = ouia!("FormSelect");
15
16#[derive(Clone, PartialEq, Properties)]
18pub struct FormSelectProperties<K: 'static + Clone + PartialEq + Display + FromStr> {
19 #[prop_or_default]
20 pub id: Option<AttrValue>,
21
22 #[prop_or_default]
23 pub name: Option<AttrValue>,
24
25 #[prop_or_default]
26 pub disabled: bool,
27
28 #[prop_or_default]
29 pub required: bool,
30
31 #[prop_or_default]
32 pub placeholder: Option<AttrValue>,
33
34 #[prop_or_default]
35 pub onchange: Callback<Option<K>>,
36
37 #[prop_or_default]
39 pub value: Option<K>,
40
41 #[prop_or_default]
42 pub children: ChildrenRenderer<FormSelectChildVariant<K>>,
43
44 #[prop_or_default]
45 pub onvalidate: Callback<ValidationContext<Vec<K>>>,
46
47 #[prop_or_default]
49 pub ouia_id: Option<String>,
50 #[prop_or(OUIA.component_type())]
52 pub ouia_type: OuiaComponentType,
53 #[prop_or(OuiaSafe::TRUE)]
55 pub ouia_safe: OuiaSafe,
56}
57
58#[function_component(FormSelect)]
60pub fn form_select<K>(props: &FormSelectProperties<K>) -> Html
61where
62 K: 'static + Clone + PartialEq + Display + FromStr,
63{
64 let ouia_id = use_memo(props.ouia_id.clone(), |id| {
65 id.clone().unwrap_or(OUIA.generated_id())
66 });
67 let node_ref = use_node_ref();
68
69 let oninput = {
70 let node_ref = node_ref.clone();
71 let onchange = props.onchange.clone();
72 Callback::from(move |_evt: InputEvent| {
73 if let Some(ele) = node_ref.cast::<HtmlSelectElement>() {
74 let value = ele.value();
75 if value.is_empty() {
76 onchange.emit(None);
77 } else {
78 onchange.emit(K::from_str(&value).ok());
79 }
80 }
81 })
82 };
83
84 {
85 let node_ref = node_ref.clone();
86
87 use_effect_with(
88 props
89 .value
90 .as_ref()
91 .map(ToString::to_string)
92 .unwrap_or_default(),
93 move |value| {
94 if let Some(ele) = node_ref.cast::<HtmlSelectElement>() {
95 ele.set_value(value);
96 }
97 },
98 );
99 }
100
101 html! (
102 <div class="pf-v5-c-form-control">
103 <select
104 {oninput}
105 name={&props.name}
106 id={&props.id}
107 ref={node_ref}
108 required={props.required}
109 data-ouia-component-id={(*ouia_id).clone()}
110 data-ouia-component-type={props.ouia_type}
111 data-ouia-safe={props.ouia_safe}
112 >
113 if let Some(placeholder) = &props.placeholder {
114 <option value="">{ placeholder }</option>
115 }
116 { for props.children.iter() }
117 </select>
118 <div class="pf-v5-c-form-control__utilities">
119 <div class="pf-v5-c-form-control__toggle-icon">
120 {Icon::CaretDown}
121 </div>
122 </div>
123 </div>
124 )
125}
126
127#[derive(Clone, PartialEq)]
128pub enum FormSelectChild<K>
129where
130 K: 'static + Clone + PartialEq + Display,
131{
132 Option(Rc<<FormSelectOption<K> as BaseComponent>::Properties>),
133 Group(Rc<<FormSelectGroup<K> as BaseComponent>::Properties>),
134}
135
136impl<K> From<FormSelectOptionProperties<K>> for FormSelectChild<K>
137where
138 K: Clone + PartialEq + Display,
139{
140 fn from(props: FormSelectOptionProperties<K>) -> Self {
141 FormSelectChild::Option(Rc::new(props))
142 }
143}
144
145impl<K> From<FormSelectGroupProperties<K>> for FormSelectChild<K>
146where
147 K: Clone + PartialEq + Display,
148{
149 fn from(props: FormSelectGroupProperties<K>) -> Self {
150 FormSelectChild::Group(Rc::new(props))
151 }
152}
153
154#[derive(PartialEq, Clone)]
157pub struct FormSelectChildVariant<K>
158where
159 K: 'static + Clone + PartialEq + Display,
160{
161 props: FormSelectChild<K>,
162}
163
164impl<K, CHILD> From<VChild<CHILD>> for FormSelectChildVariant<K>
165where
166 CHILD: BaseComponent,
167 CHILD::Properties: Into<FormSelectChild<K>> + Clone,
168 K: 'static + Clone + PartialEq + Display,
169{
170 fn from(vchild: VChild<CHILD>) -> Self {
171 Self {
172 props: (*vchild.props).clone().into(),
173 }
174 }
175}
176
177impl<K> From<FormSelectChildVariant<K>> for Html
178where
179 K: 'static + Clone + PartialEq + Display,
180{
181 fn from(value: FormSelectChildVariant<K>) -> Self {
182 match value.props {
183 FormSelectChild::Option(props) => VComp::new::<FormSelectOption<K>>(props, None).into(),
184 FormSelectChild::Group(props) => VComp::new::<FormSelectGroup<K>>(props, None).into(),
185 }
186 }
187}
188
189#[derive(Clone, PartialEq, Properties)]
192pub struct FormSelectOptionProperties<K>
193where
194 K: Clone + PartialEq + Display,
195{
196 pub value: K,
197
198 #[prop_or_default]
199 pub id: Option<String>,
200
201 #[prop_or_default]
202 pub description: Option<String>,
203
204 #[prop_or_default]
205 pub selected: bool,
206}
207
208#[function_component(FormSelectOption)]
209pub fn form_select_option<K>(props: &FormSelectOptionProperties<K>) -> Html
210where
211 K: 'static + Clone + PartialEq + Display,
212{
213 html! (
214 <option
215 id={props.id.clone()}
216 selected={props.selected}
217 value={props.value.to_string()}
218 >
219 if let Some(description) = &props.description {
220 { &description }
221 } else {
222 { &props.value }
223 }
224 </option>
225 )
226}
227
228#[derive(Clone, PartialEq, Properties)]
229pub struct FormSelectGroupProperties<K>
230where
231 K: 'static + Clone + PartialEq + Display,
232{
233 pub label: String,
234 #[prop_or_default]
235 pub children: ChildrenRenderer<FormSelectChildVariant<K>>,
236}
237
238#[function_component(FormSelectGroup)]
239pub fn form_select_group<K>(props: &FormSelectGroupProperties<K>) -> Html
240where
241 K: 'static + Clone + PartialEq + Display,
242{
243 html! (
244 <optgroup label={props.label.clone()}>
245 { for props.children.iter() }
246 </optgroup>
247 )
248}