use crate::style::Style;
use crate::tokens::{BorderRadius, Color, FontSize, FontWeight, Scale, Shadow, Spacing};
use crate::traits::{Merge, ToCss};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ButtonVariant {
Primary,
Secondary,
Outline,
Ghost,
Destructive,
Link,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ButtonSize {
Sm,
Md,
Lg,
Icon,
}
#[derive(Debug, Clone)]
pub struct Button {
pub variant: ButtonVariant,
pub size: ButtonSize,
pub disabled: bool,
pub full_width: bool,
}
impl Button {
pub fn new(variant: ButtonVariant, size: ButtonSize) -> Self {
Self {
variant,
size,
disabled: false,
full_width: false,
}
}
pub fn primary() -> Self {
Self::new(ButtonVariant::Primary, ButtonSize::Md)
}
pub fn secondary() -> Self {
Self::new(ButtonVariant::Secondary, ButtonSize::Md)
}
pub fn outline() -> Self {
Self::new(ButtonVariant::Outline, ButtonSize::Md)
}
pub fn ghost() -> Self {
Self::new(ButtonVariant::Ghost, ButtonSize::Md)
}
pub fn destructive() -> Self {
Self::new(ButtonVariant::Destructive, ButtonSize::Md)
}
pub fn sm(mut self) -> Self {
self.size = ButtonSize::Sm;
self
}
pub fn lg(mut self) -> Self {
self.size = ButtonSize::Lg;
self
}
pub fn icon(mut self) -> Self {
self.size = ButtonSize::Icon;
self
}
pub fn disabled(mut self) -> Self {
self.disabled = true;
self
}
pub fn full_width(mut self) -> Self {
self.full_width = true;
self
}
pub fn style(&self) -> Style {
let base = self.base_style();
let variant_style = self.variant_style();
let size_style = self.size_style();
let mut style = base.merge(variant_style).merge(size_style);
if self.full_width {
style = style.width(crate::utilities::Width::full());
}
if self.disabled {
style.opacity = Some(0.5);
}
style
}
fn base_style(&self) -> Style {
Style::new()
.display(crate::utilities::Display::InlineFlex)
.flex(
crate::utilities::FlexContainer::row()
.justify(crate::utilities::JustifyContent::Center)
.align(crate::utilities::AlignItems::Center),
)
.font_weight(FontWeight::Medium)
.rounded(BorderRadius::Md)
.text_align(crate::tokens::TextAlign::Center)
}
fn variant_style(&self) -> Style {
match self.variant {
ButtonVariant::Primary => Style::new()
.bg(Color::blue(Scale::S500))
.text_color(Color::slate(Scale::S50))
.shadow(Shadow::Sm),
ButtonVariant::Secondary => Style::new()
.bg(Color::gray(Scale::S100))
.text_color(Color::gray(Scale::S900)),
ButtonVariant::Outline => Style::new().text_color(Color::gray(Scale::S900)).border(
crate::tokens::BorderWidth::S1,
crate::tokens::BorderStyle::Solid,
Color::gray(Scale::S200),
),
ButtonVariant::Ghost => Style::new().text_color(Color::gray(Scale::S900)),
ButtonVariant::Destructive => Style::new()
.bg(Color::red(Scale::S500))
.text_color(Color::slate(Scale::S50))
.shadow(Shadow::Sm),
ButtonVariant::Link => Style::new()
.text_color(Color::blue(Scale::S500))
.underline(),
}
}
fn size_style(&self) -> Style {
match self.size {
ButtonSize::Sm => Style::new()
.padding(crate::utilities::Padding::symmetric(
Spacing::S1,
Spacing::S3,
))
.text_size(FontSize::Sm)
.rounded(BorderRadius::Sm),
ButtonSize::Md => Style::new()
.padding(crate::utilities::Padding::symmetric(
Spacing::S2,
Spacing::S4,
))
.text_size(FontSize::Sm),
ButtonSize::Lg => Style::new()
.padding(crate::utilities::Padding::symmetric(
Spacing::S3,
Spacing::S6,
))
.text_size(FontSize::Base)
.rounded(BorderRadius::Lg),
ButtonSize::Icon => Style::new().padding(crate::utilities::Padding::all(Spacing::S2)),
}
}
}
impl ToCss for Button {
fn to_css(&self) -> String {
self.style().to_css()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_primary_button() {
let btn = Button::primary();
let css = btn.to_css();
assert!(css.contains("background-color: #3b82f6"));
}
#[test]
fn test_button_sizes() {
let sm = Button::primary().sm().to_css();
let lg = Button::primary().lg().to_css();
assert!(sm.contains("padding: 0.25rem 0.75rem"));
assert!(lg.contains("padding: 0.75rem 1.5rem"));
}
}