This commit is contained in:
tommy
2025-11-07 15:09:10 +08:00
parent 0ae1ef29d5
commit dddab92102

View File

@@ -26,9 +26,7 @@ pub fn Select(
#[props(optional)] on_change: Option<EventHandler<String>>, #[props(optional)] on_change: Option<EventHandler<String>>,
) -> Element { ) -> Element {
let mut open = use_signal(|| false); let mut open = use_signal(|| false);
let current = use_signal(move || selected.clone()); let mut current = use_signal(move || selected.clone());
let on_change_handler = on_change.clone();
let trigger_id = id.unwrap_or_default();
let mut container_ref = use_signal(|| None as Option<Rc<MountedData>>); let mut container_ref = use_signal(|| None as Option<Rc<MountedData>>);
use_effect(move || { use_effect(move || {
@@ -60,87 +58,94 @@ pub fn Select(
onmounted: move |event| { onmounted: move |event| {
container_ref.set(Some(event.data())); container_ref.set(Some(event.data()));
}, },
onblur: { onblur: move |_| {
let mut open_signal = open.clone(); open.set(false);
move |_| {
open_signal.set(false);
}
}, },
button { SelectTrigger {
class: "ui-select-trigger", id: id.clone(),
"data-open": if open() { "true" } else { "false" }, open: open(),
disabled, disabled,
id: trigger_id.clone(), display_text,
"aria-haspopup": "listbox", on_toggle: move |_| {
"aria-expanded": if open() { "true" } else { "false" }, if !disabled {
onclick: { open.set(!open());
let mut open_signal = open.clone();
move |_| {
if !disabled {
let new_state = !open_signal();
open_signal.set(new_state);
}
}
},
span { "{display_text}" }
span {
class: "ui-select-icon",
"aria-hidden": "true",
svg {
view_box: "0 0 16 16",
xmlns: "http://www.w3.org/2000/svg",
path {
d: "M4 6l4 4 4-4",
fill: "none",
stroke: "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round",
}
} }
} }
} }
if open() { if open() {
div { SelectContent {
class: "ui-select-content", options: options.clone(),
div { selected_value,
class: "ui-select-list", on_select: move |value: String| {
for option in options.iter().cloned() { current.set(Some(value.clone()));
{ if let Some(callback) = on_change.as_ref() {
let is_active = selected_value callback.call(value);
.as_ref() }
.map(|value| value == &option.value) open.set(false);
.unwrap_or(false); }
let value = option.value.clone(); }
let handler = on_change_handler.clone(); }
let mut open_signal = open.clone(); }
let mut current_signal = current.clone(); }
}
rsx! { #[component]
button { fn SelectTrigger(
class: "ui-select-item", #[props(into, default)] id: Option<String>,
"data-state": if is_active { "active" } else { "inactive" }, open: bool,
onmousedown: { disabled: bool,
let value = value.clone(); #[props(into)] display_text: String,
let handler = handler.clone(); on_toggle: EventHandler<()>,
move |event| { ) -> Element {
event.prevent_default(); rsx! {
current_signal.set(Some(value.clone())); button {
if let Some(callback) = handler.clone() { class: "ui-select-trigger",
callback.call(value.clone()); "data-open": if open { "true" } else { "false" },
} disabled,
open_signal.set(false); id: id.unwrap_or_default(),
} "aria-haspopup": "listbox",
}, "aria-expanded": if open { "true" } else { "false" },
span { "{option.label}" } onclick: move |_| on_toggle.call(()),
if is_active { span { "{display_text}" }
span { span {
style: "font-size: 0.75rem; opacity: 0.7;", class: "ui-select-icon",
"" "aria-hidden": "true",
} svg {
} view_box: "0 0 16 16",
} xmlns: "http://www.w3.org/2000/svg",
} path {
d: "M4 6l4 4 4-4",
fill: "none",
stroke: "currentColor",
"stroke-width": "1.5",
"stroke-linecap": "round",
"stroke-linejoin": "round",
}
}
}
}
}
}
#[component]
fn SelectContent(
options: Vec<SelectOption>,
selected_value: Option<String>,
on_select: EventHandler<String>,
) -> Element {
rsx! {
div {
class: "ui-select-content",
div {
class: "ui-select-list",
for option in options {
{
let is_active = selected_value.as_ref().map(|v| v == &option.value).unwrap_or(false);
rsx! {
SelectItem {
option,
is_active,
on_select
} }
} }
} }
@@ -149,3 +154,26 @@ pub fn Select(
} }
} }
} }
#[component]
fn SelectItem(option: SelectOption, is_active: bool, on_select: EventHandler<String>) -> Element {
let value = option.value.clone();
rsx! {
button {
class: "ui-select-item",
"data-state": if is_active { "active" } else { "inactive" },
onmousedown: move |event| {
event.prevent_default();
on_select.call(value.clone());
},
span { "{option.label}" }
if is_active {
span {
style: "font-size: 0.75rem; opacity: 0.7;",
""
}
}
}
}
}