use either::Either;
use yew::prelude::*;
use crate::{cx, sui};
use crate::helper::*;
use crate::collections::{Icon, Label};
pub struct Button {
link: ComponentLink<Self>,
props: ButtonProps,
button_ref: NodeRef,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ButtonAnimationType {
Fade,
Vertical
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ButtonAttachedPosition {
Left,
Right,
Top,
Bottom,
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum ButtonLabelPosition {
Right,
Left,
}
#[derive(Debug, Clone, PartialEq, Properties)]
pub struct ButtonProps {
#[prop_or_else(|| "button".to_string())]
pub root: String,
#[prop_or(false)]
pub active: bool,
#[prop_or_else(|| Either::Left(false))]
pub animated: Either<bool, ButtonAnimationType>,
#[prop_or_else(|| Either::Left(false))]
pub attached: Either<bool, ButtonAttachedPosition>,
#[prop_or(false)]
pub basic: bool,
#[prop_or_default]
pub children: Children,
#[prop_or(false)]
pub circular: bool,
#[prop_or_else(|| None)]
pub class_name: Option<String>,
#[prop_or_else(|| None)]
pub color: Option<sui::Colors>,
#[prop_or(false)]
pub compact: bool,
#[prop_or_else(|| None)]
pub content: Option<String>,
#[prop_or(false)]
pub disabled: bool,
#[prop_or_else(|| None)]
pub floated: Option<sui::Float>,
#[prop_or(false)]
pub fluid: bool,
#[prop_or_default]
pub icon: ChildrenWithProps<Icon>,
#[prop_or(false)]
pub inverted: bool,
#[prop_or_default]
pub label: ChildrenWithProps<Label>,
#[prop_or_default]
pub label_position: ButtonLabelPosition,
#[prop_or(false)]
pub loading: bool,
#[prop_or(false)]
pub negative: bool,
#[prop_or_default]
pub onclick: Callback<MouseEvent>,
#[prop_or(false)]
pub positive: bool,
#[prop_or(false)]
pub primary: bool,
#[prop_or_else(|| None)]
pub role: Option<String>,
#[prop_or(false)]
pub secondary: bool,
#[prop_or_else(|| None)]
pub size: Option<sui::Sizes>,
#[prop_or_else(|| None)]
pub tab_index: Option<isize>,
#[prop_or(false)]
pub toggle: bool,
}
impl Component for Button {
type Message = MouseEvent;
type Properties = ButtonProps;
fn create(props: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
props,
button_ref: NodeRef::default()
}
}
fn update(&mut self, event: Self::Message) -> ShouldRender {
if self.props.disabled {
event.prevent_default();
} else {
self.props.onclick.emit(event);
}
false
}
fn change(&mut self, props: Self::Properties) -> ShouldRender {
if props != self.props {
self.props = props;
true
} else {
false
}
}
fn view(&self) -> Html {
let root_element = self.props.get_element_type();
let aria_pressed = if self.props.toggle {
Some(format!("{}", self.props.active))
} else {
None
};
let tab_index = format!("{}", self.props.get_tab_index());
if !self.props.label.is_empty() {
let button_classes = cx!(
use_str("ui"),
self.props.derive_base_classes(),
use_str("button"),
use_option(&self.props.class_name)
);
let container_classes = cx!(
use_str("ui"),
self.props.derive_labeled_class(),
use_str("button"),
use_option(&self.props.class_name),
self.props.derive_wrapper_classes()
);
html! {
<@{ root_element }
class=classes!(container_classes.as_slice())
onclick=self.link.callback(|e| e)
>
{
if self.props.label_position == ButtonLabelPosition::Left && !self.props.label.is_empty() {
html! { <>{self.props.label.clone()}</> }
} else {
html! {}
}
}
<button
ref=self.button_ref.clone()
class=classes!(button_classes.as_slice())
aria-pressed=aria_pressed
disabled=self.props.disabled
tabindex=tab_index
>
{self.props.icon.clone()}
{
match self.props.content {
Some(ref content) => html! { content.clone() },
None => html! {}
}
}
</button>
{
if self.props.label_position == ButtonLabelPosition::Right && !self.props.label.is_empty() {
html! { <>{self.props.label.clone()}</> }
} else {
html! {}
}
}
</@>
}
} else {
let classes = cx!(
use_str("ui"),
self.props.derive_base_classes(),
self.props.derive_wrapper_classes(),
self.props.derive_labeled_class(),
use_str("button"),
use_option(&self.props.class_name)
);
html!{
<@{ root_element }
class=classes!(classes.as_slice())
aria-pressed={ if self.props.toggle { Some(format!("{}", self.props.active)) } else { None } }
disabled=self.props.disabled
onclick=self.link.callback(|e| e)
role=self.props.get_aria_role()
tabindex=tab_index
>
{
if !self.props.children.is_empty() {
html! { <>{self.props.children.clone()}</> }
} else {
html! {
<>
{ self.props.icon.clone() }
{
match self.props.content {
Some(ref content) => html! { content.clone() },
None => html! {}
}
}
</>
}
}
}
</@>
}
}
}
}
impl ButtonProps {
fn derive_base_classes(&self) -> Vec<String> {
let Self {
active,
animated,
attached,
basic,
circular,
color,
compact,
fluid,
inverted,
loading,
negative,
positive,
primary,
secondary,
size,
toggle,
..
} = self;
cx!(
use_option(color),
use_option(size),
use_key(*active, "active"),
use_key(*basic, "basic"),
use_key(*circular, "circular"),
use_key(*compact, "compact"),
use_key(*fluid, "fluid"),
use_key(self.has_icon_class(), "icon"),
use_key(*inverted, "inverted"),
use_key(*loading, "loading"),
use_key(*negative, "negative"),
use_key(*positive, "positive"),
use_key(*primary, "primary"),
use_key(*secondary, "secondary"),
use_key(*toggle, "toggle"),
use_key_or_option_and_key(animated, "animated"),
use_key_or_option_and_key(attached, "attached")
)
}
fn derive_labeled_class(&self) -> Vec<String> {
let label = if !self.label.is_empty() {
Some(self.label_position)
} else {
None
};
use_option_and_key(&label, "labeled")
}
fn derive_wrapper_classes(&self) -> Vec<String> {
cx!(
use_key(self.disabled, "disabled"),
use_option_and_key(&self.floated, "floated")
)
}
fn get_element_type(&self) -> String {
if self.root != "button" {
return self.root.clone()
}
let Self {
attached,
label,
..
} = self;
if !attached.unset() || !label.is_empty() {
return "div".to_string()
}
return self.root.clone()
}
fn get_aria_role(&self) -> Option<String> {
match self.role {
None => if self.get_element_type() != "button" {
Some("button".to_string())
} else {
None
}
Some(ref role) => Some(role.clone()),
}
}
fn get_tab_index(&self) -> isize {
let Self { disabled, tab_index, .. } = self;
match tab_index {
Some(i) => *i,
None => if *disabled {
-1
} else if self.get_element_type() == "div" {
0
} else {
0
}
}
}
fn has_icon_class(&self) -> bool {
if !self.icon.is_empty() {
true
} else {
false
}
}
}
impl From<ButtonAnimationType> for &'static str {
fn from(b: ButtonAnimationType) -> Self {
use ButtonAnimationType::*;
match b {
Fade => "fade",
Vertical => "vertical",
}
}
}
impl AsRef<str> for ButtonAnimationType {
fn as_ref(&self) -> &str {
(*self).into()
}
}
impl From<ButtonAttachedPosition> for &'static str {
fn from(b: ButtonAttachedPosition) -> Self {
use ButtonAttachedPosition::*;
match b {
Left => "left",
Right => "right",
Top => "top",
Bottom => "bottom",
}
}
}
impl AsRef<str> for ButtonAttachedPosition {
fn as_ref(&self) -> &str {
(*self).into()
}
}
impl From<ButtonLabelPosition> for &'static str {
fn from(b: ButtonLabelPosition) -> Self {
use ButtonLabelPosition::*;
match b {
Left => "left",
Right => "right",
}
}
}
impl AsRef<str> for ButtonLabelPosition {
fn as_ref(&self) -> &str {
(*self).into()
}
}
impl Default for ButtonLabelPosition {
fn default() -> Self {
Self::Left
}
}