feat: implement disabled behavior for all components

This commit is contained in:
alphafetus committed 2025-08-27 13:58:32 -04:00
1 parent 75a8f2d6d4
commit df8b248ae4
4 files changed
+83 -67

No files matched your search

+45 -37
View File
@@ -4,7 +4,8 @@ use leptos::prelude::*;
pub fn Checkbox( pub fn Checkbox(
#[prop()] checked: Signal<bool>, #[prop()] checked: Signal<bool>,
#[prop(into)] on_change: Callback<(), ()>, #[prop(into)] on_change: Callback<(), ()>,
#[prop(into)] value: String #[prop(into)] value: String,
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
) -> impl IntoView { ) -> impl IntoView {
view! { view! {
<label for=value <label for=value
@@ -20,13 +21,16 @@ pub fn Checkbox(
id={value.clone()} id={value.clone()}
on:change = move |_| on_change.run(()) on:change = move |_| on_change.run(())
prop:checked = checked prop:checked = checked
disabled = disabled
/> />
// This div displays the box for the checkbox, and applies some styling to the child // This div displays the box for the checkbox, and applies some styling to the child
// span element. // span element.
<div <div
class="relative inline-block h-5 w-5 m-1 rounded-sm border border-gray-300 bg-white class="relative inline-block h-5 w-5 m-1 mr-2 rounded-sm border border-gray-300 bg-white
transition-bg duration-150 peer-checked:bg-red-700 peer-checked:border-red-700 transition-bg duration-150 peer-checked:bg-red-700 peer-checked:border-red-700
[&>span]:opacity-0 peer-checked:[&>span]:opacity-100" [&>span]:opacity-0 peer-checked:[&>span]:opacity-100
peer-disabled:bg-gray-600/33 peer-disabled:border-gray-600
peer-disabled:peer-checked:bg-gray-600"
> >
// This span is the check icon. It is centered within the div and has a border on the bottom // This span is the check icon. It is centered within the div and has a border on the bottom
// and right sides, which is then rotated to appear like a checkmark. // and right sides, which is then rotated to appear like a checkmark.
@@ -44,7 +48,8 @@ pub fn Checkbox(
#[component] #[component]
pub fn CheckboxList( pub fn CheckboxList(
#[prop(default = RwSignal::new(vec![]))] selected: RwSignal<Vec<String>>, #[prop(default = RwSignal::new(vec![]))] selected: RwSignal<Vec<String>>,
#[prop()] items: Vec<String> #[prop()] items: Vec<String>,
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
) -> impl IntoView { ) -> impl IntoView {
view! { view! {
// Checkbox elements go in here. Each one will have a derived signal // Checkbox elements go in here. Each one will have a derived signal
@@ -53,39 +58,42 @@ pub fn CheckboxList(
// Somehow create a derived signal?? // Somehow create a derived signal??
{ <div>
items.into_iter().map(|item| { {
let is_checked = { items.into_iter().map(|item| {
let item_name = item.clone(); let is_checked = {
move || { let item_name = item.clone();
// Check if this checkbox should be selected move || {
selected.get().contains(&item_name) // Check if this checkbox should be selected
} selected.get().contains(&item_name)
};
let checked_signal = Signal::derive(is_checked);
view! {
<Checkbox
checked = checked_signal
on_change = {
// Clone this value to avoid moving it into the closure
let item_name = item.clone();
move || {
selected.update(|list| {
// Check if the list contains this element. If so, remove it. If not, add it.
if let Some(index) = list.iter().position(|val| *val == item_name) {
list.remove(index);
} else {
list.push(item_name.clone());
}
});
}
} }
// The actual selected values are tracked by this element, not by the checkboxes themselves. };
value = item.clone() />
} let checked_signal = Signal::derive(is_checked);
}).collect::<Vec<_>>()
} view! {
<Checkbox
checked = checked_signal
on_change = {
// Clone this value to avoid moving it into the closure
let item_name = item.clone();
move || {
selected.update(|list| {
// Check if the list contains this element. If so, remove it. If not, add it.
if let Some(index) = list.iter().position(|val| *val == item_name) {
list.remove(index);
} else {
list.push(item_name.clone());
}
});
}
}
// The actual selected values are tracked by this element, not by the checkboxes themselves.
value = item.clone()
disabled = disabled />
}
}).collect::<Vec<_>>()
}
</div>
} }
} }
+6 -4
View File
@@ -16,12 +16,14 @@ pub fn Select(
<label> <label>
{label} {label}
<select <select
class="relative border-2 m-1.5 p-1.5 rounded-md" class="relative border-2 m-1.5 p-1.5 rounded-md
class=(["border-red-700", "bg-transparent"], move || !disabled.get()) transition-border duration-150
class=(["border-gray-600", "pointer-events-none", "bg-gray-600/33"], move || disabled.get()) border-red-700 bg-transparent
disabled:border-gray-600 disabled:pointer-events-none disabled:bg-gray-600/33"
on:change:target=move |e| { on:change:target=move |e| {
value.set(e.target().value()); value.set(e.target().value());
}> }
disabled = disabled >
// This closure handles the display of the options. // This closure handles the display of the options.
{move || { {move || {
value_list.iter().map( value_list.iter().map(
+16 -14
View File
@@ -22,19 +22,21 @@ where
}; };
view! ( view! (
<label> <div>
{ label } <label>
<input { label }
class="border-2 m-1.5 p-1.5 rounded-md bg-transparent mt-6 relative transition-all duration-400" <input
class=(["border-red-700", "bg-transparent"], move || !disabled.get()) class="border-2 m-1.5 p-1.5 rounded-md bg-transparent mt-6 relative transition-all duration-150
class=(["border-gray-600", "pointer-events-none", "bg-gray-600/33"], move || disabled.get()) border-red-700 bg-transparent
r#type={input_type} disabled:border-gray-600 disabled:pointer-events-none disabled:bg-gray-600/33"
disabled={disabled} r#type={input_type}
placeholder={placeholder} disabled={disabled}
prop:name=name placeholder={placeholder}
prop:value={move || value.get().to_string()} // Sets the initial value. prop:name=name
on:input=on_input // Sets the value on each input. prop:value={move || value.get().to_string()} // Sets the initial value.
/> on:input=on_input // Sets the value on each input.
</label> />
</label>
</div>
) )
} }
+16 -12
View File
@@ -155,39 +155,43 @@ pub fn HomePage() -> impl IntoView {
let select_value = RwSignal::new(String::from("Math")); let select_value = RwSignal::new(String::from("Math"));
let chk_select = RwSignal::new(vec!["Testing 2".into()]); let chk_select = RwSignal::new(vec!["Testing 2".into()]);
let elements_disabled = RwSignal::new(false);
view! { view! {
<p> <p>
"Current user's reported full name from the API: " "Current user's reported full name from the API: "
{reactive_info.first_name}" "{reactive_info.last_name} {reactive_info.first_name}" "{reactive_info.last_name}
</p> </p>
<form> <form
<div> class="flex flex-col gap-4">
<div class="flex flex-row gap-2">
<OutlinedTextField <OutlinedTextField
label="First Name".into() label="First Name".into()
placeholder="John".into() placeholder="John".into()
disabled=elements_disabled
value=reactive_info.first_name /> value=reactive_info.first_name />
</div>
<div>
<OutlinedTextField <OutlinedTextField
label="Last Name".into() label="Last Name".into()
placeholder="Smith".into() placeholder="Smith".into()
disabled=elements_disabled
value=reactive_info.last_name /> value=reactive_info.last_name />
</div>
<div>
<OutlinedTextField <OutlinedTextField
label="Testing".into() label="Testing".into()
placeholder="Test".into() placeholder="Test".into()
disabled=elements_disabled
value=reactive_info.math_sat_score /> value=reactive_info.math_sat_score />
</div> </div>
<Select <div class="flex flex-row gap-2">
value_list = vec!["Math".into(), "English".into(), "Science".into()] <Select
value = select_value value_list = vec!["Math".into(), "English".into(), "Science".into()]
/> value = select_value
<div> disabled=elements_disabled
/>
<CheckboxList <CheckboxList
selected = chk_select selected = chk_select
items = vec!["Testing 1".into(), "Testing 2".into()] /> items = vec!["Testing 1".into(), "Testing 2".into()]
disabled=elements_disabled />
</div> </div>
<div> <div>
<ActionButton <ActionButton