Button
Displays a button or a component that looks like a button.
src/components/rahti_ui/button.rs
Variants
Six of them, and `.default_variant()`, `.secondary()`, `.destructive()`, `.outline()`, `.ghost()` and `.link()` are shorthands for the same thing.
Sizes
`.xs()`, `.sm()`, the default, and `.lg()`.
Icon sizes
Square, no label padding. Give one an `aria-label` — there is no text to read out.
With icons
Icons are components, installed with `cargo rahti-icons add` rather than pasted in, and the base class list both sizes and spaces them. Two selectors do that and they are not equally forgiving: `[&_svg]` is a descendant selector and finds an icon wherever it sits, while `has-[>svg]:px-3` wants it as a direct child. These are built with `Html::concat` rather than an `html!` fragment so that it is one — see `icon_and_text`.
States
`.disabled(true)` writes the attribute; the base class list handles the rest. A spinner child is all a loading button is. `aria-invalid` is styled too.
As a link
`.href(...)` renders an `<a>`. A disabled one has no `disabled` attribute to set, so it gets `aria-disabled` and is made inert instead.
Attributes
`.attr()`, `.flag()`, `.submit()`, `.reset()` and `.plain()` land on the rendered element, and anything the caller sets wins over the component's own.
Class override
`class` is merged, not appended: a utility written here replaces the one it conflicts with and leaves the rest of the variant alone. The merge is `twMerge`'s, in the browser — so unlike every other button on this page, these three are unstyled until PulsePoint mounts.
Events, page scope
A handler that reads this page's state belongs on markup this page owns. `button_variants` returns the same class list the component renders, so a native `<button>` here is a Button in every way that shows.
Events and bindings, inline
`.render(children)` returns the element itself, with no boundary around it, so interpolating it into this block puts it in this block's scope. Handlers and bindings both work, and it is still the same component.
Reactive class strings
When the class list itself changes with state, the merge has to happen in the browser — and `main.js` publishes `twMerge` as a global for exactly that. `button_variants` is seeded into the script below, so the base is still this component's. Note the cost: the attribute is a binding, so it is literal text until PulsePoint mounts.
Events, component tag
Native event attributes, written on the tag and put on the `<button>` untouched — the component is UI and the handler is the caller's. The tag stamps a boundary, but an event expression is resolved when it fires, so these drive the same `count` this page declares. A `{…}` written between the tags is not: it is compiled against the component's scope, and the last one is here to show that.