use super::*;
#[component]
pub fn euv_select(node: VirtualNode<EuvSelectProps>) -> VirtualNode {
let EuvSelectProps {
value,
options,
placeholder,
label: label_text,
disabled,
on_change,
}: EuvSelectProps = node.try_get_props().unwrap_or_default();
let open: Signal<bool> = use_euv_select_open();
let selected: String = value.get();
let selected_label: Option<&'static str> = options
.iter()
.find(|option: &&EuvSelectOption| option.value == selected)
.map(|option: &EuvSelectOption| option.label);
let trigger_text: &'static str = selected_label.unwrap_or(placeholder);
let has_selection: bool = selected_label.is_some();
let is_empty: bool = options.is_empty();
let entries: Vec<SelectPanelEntry> = select_panel_entries(options);
let panel_items: Vec<VirtualNode> = if is_empty {
vec![html! {
div {
class: c_euv_select_empty()
"No options available"
}
}]
} else {
entries
.into_iter()
.map(|entry: SelectPanelEntry| match entry {
SelectPanelEntry::Caption(caption) => html! {
div {
class: c_euv_select_group()
caption
}
},
SelectPanelEntry::Option(option) => select_option_row(
option,
option.value == selected,
value,
open,
on_change.clone(),
),
})
.collect()
};
html! {
div {
class: c_euv_select()
if !label_text.is_empty() {
div {
class: c_form_label()
label_text
}
}
button {
type: "button"
class: c_euv_select_trigger()
disabled: boolean_attr(disabled)
onclick: select_toggle(open, disabled)
span {
class: if has_selection {
c_euv_select_value()
} else {
c_euv_select_placeholder()
}
trigger_text
}
span {
class: c_euv_select_caret()
"▾"
}
}
div {
class: c_euv_select_panel()
class: if { open } {
c_euv_select_panel_open()
} else {
c_euv_select_panel_closed()
}
panel_items
}
}
}
}
pub fn use_euv_select_open() -> Signal<bool> {
HookContext::use_hook(|| Signal::create(false))
}
fn select_panel_entries(options: Vec<EuvSelectOption>) -> Vec<SelectPanelEntry> {
let mut entries: Vec<SelectPanelEntry> = Vec::with_capacity(options.len());
let mut groups: Vec<(&'static str, Vec<EuvSelectOption>)> = Vec::new();
for option in options {
if option.group.is_empty() {
entries.push(SelectPanelEntry::Option(option));
continue;
}
let existing: Option<usize> =
groups
.iter()
.position(|(caption, _): &(&'static str, Vec<EuvSelectOption>)| {
*caption == option.group
});
match existing {
Some(index) => groups[index].1.push(option),
None => groups.push((option.group, vec![option])),
}
}
for (caption, bucket) in groups {
entries.push(SelectPanelEntry::Caption(caption));
for option in bucket {
entries.push(SelectPanelEntry::Option(option));
}
}
entries
}
fn select_option_row(
option: EuvSelectOption,
is_selected: bool,
value: Signal<String>,
open: Signal<bool>,
on_change: Option<Rc<dyn Fn(&'static str)>>,
) -> VirtualNode {
let disabled: bool = option.disabled;
let handler: Option<Rc<dyn Fn(Event)>> = if disabled {
None
} else {
select_option(value, open, on_change, option.value)
};
html! {
button {
type: "button"
class: if disabled {
c_euv_select_option_disabled()
} else if is_selected {
c_euv_select_option_active()
} else {
c_euv_select_option()
}
disabled: boolean_attr(disabled)
onclick: handler
option.label
}
}
}
fn select_toggle(open: Signal<bool>, disabled: bool) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
if disabled {
return;
}
let is_open: bool = open.get();
open.set(!is_open);
}))
}
fn select_option(
value: Signal<String>,
open: Signal<bool>,
on_change: Option<Rc<dyn Fn(&'static str)>>,
option_value: &'static str,
) -> Option<Rc<dyn Fn(Event)>> {
Some(Rc::new(move |_: Event| {
value.set(String::from(option_value));
if let Some(callback) = &on_change {
callback(option_value);
}
open.set(false);
}))
}
fn boolean_attr(flag: bool) -> &'static str {
if flag { "true" } else { "" }
}