use dioxus::prelude::*;
use crate::components::input::{Input, InputProps, InputType};
pub const FORM: &str = "el-form";
pub const FORM_HORIZONTAL: &str = "el-form--horizontal";
pub const FORM_INLINE: &str = "el-form--inline";
pub const FORM_ITEM: &str = "el-form-item";
pub const FORM_ITEM_LABEL: &str = "el-form-item__label";
pub const FORM_ITEM_CONTENT: &str = "el-form-item__content";
pub const FORM_ITEM_ERROR: &str = "el-form-item__error";
pub const FORM_ITEM_HELP: &str = "el-form-item__help";
pub const FORM_ITEM_REQUIRED: &str = "is-required";
pub const FORM_ITEM_ERROR_STATE: &str = "is-error";
pub const SELECT: &str = "el-select";
pub const CHECKBOX: &str = "el-checkbox";
pub const RADIO: &str = "el-radio";
pub const SLIDER: &str = "el-slider";
pub const IS_DISABLED: &str = "is-disabled";
#[derive(Clone, PartialEq)]
pub enum FormItemStatus {
Valid,
Invalid,
Validating,
None,
}
impl FormItemStatus {
pub fn as_class(&self) -> &'static str {
match self {
FormItemStatus::Valid => "is-success",
FormItemStatus::Invalid => "is-error",
FormItemStatus::Validating => "is-validating",
FormItemStatus::None => "",
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct FormItemProps {
pub label: String,
pub children: Element,
#[props(default = false)]
pub required: bool,
#[props(default = FormItemStatus::None)]
pub status: FormItemStatus,
#[props(default)]
pub error_message: Option<String>,
#[props(default)]
pub help_text: Option<String>,
#[props(default = 120)]
pub label_width: u32,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn FormItem(props: FormItemProps) -> Element {
let mut class_names = vec![FORM_ITEM.to_string()];
if !props.status.as_class().is_empty() {
class_names.push(props.status.as_class().to_string());
}
if props.required {
class_names.push("is-required".to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
let label_style = format!("width: {}px; display: inline-block;", props.label_width);
rsx! {
div {
class: "{class_string}",
style: "{style_string}",
if !props.label.is_empty() {
label {
class: "el-form-item__label",
style: "{label_style}",
"{props.label}"
}
}
div {
class: "el-form-item__content",
{props.children}
if let Some(ref error_msg) = props.error_message {
div {
class: "el-form-item__error",
"{error_msg}"
}
}
if let Some(ref help_text) = props.help_text {
div {
class: "el-form-item__help",
"{help_text}"
}
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct FormProps {
pub children: Element,
#[props(default = "horizontal".to_string())]
pub layout: String,
#[props(default = "right".to_string())]
pub label_position: String,
#[props(default = 120)]
pub label_width: u32,
#[props(default)]
pub size: Option<String>,
#[props(default = true)]
pub show_asterisk: bool,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
#[props(default)]
pub on_submit: Option<EventHandler<FormEvent>>,
}
#[component]
pub fn Form(props: FormProps) -> Element {
let mut class_names = vec![FORM.to_string()];
class_names.push(format!("el-form--{}", props.layout));
if let Some(ref size) = props.size {
class_names.push(format!("el-form--{}", size));
}
if props.show_asterisk {
class_names.push("el-form--show-asterisk".to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
let form_style = format!(
"--el-form-label-width: {}px; {}",
props.label_width,
style_string
);
rsx! {
form {
class: "{class_string}",
style: "{form_style}",
onsubmit: move |event| {
event.prevent_default();
if let Some(handler) = props.on_submit {
handler.call(event);
}
},
{props.children}
}
}
}
#[derive(Clone, PartialEq)]
pub struct SelectOption {
pub value: String,
pub label: String,
pub disabled: bool,
}
#[derive(Props, Clone, PartialEq)]
pub struct SelectProps {
#[props(default)]
pub value: Option<String>,
pub options: Vec<SelectOption>,
#[props(default)]
pub placeholder: Option<String>,
#[props(default = false)]
pub disabled: bool,
#[props(default = false)]
pub clearable: bool,
#[props(default)]
pub on_change: Option<EventHandler<FormEvent>>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn Select(props: SelectProps) -> Element {
let mut class_names = vec![SELECT.to_string()];
if props.disabled {
class_names.push(IS_DISABLED.to_string());
}
if props.clearable {
class_names.push("is-clearable".to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
let selected_option = props.options.iter()
.find(|opt| Some(opt.value.clone()) == props.value)
.map(|opt| opt.label.clone())
.unwrap_or_default();
rsx! {
div {
class: "{class_string}",
style: "{style_string}",
div {
class: "el-select__wrapper",
div {
class: "el-select__selection",
if selected_option.is_empty() {
if let Some(ref placeholder) = props.placeholder {
span {
class: "el-select__placeholder",
"{placeholder}"
}
}
} else {
span {
class: "el-select__selected-item",
"{selected_option}"
}
}
if props.clearable && !selected_option.is_empty() {
span {
class: "el-select__clear",
i {
class: "el-icon-close"
}
}
}
}
i {
class: "el-select__caret el-icon-arrow-down"
}
}
select {
class: "el-select__input",
disabled: props.disabled,
value: props.value.unwrap_or_default(),
onchange: move |event| {
if let Some(handler) = props.on_change {
handler.call(event);
}
},
for option in props.options.iter() {
option {
value: "{option.value}",
disabled: option.disabled,
"{option.label}"
}
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct CheckboxProps {
#[props(default)]
pub label: Option<String>,
#[props(default = false)]
pub checked: bool,
#[props(default = false)]
pub disabled: bool,
#[props(default)]
pub on_change: Option<EventHandler<FormEvent>>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn Checkbox(props: CheckboxProps) -> Element {
let mut class_names = vec![CHECKBOX.to_string()];
if props.checked {
class_names.push("is-checked".to_string());
}
if props.disabled {
class_names.push(IS_DISABLED.to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
rsx! {
label {
class: "{class_string}",
style: "{style_string}",
span {
class: "el-checkbox__input",
input {
r#type: "checkbox",
checked: props.checked,
disabled: props.disabled,
onchange: move |event| {
if let Some(handler) = props.on_change {
handler.call(event);
}
},
}
span {
class: "el-checkbox__inner"
}
}
if let Some(ref label_text) = props.label {
span {
class: "el-checkbox__label",
"{label_text}"
}
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct RadioProps {
pub label: String,
pub value: String,
#[props(default = false)]
pub checked: bool,
#[props(default = false)]
pub disabled: bool,
#[props(default)]
pub name: Option<String>,
#[props(default)]
pub on_change: Option<EventHandler<FormEvent>>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn Radio(props: RadioProps) -> Element {
let mut class_names = vec![RADIO.to_string()];
if props.checked {
class_names.push("is-checked".to_string());
}
if props.disabled {
class_names.push(IS_DISABLED.to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
rsx! {
label {
class: "{class_string}",
style: "{style_string}",
span {
class: "el-radio__input",
input {
r#type: "radio",
name: props.name.unwrap_or_default(),
value: props.value,
checked: props.checked,
disabled: props.disabled,
onchange: move |event| {
if let Some(handler) = props.on_change {
handler.call(event);
}
},
}
span {
class: "el-radio__inner"
}
}
span {
class: "el-radio__label",
"{props.label}"
}
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct SliderProps {
#[props(default = 0.0)]
pub value: f64,
#[props(default = 0.0)]
pub min: f64,
#[props(default = 100.0)]
pub max: f64,
#[props(default = 1.0)]
pub step: f64,
#[props(default = false)]
pub disabled: bool,
#[props(default = true)]
pub show_tooltip: bool,
#[props(default)]
pub on_change: Option<EventHandler<FormEvent>>,
#[props(default)]
pub on_input: Option<EventHandler<FormEvent>>,
#[props(default)]
pub class: Option<String>,
#[props(default)]
pub style: Option<String>,
}
#[component]
pub fn Slider(props: SliderProps) -> Element {
let mut class_names = vec![SLIDER.to_string()];
if props.disabled {
class_names.push(IS_DISABLED.to_string());
}
if props.show_tooltip {
class_names.push("show-tooltip".to_string());
}
if let Some(ref custom_class) = props.class {
class_names.push(custom_class.to_string());
}
let class_string = class_names.join(" ");
let style_string = props.style.as_ref().cloned().unwrap_or_default();
let percentage = if props.max > props.min {
((props.value - props.min) / (props.max - props.min) * 100.0).clamp(0.0, 100.0)
} else {
0.0
};
rsx! {
div {
class: "{class_string}",
style: "{style_string}",
div {
class: "el-slider__runway",
div {
class: "el-slider__bar",
style: "width: {percentage}%"
}
div {
class: "el-slider__button-wrapper",
style: "left: {percentage}%",
div {
class: "el-slider__button"
}
if props.show_tooltip {
div {
class: "el-tooltip__popper is-dark",
"{props.value as i32}"
}
}
}
}
input {
r#type: "range",
min: props.min,
max: props.max,
step: props.step,
value: props.value,
disabled: props.disabled,
onchange: move |event| {
if let Some(handler) = props.on_change {
handler.call(event);
}
},
oninput: move |event| {
if let Some(handler) = props.on_input {
handler.call(event);
}
},
}
}
}
}