use hikari_palette::TypedClass;
use hikari_palette::classes::SelectionClassNew;
use tairitsu_style::ClassesBuilder;
use crate::{prelude::*, styled::StyledComponent};
pub struct SelectionComponent;
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum SelectionType {
#[default]
Checkbox,
Radio,
}
#[define_props]
pub struct SelectionProps {
#[default]
pub row_keys: Vec<String>,
#[default]
pub selection_type: SelectionType,
#[default(false)]
pub fixed_column: bool,
pub total_rows: u32,
#[default]
pub available_keys: Vec<String>,
#[default]
pub class: String,
pub on_change: Option<EventHandler<Vec<String>>>,
}
#[component]
pub fn Selection(props: SelectionProps) -> Element {
let mut selected = use_signal(|| props.row_keys.clone());
let mut is_all_selected = use_signal(|| false);
let mut is_indeterminate = use_signal(|| false);
let available_keys = props.available_keys.clone();
let available_keys_for_effect = available_keys.clone();
let available_keys_for_select_all = available_keys.clone();
let selected_for_effect = selected.clone();
let is_all_selected_for_effect = is_all_selected.clone();
let is_indeterminate_for_effect = is_indeterminate.clone();
use_effect(move || {
let total_selected = selected_for_effect.get().len();
let total_available = available_keys_for_effect.len();
is_all_selected_for_effect.set(total_selected > 0 && total_selected == total_available);
is_indeterminate_for_effect.set(total_selected > 0 && total_selected < total_available);
});
let selected_for_select_all = selected.clone();
let is_all_selected_for_select_all = is_all_selected.clone();
let on_change_for_select_all = props.on_change.clone();
let handle_select_all = move |_| {
let new_selection = if is_all_selected_for_select_all.get() {
Vec::new()
} else {
available_keys_for_select_all.clone()
};
selected_for_select_all.set(new_selection.clone());
if let Some(handler) = on_change_for_select_all.as_ref() {
handler.call(new_selection);
}
};
let selected_for_row_select = selected.clone();
let on_change_for_row_select = props.on_change.clone();
let mut handle_row_select = move |row_key: String| {
let mut new_selection = selected_for_row_select.get();
match props.selection_type {
SelectionType::Checkbox => {
if let Some(pos) = new_selection.iter().position(|k| k == &row_key) {
new_selection.remove(pos);
} else {
new_selection.push(row_key);
}
}
SelectionType::Radio => {
new_selection.clear();
new_selection.push(row_key);
}
}
selected_for_row_select.set(new_selection.clone());
if let Some(handler) = on_change_for_row_select.as_ref() {
handler.call(new_selection);
}
};
let selected_for_is_row = selected.clone();
let is_row_selected =
move |key: &str| -> bool { selected_for_is_row.get().iter().any(|k| k == key) };
let get_input_type = || match props.selection_type {
SelectionType::Checkbox => "checkbox",
SelectionType::Radio => "radio",
};
let container_classes = ClassesBuilder::new()
.add_typed(SelectionClassNew::RowSelection)
.add_typed(SelectionClassNew::SelectionColumn)
.add_typed_if(SelectionClassNew::SelectionFixed, props.fixed_column)
.build();
let column_classes = ClassesBuilder::new()
.add_typed(SelectionClassNew::SelectionColumn)
.add_typed_if(SelectionClassNew::SelectionFixed, props.fixed_column)
.build();
let is_checkbox_type = props.selection_type == SelectionType::Checkbox;
let is_all_selected_for_template = is_all_selected.clone();
let selection_items: Vec<Element> = {
let selected_for_item = selected.clone();
let on_change_for_item = props.on_change.clone();
let selection_type = props.selection_type;
available_keys.iter().map(move |key| {
let key_clone = key.clone();
let key_for_check = key.clone();
let checked = selected_for_item.read().iter().any(|k| k == &key_for_check);
let selected_for_change = selected_for_item.clone();
let on_change_for_change = on_change_for_item.clone();
let handle_change = move |_| {
let mut new_selection = selected_for_change.get();
match selection_type {
SelectionType::Checkbox => {
if let Some(pos) = new_selection.iter().position(|k| k == &key_clone) {
new_selection.remove(pos);
} else {
new_selection.push(key_clone.clone());
}
}
SelectionType::Radio => {
new_selection.clear();
new_selection.push(key_clone.clone());
}
}
selected_for_change.set(new_selection.clone());
if let Some(handler) = on_change_for_change.as_ref() {
handler.call(new_selection);
}
};
rsx! {
div { class: {SelectionClassNew::SelectionRow.class_name()},
label { class: {SelectionClassNew::SelectionItem.class_name()},
input {
class: {SelectionClassNew::SelectionCheckbox.class_name()},
r#type: get_input_type(),
checked,
name: if selection_type == SelectionType::Radio { "hi-selection-radio-group" } else { "" },
onchange: handle_change,
}
"{key}"
}
}
}
}).collect()
};
rsx! {
div { class: {container_classes},
div { class: {column_classes},
if is_checkbox_type {
div { class: {SelectionClassNew::SelectionHeader.class_name()},
label { class: {SelectionClassNew::SelectionAll.class_name()},
input {
class: {SelectionClassNew::SelectionCheckbox.class_name()},
r#type: "checkbox",
checked: is_all_selected_for_template.get(),
onchange: handle_select_all,
}
}
}
}
..selection_items,
}
}
}
}
#[define_props]
pub struct RowSelectionProps {
pub row_key: String,
#[default]
pub selected_keys: Vec<String>,
#[default]
pub selection_type: SelectionType,
#[default]
pub class: String,
pub on_select: Option<EventHandler<String>>,
}
#[component]
pub fn RowSelection(props: RowSelectionProps) -> Element {
let is_selected = props.selected_keys.iter().any(|k| k == &props.row_key);
let handle_change = move |_| {
if let Some(handler) = props.on_select.as_ref() {
handler.call(props.row_key.clone());
}
};
let _input_type = match props.selection_type {
SelectionType::Checkbox => "checkbox",
SelectionType::Radio => "radio",
};
let container_classes = ClassesBuilder::new()
.add_typed(SelectionClassNew::RowSelection)
.add(&props.class)
.build();
let custom_classes = ClassesBuilder::new()
.add_typed(SelectionClassNew::RowSelectionCustom)
.add_typed_if(SelectionClassNew::RowSelectionChecked, is_selected)
.build();
let is_checkbox = props.selection_type == SelectionType::Checkbox;
let is_radio = props.selection_type == SelectionType::Radio;
let show_checkbox_icon = is_checkbox && is_selected;
let show_radio_dot = is_radio && is_selected;
let input_name = if is_radio {
"hi-selection-radio-group"
} else {
""
};
rsx! {
div { class: {container_classes},
label { class: {SelectionClassNew::RowSelectionLabel.class_name()},
input {
class: {SelectionClassNew::RowSelectionInput.class_name()},
r#type: "checkbox",
checked: is_selected,
name: input_name,
onchange: handle_change,
}
span { class: {custom_classes},
if show_checkbox_icon {
svg {
xmlns: "http://www.w3.org/2000/svg",
fill: "none",
view_box: "0 0 24 24",
stroke_width: 3,
stroke: "currentColor",
path {
stroke_linecap: "round",
stroke_linejoin: "round",
d: "M4.5 12.75l6 6 9-13.5",
}
}
}
if show_radio_dot {
span { class: {SelectionClassNew::RowSelectionRadioDot.class_name()} }
}
}
}
}
}
}
impl StyledComponent for SelectionComponent {
fn styles() -> &'static str {
include_str!(concat!(env!("OUT_DIR"), "/styles/selection.css"))
}
fn name() -> &'static str {
"selection"
}
}