Merge pull request #15 from R15-Software-Dev/feature/chips-component
Implement mockup of chips component
This commit was merged in pull request #15.
This commit is contained in:
3 files changed
+178
-3
No files matched your search
@@ -0,0 +1,146 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use leptos::prelude::*;
|
||||||
|
use crate::components::ValueType;
|
||||||
|
|
||||||
|
/// # Chip Component
|
||||||
|
///
|
||||||
|
/// This component defines a single chip for use in the `ChipsList` component. It functions almost
|
||||||
|
/// identically to the `Checkbox` component. It should only be created from within its parent
|
||||||
|
/// `CheckboxList` component.
|
||||||
|
///
|
||||||
|
/// Example usage:
|
||||||
|
/// ```
|
||||||
|
/// view! {
|
||||||
|
/// <Chip
|
||||||
|
/// value="Some Value"
|
||||||
|
/// name="list_name"
|
||||||
|
/// on_change=move || { println!("Some work here.") }
|
||||||
|
/// checked=RwSignal::new(false)
|
||||||
|
/// disabled=RwSignal::new(false)
|
||||||
|
/// />
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[component]
|
||||||
|
pub fn Chip(
|
||||||
|
#[prop(into)] value: String,
|
||||||
|
#[prop(into)] name: String,
|
||||||
|
#[prop(into)] on_change: Callback<(), ()>,
|
||||||
|
#[prop()] checked: Signal<bool>,
|
||||||
|
#[prop(optional)] disabled: RwSignal<bool>,
|
||||||
|
) -> impl IntoView {
|
||||||
|
// Generate a unique id - ensure that the value doesn't contain spaces.
|
||||||
|
// Without this id, checkbox/radio inputs can interfere with each other.
|
||||||
|
let value_no_spaces = value.clone().chars()
|
||||||
|
.filter(|c| !c.is_whitespace())
|
||||||
|
.collect::<String>();
|
||||||
|
let id = format!("{}-{}", name.clone(), value_no_spaces);
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<label for=id>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
class="relative peer shrink-0 hidden"
|
||||||
|
name=name.clone()
|
||||||
|
id=id.clone()
|
||||||
|
prop:checked=checked
|
||||||
|
on:change=move |_| on_change.run(())
|
||||||
|
disabled=disabled />
|
||||||
|
<div class="rounded-full p-2.5
|
||||||
|
cursor-pointer text-base
|
||||||
|
bg-transparent border-1 border-gray-300
|
||||||
|
shadow-md/50 hover:shadow-md/70
|
||||||
|
peer-checked:bg-red-700 peer-checked:border-red-700 peer-checked:text-white
|
||||||
|
peer-disabled:peer-checked:bg-gray-700
|
||||||
|
peer-disabled:peer-checked:border-gray-700
|
||||||
|
peer-disabled:cursor-default
|
||||||
|
transition-all duration-150">
|
||||||
|
<span>{value.clone()}</span>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// # Chips List Component
|
||||||
|
///
|
||||||
|
/// This defines a list of `Chip` components. The list of values is created based on the data from the
|
||||||
|
/// `data_map` prop, which is found using the given `data_member`.
|
||||||
|
///
|
||||||
|
/// Any values selected from the list are stored within the `data_map` immediately as part of a callback
|
||||||
|
/// passed to the `on_change` prop for each individual `Chip` component.
|
||||||
|
#[component]
|
||||||
|
pub fn ChipsList(
|
||||||
|
#[prop(into)] data_member: String,
|
||||||
|
#[prop()] data_map: RwSignal<HashMap<String, ValueType>>,
|
||||||
|
#[prop()] items: Vec<String>,
|
||||||
|
#[prop(default = RwSignal::new(false))] disabled: RwSignal<bool>,
|
||||||
|
#[prop(optional, into)] label: String,
|
||||||
|
) -> impl IntoView {
|
||||||
|
view! {
|
||||||
|
<div class="m-1.5 mt-0 mb-0">
|
||||||
|
<span class="font-bold">{label}</span>
|
||||||
|
<div class="flex flex-row flex-wrap gap-2 mt-1">
|
||||||
|
{items
|
||||||
|
.into_iter()
|
||||||
|
.map(|item| {
|
||||||
|
let checked_signal = Signal::derive({
|
||||||
|
let item_name = item.clone();
|
||||||
|
let data_member = data_member.clone();
|
||||||
|
move || {
|
||||||
|
data_map.get()
|
||||||
|
.get(&data_member)
|
||||||
|
.unwrap_or(&ValueType::List(None))
|
||||||
|
.as_list()
|
||||||
|
.unwrap_or(Some(vec!()))
|
||||||
|
.unwrap_or(vec!())
|
||||||
|
.contains(&ValueType::String(Some(item_name.clone())))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let on_change = {
|
||||||
|
let item_name = item.clone();
|
||||||
|
let data_member = data_member.clone();
|
||||||
|
move || {
|
||||||
|
let result = ValueType::String(Some(item_name.clone()));
|
||||||
|
data_map.update(|map| {
|
||||||
|
// Attempt to get the value type from the hash map.
|
||||||
|
match map.get_mut(&data_member) {
|
||||||
|
Some(value_type) => {
|
||||||
|
// Attempt to get the value as a list.
|
||||||
|
match value_type.as_list().unwrap_or(None) {
|
||||||
|
Some(mut list) => {
|
||||||
|
// Attempt to find the selected value in the selected list.
|
||||||
|
// If it's present, remove it, or vice versa.
|
||||||
|
match list.iter().position(|val| *val == result) {
|
||||||
|
Some(index) => { list.remove(index); },
|
||||||
|
None => { list.push(result); }
|
||||||
|
};
|
||||||
|
// Update the existing value_type entry in the hash map.
|
||||||
|
*value_type = ValueType::List(Some(list));
|
||||||
|
},
|
||||||
|
// Insert a new value_type entry.
|
||||||
|
None => { *value_type = ValueType::List(Some(vec![result])); }
|
||||||
|
};
|
||||||
|
},
|
||||||
|
// Insert a new value_type entry.
|
||||||
|
None => { map.insert(data_member.clone(), ValueType::List(Some(vec![result]))); }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<Chip
|
||||||
|
checked=checked_signal
|
||||||
|
name=data_member.clone()
|
||||||
|
on_change=on_change
|
||||||
|
value=item.clone()
|
||||||
|
disabled=disabled
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view()
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,8 +7,9 @@ mod radio;
|
|||||||
mod row;
|
mod row;
|
||||||
mod select;
|
mod select;
|
||||||
mod text_field;
|
mod text_field;
|
||||||
|
mod chips;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
action_button::*, checkbox::*, loading::*, multi_entry::*, panel::*, radio::*, row::*,
|
action_button::*, checkbox::*, loading::*, multi_entry::*, panel::*, radio::*, row::*,
|
||||||
select::*, text_field::*,
|
select::*, text_field::*, chips::*
|
||||||
};
|
};
|
||||||
+30
-2
@@ -9,9 +9,8 @@ use crate::common::{ExpandableInfo, UserClaims};
|
|||||||
use crate::pages::UnauthenticatedPage;
|
use crate::pages::UnauthenticatedPage;
|
||||||
use crate::components::{
|
use crate::components::{
|
||||||
ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row,
|
ActionButton, CheckboxList, Loading, MultiEntry, OutlinedTextField, Panel, RadioList, Row,
|
||||||
Select,
|
Select, ChipsList
|
||||||
};
|
};
|
||||||
use crate::input;
|
|
||||||
use leptos::leptos_dom::logging::console_log;
|
use leptos::leptos_dom::logging::console_log;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
use leptos_oidc::{Algorithm, AuthLoaded, AuthSignal, Authenticated};
|
||||||
@@ -267,6 +266,35 @@ pub fn HomePage() -> impl IntoView {
|
|||||||
data_map=expandable_react.data
|
data_map=expandable_react.data
|
||||||
/>
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<ChipsList
|
||||||
|
data_member="athletic_requirements"
|
||||||
|
data_map=expandable_react.data
|
||||||
|
items=vec!(
|
||||||
|
"Football", "Soccer", "Cross Country", "Cheerleading",
|
||||||
|
"Swimming", "Wrestling", "Ski", "Basketball",
|
||||||
|
"Lacrosse", "Softball", "Indoor/Outdoor Track",
|
||||||
|
"Golf", "Tennis", "Volleyball"
|
||||||
|
)
|
||||||
|
.into_iter().map(|s| s.to_owned())
|
||||||
|
.collect()
|
||||||
|
disabled=elements_disabled
|
||||||
|
label="Sports/Athletic Requirements"
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<ChipsList
|
||||||
|
data_member="community_involvement"
|
||||||
|
data_map=expandable_react.data
|
||||||
|
items=vec!(
|
||||||
|
"Lion's Club", "Knights of Columbus",
|
||||||
|
"Community Service > 20hrs"
|
||||||
|
)
|
||||||
|
.into_iter().map(|s| s.to_owned())
|
||||||
|
.collect()
|
||||||
|
disabled=elements_disabled
|
||||||
|
label="Required Community Involvement"
|
||||||
|
</Row>
|
||||||
<Row>
|
<Row>
|
||||||
<MultiEntry
|
<MultiEntry
|
||||||
data_map = expandable_react.data
|
data_map = expandable_react.data
|
||||||
|
|||||||
Reference in new issue
Block a user