use dioxus::prelude::*;
use crate::utils;
const BUTTON_CSS: &str = include_str!("./button.css");
#[derive(Clone, Copy, PartialEq)]
pub enum ButtonVariant {
Default,
Destructive,
Outline,
Secondary,
Ghost,
Link,
}
impl ButtonVariant {
pub fn as_str(&self) -> &'static str {
match self {
ButtonVariant::Default => "bg-primary text-primary-foreground hover:bg-primary/90",
ButtonVariant::Destructive => "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
ButtonVariant::Outline => "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
ButtonVariant::Secondary => "bg-secondary text-secondary-foreground hover:bg-secondary/80",
ButtonVariant::Ghost => "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
ButtonVariant::Link => "text-primary underline-offset-4 hover:underline",
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum ButtonSize {
Default,
Sm,
Lg,
Icon,
IconSm,
IconLg,
}
impl ButtonSize {
pub fn as_str(&self) -> &'static str {
match self {
ButtonSize::Default => "h-9 px-4 py-2 has-[>svg]:px-3",
ButtonSize::Sm => "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
ButtonSize::Lg => "h-10 rounded-md px-6 has-[>svg]:px-4",
ButtonSize::Icon => "size-9",
ButtonSize::IconSm => "size-8",
ButtonSize::IconLg => "size-10",
}
}
}
#[derive(Props, Clone, PartialEq)]
pub struct ButtonProps {
#[props(default)]
pub children: Element,
#[props(optional)]
pub class: Option<String>,
#[props(optional)]
pub variant: Option<ButtonVariant>,
#[props(optional)]
pub size: Option<ButtonSize>,
#[props(optional)]
pub as_: Option<String>,
#[props(optional)]
pub disabled: Option<bool>,
#[props(optional)]
pub href: Option<String>,
#[props(optional)]
pub onclick: Option<EventHandler<MouseEvent>>,
#[props(optional)]
pub aria_invalid: Option<bool>,
}
#[component]
pub fn Button(props: ButtonProps) -> Element {
let variant = props.variant.unwrap_or(ButtonVariant::Default);
let size = props.size.unwrap_or(ButtonSize::Default);
let base = "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive";
let class_name = utils::cn(vec![
Some(base),
Some(variant.as_str()),
Some(size.as_str()),
props.class.as_deref(),
]);
let tag = props.as_.as_deref().unwrap_or("button");
let aria_invalid_attr = props.aria_invalid.unwrap_or(false);
match tag {
"a" => rsx! {
style { {BUTTON_CSS} }
a {
class: "{class_name}",
href: props.href.as_deref().unwrap_or("#"),
onclick: move |e| {
if let Some(cb) = &props.onclick {
cb.call(e);
}
},
aria_invalid: "{aria_invalid_attr}",
{props.children}
}
},
"div" => rsx! {
style { {BUTTON_CSS} }
div {
class: "{class_name}",
onclick: move |e| { if let Some(handler) = &props.onclick { handler.call(e); } },
aria_invalid: "{aria_invalid_attr}",
{props.children}
}
},
"span" => rsx! {
style { {BUTTON_CSS} }
span {
class: "{class_name}",
onclick: move |e| { if let Some(handler) = &props.onclick { handler.call(e); } },
aria_invalid: "{aria_invalid_attr}",
{props.children}
}
},
_ => rsx!(
style { {BUTTON_CSS} }
button {
class: "{class_name}",
disabled: "{props.disabled.unwrap_or(false)}",
onclick: move |e| { if let Some(handler) = &props.onclick { handler.call(e); } },
aria_invalid: "{aria_invalid_attr}",
{props.children}
}
),
}
}