use tablo_ui::{checkbox as ui_checkbox, switch as ui_switch};
use topcoat::{context::Cx, view::*};
pub trait Control: Send + Sync {
fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a>;
}
#[derive(Debug, Clone)]
pub struct ControlInput {
name: String,
id: String,
value: Option<String>,
required: bool,
invalid: bool,
described_by: Option<String>,
disabled: bool,
}
impl ControlInput {
pub(crate) fn new(
name: &str,
id: &str,
value: Option<&str>,
required: bool,
invalid: bool,
described_by: Option<String>,
) -> Self {
Self {
name: name.to_string(),
id: id.to_string(),
value: value.map(str::to_string),
required,
invalid,
described_by,
disabled: false,
}
}
pub(crate) fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
pub fn name(&self) -> &str {
&self.name
}
pub fn id(&self) -> &str {
&self.id
}
pub fn value(&self) -> Option<&str> {
self.value.as_deref()
}
pub fn required(&self) -> bool {
self.required
}
pub fn invalid(&self) -> bool {
self.invalid
}
pub fn described_by(&self) -> Option<&str> {
self.described_by.as_deref()
}
pub fn is_disabled(&self) -> bool {
self.disabled
}
pub fn attributes(&self, cx: &Cx) -> Attributes {
let required = self.required;
attributes! {
id=(self.id.clone())
name=(self.name.clone())
value=(self.value.clone())
required=(required)
disabled=(self.disabled)
aria-required=(required.then_some("true"))
aria-invalid=(if self.invalid { "true" } else { "false" })
aria-describedby=(self.described_by.clone())
}
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Toggle;
impl Control for Toggle {
fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a> {
boolean(cx, input, false)
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct Switch;
impl Control for Switch {
fn render<'a>(&self, cx: &'a Cx, input: ControlInput) -> BoxView<'a> {
boolean(cx, input, true)
}
}
fn boolean<'a>(cx: &'a Cx, input: ControlInput, switch: bool) -> BoxView<'a> {
let name = input.name().to_string();
let id = input.id().to_string();
let checked = input.value().is_some_and(|v| v.trim() == "true");
let invalid = if input.invalid() { "true" } else { "false" };
let described_by = input.described_by().map(str::to_string);
let disabled = input.is_disabled();
let required = input.required();
let attrs = attributes! {
id=(id)
name=(name.clone())
value="true"
checked=(checked)
required=(required)
aria-required=(required.then_some("true"))
disabled=(disabled)
aria-invalid=(invalid)
aria-describedby=(described_by)
};
view! {
<input type="hidden" name=(name) value="false" disabled=(disabled)>
if switch {
ui_switch(attrs: attrs)
} else {
ui_checkbox(attrs: attrs)
}
}
.boxed()
}