Skip to main content

orbital_base_components/button/
base.rs

1use leptos::{ev, html, prelude::*};
2
3use super::r#ref::ButtonRef;
4use super::types::ButtonType;
5use crate::ComponentRef;
6
7/// Headless native `<button>` — semantics and a11y only; styling via `class` and modifier strings.
8#[component(transparent)]
9pub fn BaseButton(
10    #[prop(optional, into)] class: MaybeProp<String>,
11    #[prop(optional, into)] appearance: MaybeProp<String>,
12    #[prop(optional, into)] shape: MaybeProp<String>,
13    #[prop(optional, into)] size: MaybeProp<String>,
14    #[prop(optional, into)] button_type: MaybeProp<ButtonType>,
15    #[prop(optional, into)] block: Signal<bool>,
16    #[prop(optional, into)] disabled: Signal<bool>,
17    #[prop(optional, into)] disabled_focusable: Signal<bool>,
18    #[prop(optional, into)] loading: Signal<bool>,
19    #[prop(optional, into)] aria_pressed: MaybeProp<String>,
20    #[prop(optional)] on_click: Option<Callback<ev::MouseEvent>>,
21    children: Children,
22    #[prop(optional)] comp_ref: ComponentRef<ButtonRef>,
23) -> impl IntoView {
24    let _ = block;
25    let btn_disabled = Memo::new(move |_| disabled.get());
26    let aria_disabled = move || {
27        if loading.get() || disabled_focusable.get() {
28            Some("true")
29        } else {
30            None
31        }
32    };
33    let aria_busy = move || loading.get().then_some("true");
34
35    let button_ref = NodeRef::<html::Button>::new();
36    comp_ref.load(ButtonRef { button_ref });
37
38    let on_click = move |e: ev::MouseEvent| {
39        if btn_disabled.get_untracked() || loading.get_untracked() {
40            return;
41        }
42        let Some(on_click) = on_click else {
43            return;
44        };
45        on_click.run(e);
46    };
47
48    view! {
49        <button
50            class=move || {
51                let mut parts = Vec::new();
52                if let Some(c) = class.get() {
53                    if !c.is_empty() {
54                        parts.push(c);
55                    }
56                }
57                if let Some(c) = appearance.get() {
58                    if !c.is_empty() {
59                        parts.push(c);
60                    }
61                }
62                if let Some(c) = shape.get() {
63                    if !c.is_empty() {
64                        parts.push(c);
65                    }
66                }
67                if let Some(c) = size.get() {
68                    if !c.is_empty() {
69                        parts.push(c);
70                    }
71                }
72                parts.join(" ")
73            }
74            type=move || button_type.get().map(|t| t.as_str())
75            disabled=move || {
76                if disabled_focusable.get() {
77                    None
78                } else {
79                    disabled.get().then_some("")
80                }
81            }
82            tabindex=move || disabled_focusable.get().then_some(0)
83            aria-disabled=aria_disabled
84            aria-busy=aria_busy
85            aria-pressed=move || aria_pressed.get()
86            on:click=on_click
87            node_ref=button_ref
88        >
89            {children()}
90        </button>
91    }
92}