use blinc_layout::prelude::*;
use blinc_theme::{ColorToken, ThemeState, TypographyTokens};
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub enum LabelSize {
Small,
#[default]
Medium,
Large,
}
impl LabelSize {
fn font_size(&self, typography: &TypographyTokens) -> f32 {
match self {
LabelSize::Small => typography.text_xs,
LabelSize::Medium => typography.text_sm,
LabelSize::Large => typography.text_base,
}
}
}
pub struct Label {
inner: Div,
}
impl Label {
pub fn new(text: impl blinc_layout::binding::IntoReactive<String>) -> Self {
Self::with_config(LabelConfig::new(text.into_reactive()))
}
fn with_config(config: LabelConfig) -> Self {
let theme = ThemeState::get();
let typography = theme.typography();
let font_size = config.size.font_size(&typography);
let inner = if config.required {
div()
.flex_shrink_0()
.flex_row()
.gap(2.0)
.child_box(crate::reactive_props::reactive_text(
&config.text,
move |t| t.class("cn-label-text").size(font_size).medium().no_wrap(),
))
.child(
text("*")
.class("cn-label-required")
.size(font_size)
.medium(),
)
} else {
div()
.flex_shrink_0()
.child_box(crate::reactive_props::reactive_text(
&config.text,
move |t| t.class("cn-label-text").size(font_size).medium().no_wrap(),
))
}
.class("cn-label")
.class_when("cn-label--disabled", config.disabled.clone())
.attr_when("disabled", "", config.disabled);
Self { inner }
}
pub fn class(mut self, name: impl AsRef<str>) -> Self {
self.inner = self.inner.class(name);
self
}
pub fn id(mut self, id: &str) -> Self {
self.inner = self.inner.id(id);
self
}
}
pub(crate) struct LabelConfig {
text: blinc_layout::binding::Reactive<String>,
size: LabelSize,
required: bool,
disabled: blinc_layout::binding::Reactive<bool>,
}
impl Clone for LabelConfig {
fn clone(&self) -> Self {
Self {
text: self.text.clone(),
size: self.size,
required: self.required,
disabled: self.disabled.clone(),
}
}
}
impl LabelConfig {
fn new(text: blinc_layout::binding::Reactive<String>) -> Self {
Self {
text,
size: LabelSize::default(),
required: false,
disabled: blinc_layout::binding::Reactive::Const(false),
}
}
}
pub struct LabelBuilder {
pub(crate) config: LabelConfig,
built: std::cell::OnceCell<Label>,
}
impl LabelBuilder {
pub fn new(text: impl blinc_layout::binding::IntoReactive<String>) -> Self {
Self {
config: LabelConfig::new(text.into_reactive()),
built: std::cell::OnceCell::new(),
}
}
fn get_or_build(&self) -> &Label {
::blinc_layout::build_once::build_once(&self.built, || {
Label::with_config(self.config.clone())
})
}
pub fn size(mut self, size: LabelSize) -> Self {
self.config.size = size;
self
}
pub fn required(mut self) -> Self {
self.config.required = true;
self
}
pub fn disabled(mut self, disabled: impl blinc_layout::binding::IntoReactive<bool>) -> Self {
self.config.disabled = disabled.into_reactive();
self
}
pub fn build_component(self) -> Label {
Label::with_config(self.config)
}
}
impl ElementBuilder for Label {
fn build(&self, tree: &mut blinc_layout::tree::LayoutTree) -> blinc_layout::tree::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 element_classes(&self) -> &[std::sync::Arc<str>] {
self.inner.element_classes()
}
}
impl ElementBuilder for LabelBuilder {
fn build(&self, tree: &mut blinc_layout::tree::LayoutTree) -> blinc_layout::tree::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 element_classes(&self) -> &[std::sync::Arc<str>] {
self.get_or_build().element_classes()
}
}
pub fn label(text: impl blinc_layout::binding::IntoReactive<String>) -> LabelBuilder {
LabelBuilder::new(text)
}
#[cfg(test)]
mod tests {
use super::*;
use blinc_theme::TypographyTokens;
#[test]
fn test_label_size_values() {
let typography = TypographyTokens::default();
assert_eq!(LabelSize::Small.font_size(&typography), typography.text_xs);
assert_eq!(LabelSize::Medium.font_size(&typography), typography.text_sm);
assert_eq!(
LabelSize::Large.font_size(&typography),
typography.text_base
);
}
#[test]
fn test_label_builder() {
let lbl = label("Username").required().size(LabelSize::Large);
assert!(lbl.config.required);
assert_eq!(lbl.config.size, LabelSize::Large);
}
}