use blinc_core::reactive::Signal;
use blinc_layout::binding::{IntoReactive, Reactive};
use blinc_layout::div::ElementBuilder;
use blinc_layout::tree::{LayoutNodeId, LayoutTree};
use blinc_layout::widgets::toggle as layout_toggle;
use blinc_theme::TypographyTokens;
use std::sync::Arc;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ToggleVariant {
#[default]
Default,
Outline,
}
impl ToggleVariant {
fn css_class(self) -> &'static str {
match self {
ToggleVariant::Default => "cn-toggle--default",
ToggleVariant::Outline => "cn-toggle--outline",
}
}
pub(crate) fn data_value(self) -> &'static str {
match self {
ToggleVariant::Default => "default",
ToggleVariant::Outline => "outline",
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum ToggleSize {
#[default]
Medium,
Small,
Large,
}
impl ToggleSize {
pub fn height(self) -> f32 {
match self {
ToggleSize::Small => 32.0,
ToggleSize::Medium => 36.0,
ToggleSize::Large => 40.0,
}
}
pub fn padding_x(self) -> f32 {
match self {
ToggleSize::Small => 8.0,
ToggleSize::Medium => 12.0,
ToggleSize::Large => 16.0,
}
}
pub fn icon_size(self) -> f32 {
match self {
ToggleSize::Small => 14.0,
ToggleSize::Medium => 16.0,
ToggleSize::Large => 18.0,
}
}
pub fn font_size(self, typography: &TypographyTokens) -> f32 {
match self {
ToggleSize::Small => typography.text_xs,
ToggleSize::Medium => typography.text_sm,
ToggleSize::Large => typography.text_base,
}
}
pub fn radius_token(self) -> blinc_theme::RadiusToken {
match self {
ToggleSize::Small => blinc_theme::RadiusToken::Sm,
ToggleSize::Medium | ToggleSize::Large => blinc_theme::RadiusToken::Default,
}
}
pub(crate) fn data_value(self) -> &'static str {
match self {
ToggleSize::Small => "sm",
ToggleSize::Medium => "md",
ToggleSize::Large => "lg",
}
}
fn css_class(self) -> &'static str {
match self {
ToggleSize::Small => "cn-toggle--sm",
ToggleSize::Medium => "cn-toggle--md",
ToggleSize::Large => "cn-toggle--lg",
}
}
}
#[derive(Clone)]
struct ToggleConfig {
on: Signal<bool>,
variant: ToggleVariant,
size: ToggleSize,
label: Option<Reactive<String>>,
icon: Option<String>,
aria_label: Option<String>,
disabled: Reactive<bool>,
on_change: Option<Arc<dyn Fn(bool) + Send + Sync>>,
css_element_id: Option<String>,
}
pub struct Toggle {
inner: layout_toggle::ToggleBuilder,
}
impl Toggle {
fn from_config(config: ToggleConfig) -> Self {
let mut inner = layout_toggle::toggle(config.on)
.class("cn-toggle")
.class("tabindex=0")
.class(config.variant.css_class())
.class(config.size.css_class())
.variant(config.variant.data_value())
.size(config.size.data_value())
.icon_size(config.size.icon_size())
.disabled(config.disabled);
if let Some(label) = config.label {
inner = inner.label(label);
}
if let Some(icon) = config.icon {
inner = inner.icon(icon);
}
if let Some(id) = config.css_element_id {
inner = inner.id(&id);
}
if let Some(cb) = config.on_change {
inner = inner.on_change(move |on| cb(on));
}
let _ = config.aria_label;
Self { inner }
}
}
impl ElementBuilder for Toggle {
fn build(&self, tree: &mut LayoutTree) -> LayoutNodeId {
self.inner.build(tree)
}
fn render_props(&self) -> blinc_layout::element::RenderProps {
self.inner.render_props()
}
fn children_builders(&self) -> &[Box<dyn ElementBuilder>] {
self.inner.children_builders()
}
fn element_type_id(&self) -> blinc_layout::div::ElementTypeId {
self.inner.element_type_id()
}
fn semantic_type_name(&self) -> Option<&'static str> {
Some("toggle")
}
fn layout_style(&self) -> Option<&taffy::Style> {
self.inner.layout_style()
}
fn event_handlers(&self) -> Option<&blinc_layout::event_handler::EventHandlers> {
self.inner.event_handlers()
}
fn element_id(&self) -> Option<&str> {
self.inner.element_id()
}
fn element_classes(&self) -> &[Arc<str>] {
self.inner.element_classes()
}
}
pub struct ToggleBuilder {
config: ToggleConfig,
built: std::cell::OnceCell<Toggle>,
}
impl ToggleBuilder {
#[track_caller]
pub fn new(on: Signal<bool>) -> Self {
Self {
config: ToggleConfig {
on,
variant: ToggleVariant::default(),
size: ToggleSize::default(),
label: None,
icon: None,
aria_label: None,
disabled: Reactive::Const(false),
on_change: None,
css_element_id: None,
},
built: std::cell::OnceCell::new(),
}
}
fn get_or_build(&self) -> &Toggle {
::blinc_layout::build_once::build_once(&self.built, || {
Toggle::from_config(self.config.clone())
})
}
pub fn variant(mut self, variant: ToggleVariant) -> Self {
self.config.variant = variant;
self
}
pub fn size(mut self, size: ToggleSize) -> Self {
self.config.size = size;
self
}
pub fn label(mut self, label: impl IntoReactive<String>) -> Self {
self.config.label = Some(label.into_reactive());
self
}
pub fn icon(mut self, svg_markup: impl Into<String>) -> Self {
self.config.icon = Some(svg_markup.into());
self
}
pub fn aria_label(mut self, label: impl Into<String>) -> Self {
self.config.aria_label = Some(label.into());
self
}
pub fn disabled(mut self, disabled: impl IntoReactive<bool>) -> Self {
self.config.disabled = disabled.into_reactive();
self
}
pub fn on_change<F>(mut self, handler: F) -> Self
where
F: Fn(bool) + Send + Sync + 'static,
{
self.config.on_change = Some(Arc::new(handler));
self
}
pub fn id(mut self, id: impl Into<String>) -> Self {
self.config.css_element_id = Some(id.into());
self
}
}
impl ElementBuilder for ToggleBuilder {
fn build(&self, tree: &mut LayoutTree) -> LayoutNodeId {
self.get_or_build().build(tree)
}
fn render_props(&self) -> blinc_layout::element::RenderProps {
self.get_or_build().render_props()
}
fn children_builders(&self) -> &[Box<dyn ElementBuilder>] {
self.get_or_build().children_builders()
}
fn element_type_id(&self) -> blinc_layout::div::ElementTypeId {
self.get_or_build().element_type_id()
}
fn semantic_type_name(&self) -> Option<&'static str> {
Some("toggle")
}
fn layout_style(&self) -> Option<&taffy::Style> {
self.get_or_build().layout_style()
}
fn event_handlers(&self) -> Option<&blinc_layout::event_handler::EventHandlers> {
self.get_or_build().event_handlers()
}
fn element_id(&self) -> Option<&str> {
self.get_or_build().element_id()
}
fn element_classes(&self) -> &[Arc<str>] {
self.get_or_build().element_classes()
}
}
#[track_caller]
pub fn toggle(on: Signal<bool>) -> ToggleBuilder {
ToggleBuilder::new(on)
}