use std::rc::Rc;
use yew::prelude::*;
#[derive(Properties, PartialEq, Clone)]
pub struct SelectProps {
#[prop_or_default]
pub name: &'static str,
#[prop_or_default]
pub id: &'static str,
#[prop_or_default]
pub placeholder: &'static str,
#[prop_or_default]
pub multiple: bool,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub required: bool,
#[prop_or(0)]
pub size: u64,
#[prop_or_default]
pub form: &'static str,
#[prop_or_default]
pub autocomplete: &'static str,
#[prop_or_default]
pub autofocus: bool,
#[prop_or_default]
pub onchange: Callback<Vec<String>>,
#[prop_or_default]
pub children: ChildrenWithProps<Group>,
#[prop_or_default]
pub class: &'static str,
#[prop_or_default]
pub style: &'static str,
#[prop_or_default]
pub labels_class: &'static str,
#[prop_or_default]
pub labels_style: &'static str,
#[prop_or_default]
pub label_class: &'static str,
#[prop_or_default]
pub label_style: &'static str,
#[prop_or_default]
pub close_class: &'static str,
#[prop_or_default]
pub close_style: &'static str,
#[prop_or_default]
pub select_class: &'static str,
#[prop_or_default]
pub select_style: &'static str,
}
#[function_component(Select)]
pub fn select(props: &SelectProps) -> Html {
let SelectProps {
name,
id,
placeholder,
multiple,
disabled,
onchange,
children,
class,
style,
labels_class,
labels_style,
label_class,
label_style,
close_class,
close_style,
select_class,
select_style,
size,
required,
form,
autocomplete,
autofocus,
} = props.clone();
let selected_values = use_state(Vec::<String>::new);
let selected = (*selected_values).clone();
let handle_group_change = {
let selected_values = selected_values.clone();
let on_change = onchange.clone();
Callback::from(move |value: String| {
let mut current_values = (*selected_values).clone();
if multiple {
if current_values.contains(&value) {
current_values.retain(|v| v != &value);
} else {
current_values.push(value);
}
} else {
current_values = vec![value];
}
selected_values.set(current_values.clone());
on_change.emit(current_values);
})
};
let remove_chip = {
let selected_values = selected_values.clone();
let on_change = onchange.clone();
Callback::from(move |value: String| {
let mut current_values = (*selected_values).clone();
current_values.retain(|v| v != &value);
selected_values.set(current_values.clone());
on_change.emit(current_values);
})
};
html! {
<div class={class} style={style}>
{ if multiple {
html! {
<div class={labels_class} style={labels_style}>
{ for selected.clone().into_iter().map(|value| html! {
<div class={label_class} style={label_style}>
{ value.clone() }
<button class={close_class} style={close_style} onclick={remove_chip.clone().reform(move |_| value.clone())}>
{ "x" }
</button>
</div>
}) }
</div>
}
} else {
html! {}
} }
<select
id={id}
name={name}
multiple={multiple}
class={select_class}
style={select_style}
disabled={disabled}
size={size.to_string()}
required={required}
form={form}
autocomplete={autocomplete}
autofocus={autofocus}
>
{ if (!placeholder.is_empty() && selected.is_empty()) || disabled {
html! { <option disabled=true selected=true>{ placeholder }</option> }
} else {
html! {}
} }
if !disabled {
{ for children.iter().map(|mut child| {
let props = Rc::make_mut(&mut child.props);
props.selected = selected.first().cloned().unwrap_or_default();
let handle_group_change = handle_group_change.clone();
props.onchange = Callback::from(move |value| handle_group_change.emit(value));
child
}) }
}
</select>
</div>
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct GroupProps {
#[prop_or_default]
pub label: &'static str,
#[prop_or_default]
pub group: bool,
#[prop_or_default]
pub selected: String,
#[prop_or_default]
pub onchange: Callback<String>,
#[prop_or_default]
pub children: ChildrenWithProps<Option>,
#[prop_or_default]
pub class: &'static str,
#[prop_or_default]
pub style: &'static str,
}
#[function_component(Group)]
pub fn group(props: &GroupProps) -> Html {
let GroupProps {
label,
group,
selected,
onchange,
children,
class,
style,
} = props.clone();
if group {
html! {
<optgroup label={label} class={class} style={style}>
{ for children.iter().map(|mut child| {
let props = Rc::make_mut(&mut child.props);
let is_selected = props.value == selected;
let onchange = onchange.clone();
let value = props.value;
props.selected = is_selected;
props.on_click = Callback::from(move |_| {
onchange.emit(value.to_string());
});
child
}) }
</optgroup>
}
} else {
html! {
{ for children.iter().map(|mut child| {
let props = Rc::make_mut(&mut child.props);
let is_selected = props.value == selected;
let onchange = onchange.clone();
let value = props.value;
props.selected = is_selected;
props.on_click = Callback::from(move |_| {
onchange.emit(value.to_string());
});
child
}) }
}
}
}
#[derive(Properties, PartialEq, Clone)]
pub struct OptionProps {
#[prop_or_default]
pub value: &'static str,
#[prop_or_default]
pub label: Children,
#[prop_or_default]
pub selected: bool,
#[prop_or_default]
pub disabled: bool,
#[prop_or_default]
pub on_click: Callback<()>,
#[prop_or_default]
pub class: &'static str,
#[prop_or_default]
pub style: &'static str,
#[prop_or_default]
pub selected_class: &'static str,
#[prop_or_default]
pub selected_style: &'static str,
}
#[function_component(Option)]
pub fn option(props: &OptionProps) -> Html {
let OptionProps {
label,
selected,
disabled,
on_click,
class,
style,
selected_style,
selected_class,
..
} = props.clone();
let handle_click = {
let on_click = on_click.clone();
Callback::from(move |_| on_click.emit(()))
};
html! {
<option
class={format!("{} {}", class, if selected { selected_class } else { "" })}
style={format!("{} {}", style, if selected { selected_style } else { "" })}
onclick={move |ev: MouseEvent| {
ev.prevent_default();
handle_click.emit(ev);
}}
disabled={disabled}
>
{ label }
</option>
}
}