use blinc_core::Color;
use blinc_core::reactive::{ReactiveGraph, Signal, computed};
use blinc_layout::binding::{IntoReactive, Reactive};
use blinc_layout::div::ElementBuilder;
use blinc_layout::prelude::*;
use blinc_layout::svg::svg;
use blinc_layout::tree::{LayoutNodeId, LayoutTree};
use blinc_layout::{InstanceKey, Interaction, units};
use blinc_theme::{ColorToken, ThemeState};
use std::sync::Arc;
use crate::components::toggle::{ToggleSize, ToggleVariant};
#[derive(Clone)]
pub struct ToggleItem {
value: String,
label: Option<String>,
icon: Option<String>,
aria_label: Option<String>,
disabled: bool,
}
impl ToggleItem {
pub fn new(value: impl Into<String>) -> Self {
Self {
value: value.into(),
label: None,
icon: None,
aria_label: None,
disabled: false,
}
}
pub fn label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn icon(mut self, svg_markup: impl Into<String>) -> Self {
self.icon = Some(svg_markup.into());
self
}
pub fn aria_label(mut self, label: impl Into<String>) -> Self {
self.aria_label = Some(label.into());
self
}
pub fn disabled(mut self, disabled: bool) -> Self {
self.disabled = disabled;
self
}
}
pub fn toggle_item(value: impl Into<String>) -> ToggleItem {
ToggleItem::new(value)
}
#[allow(clippy::type_complexity)]
#[derive(Clone)]
struct ToggleGroupConfig {
value: Signal<String>,
items: Vec<ToggleItem>,
variant: ToggleVariant,
size: ToggleSize,
disabled: Reactive<bool>,
gap: Option<f32>,
on_change: Option<Arc<dyn Fn(&str) + Send + Sync>>,
}
pub struct ToggleGroup {
inner: Div,
}
impl ToggleGroup {
fn with_config(instance_key: &InstanceKey, config: ToggleGroupConfig) -> Self {
let mut row = div()
.class("cn-toggle-group")
.class("blinc-toggle-group")
.attr("data-variant", config.variant.data_value())
.attr("data-size", config.size.data_value())
.flex_row()
.items_center();
if let Some(gap) = config.gap {
row = row.gap(gap);
}
for item in &config.items {
row = row.child(build_item(instance_key, &config, item));
}
Self { inner: row }
}
}
fn build_item(
_instance_key: &InstanceKey,
config: &ToggleGroupConfig,
item: &ToggleItem,
) -> impl ElementBuilder + 'static {
let group = config.value;
let item_value = item.value.clone();
let item_value_for_click = item.value.clone();
let item_label = item.label.clone();
let item_icon = item.icon.clone();
let disabled = if item.disabled {
Reactive::Const(true)
} else {
config.disabled.clone()
};
let on_change = config.on_change.clone();
let icon_size = config.size.icon_size();
let _ = item.aria_label.clone();
let is_on = {
let value = item_value.clone();
computed(move |g: &ReactiveGraph| g.get(group).unwrap_or_default() == value)
};
let is_off = {
let value = item_value;
computed(move |g: &ReactiveGraph| g.get(group).unwrap_or_default() != value)
};
let mut body = div()
.class("blinc-toggle")
.class("cn-toggle")
.class("tabindex=0")
.attr_when("data-state", "on", &is_on)
.attr_when("data-state", "off", &is_off)
.attr_when("checked", "", &is_on)
.attr_when("disabled", "", disabled.clone())
.cursor_pointer();
if let Some(ref icon_svg) = item_icon {
body = body.child(svg(icon_svg).size(icon_size, icon_size).internal());
}
if let Some(ref label_text) = item_label {
body = body.child(text(label_text));
}
body.on_click(move |_| {
if disabled.now().unwrap_or(false) {
return;
}
if group.try_get().as_deref() != Some(item_value_for_click.as_str()) {
group.set(item_value_for_click.clone());
if let Some(ref cb) = on_change {
cb(&item_value_for_click);
}
}
})
}
impl ElementBuilder for ToggleGroup {
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_group")
}
fn layout_style(&self) -> Option<&taffy::Style> {
self.inner.layout_style()
}
fn event_handlers(&self) -> Option<&blinc_layout::event_handler::EventHandlers> {
ElementBuilder::event_handlers(&self.inner)
}
fn element_id(&self) -> Option<&str> {
self.inner.element_id()
}
fn element_classes(&self) -> &[Arc<str>] {
self.inner.element_classes()
}
}
pub struct ToggleGroupBuilder {
key: InstanceKey,
config: ToggleGroupConfig,
built: std::cell::OnceCell<ToggleGroup>,
}
impl ToggleGroupBuilder {
#[track_caller]
pub fn new(value: Signal<String>) -> Self {
Self {
key: InstanceKey::new("toggle_group"),
config: ToggleGroupConfig {
value,
items: Vec::new(),
variant: ToggleVariant::default(),
size: ToggleSize::default(),
disabled: Reactive::Const(false),
gap: None,
on_change: None,
},
built: std::cell::OnceCell::new(),
}
}
fn get_or_build(&self) -> &ToggleGroup {
::blinc_layout::build_once::build_once(&self.built, || {
ToggleGroup::with_config(&self.key, self.config.clone())
})
}
pub fn key(mut self, key: impl Into<String>) -> Self {
self.key = InstanceKey::explicit(key);
self
}
pub fn item(mut self, item: ToggleItem) -> Self {
self.config.items.push(item);
self
}
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 disabled(mut self, disabled: impl IntoReactive<bool>) -> Self {
self.config.disabled = disabled.into_reactive();
self
}
pub fn gap(mut self, gap: f32) -> Self {
self.config.gap = Some(gap);
self
}
pub fn on_change<F>(mut self, handler: F) -> Self
where
F: Fn(&str) + Send + Sync + 'static,
{
self.config.on_change = Some(Arc::new(handler));
self
}
}
impl ElementBuilder for ToggleGroupBuilder {
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_group")
}
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_group(value: Signal<String>) -> ToggleGroupBuilder {
ToggleGroupBuilder::new(value)
}