feat: implement radio button element and rename checkbox

This commit is contained in:
alphafetus committed 2025-08-31 16:25:45 -04:00
1 parent 4985e1596c
commit 2befd9225c
3 files changed
+17 -111

No files matched your search

-99
View File
@@ -1,99 +0,0 @@
use leptos::prelude::*;
#[component]
pub fn Checkbox(
#[prop()] checked: Signal<bool>,
#[prop(into)] on_change: Callback<(), ()>,
#[prop(into)] value: String,
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
) -> impl IntoView {
view! {
<label for=value
class="flex items-center">
<input
// Using the "peer" class links this input with the div "peer" at the same level.
// By using the "peer-checked" selector this div can toggle states based on this
// input's state (checked/unchecked)
class="
relative peer shrink-0
hidden"
type="checkbox"
id={value.clone()}
on:change = move |_| on_change.run(())
prop:checked = checked
disabled = disabled
/>
// This div displays the box for the checkbox, and applies some styling to the child
// span element.
<div
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
[&>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
// and right sides, which is then rotated to appear like a checkmark.
<span
class="absolute left-1/2 top-1/2 block h-3 w-1.5 -translate-x-1/2 -translate-y-1/2
rotate-45 transform border-b-3 border-r-3
border-white transition-opacity duration-150"
></span>
</div>
<span>{value.clone()}</span>
</label>
}
}
#[component]
pub fn CheckboxList(
#[prop(default = RwSignal::new(vec![]))] selected: RwSignal<Vec<String>>,
#[prop()] items: Vec<String>,
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
) -> impl IntoView {
view! {
// Checkbox elements go in here. Each one will have a derived signal
// that checks if the name/value of that checkbox is in the selected items
// vec. If so, display as selected. If not, clear that checkbox.
// Somehow create a derived signal??
<div>
{
items.into_iter().map(|item| {
let is_checked = {
let item_name = item.clone();
move || {
// 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()
disabled = disabled />
}
}).collect::<Vec<_>>()
}
</div>
}
}
+6 -11
View File
@@ -1,19 +1,14 @@
mod action_button; mod action_button;
mod checkbox;
mod loading; mod loading;
mod multi_entry; mod multi_entry;
mod text_field;
mod select;
mod checkboxes;
mod panel; mod panel;
mod radio;
mod row; mod row;
mod select;
mod text_field;
pub use self::{ pub use self::{
action_button::*, action_button::*, checkbox::*, loading::*, multi_entry::*, panel::*, radio::*, row::*,
loading::*, select::*, text_field::*,
multi_entry::*,
text_field::*,
select::*,
checkboxes::*,
panel::*,
row::*
}; };
+11 -1
View File
@@ -9,6 +9,7 @@ use crate::app::Unauthenticated;
use crate::common::{StudentInfo, StudentInfoReactive, UserClaims}; use crate::common::{StudentInfo, StudentInfoReactive, UserClaims};
use crate::components::{ use crate::components::{
ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, Row, Select, ActionButton, CheckboxList, Loading, OutlinedTextField, Panel, Row, Select,
RadioList
}; };
use leptos::leptos_dom::logging::console_log; use leptos::leptos_dom::logging::console_log;
use leptos::prelude::*; use leptos::prelude::*;
@@ -122,6 +123,7 @@ 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); let elements_disabled = RwSignal::new(false);
let radio_value = RwSignal::new(String::from("Test"));
view! { view! {
<div class="flex flex-row"> <div class="flex flex-row">
@@ -171,6 +173,14 @@ pub fn HomePage() -> impl IntoView {
value=select_value value=select_value
disabled=elements_disabled disabled=elements_disabled
/> />
<RadioList
selected=radio_value
items=vec!["Test", "Testing 2"]
.into_iter()
.map(|s| s.into())
.collect()
disabled=elements_disabled
/>
<CheckboxList <CheckboxList
selected=chk_select selected=chk_select
items=vec!["Testing 1", "Testing 2"] items=vec!["Testing 1", "Testing 2"]
@@ -183,7 +193,7 @@ pub fn HomePage() -> impl IntoView {
<Row> <Row>
<ActionButton on:click=move |_| { <ActionButton on:click=move |_| {
console_log( console_log(
format!("Found value {:?}", chk_select.get()).as_str(), format!("Found value {:?}", radio_value.get()).as_str(),
); );
submit_action submit_action
.dispatch(CreateSampleSubmission { .dispatch(CreateSampleSubmission {