✨ (button): Añade Button::anchor() para navegación
- Renderiza un `<a href>` real con el aspecto de un botón, distinto del estilo puramente visual `button::Style::Link`. - Renombra `ButtonKind`/`ButtonStyle` a `Kind`/`Style`. - Añade `button::Size` (`button-sm`/`button-lg`).
This commit is contained in:
parent
434d31c5eb
commit
da959183f6
6 changed files with 229 additions and 67 deletions
|
|
@ -101,6 +101,7 @@ body {
|
||||||
background-color: var(--val-color--primary);
|
background-color: var(--val-color--primary);
|
||||||
border: 1px solid var(--val-color--primary);
|
border: 1px solid var(--val-color--primary);
|
||||||
border-radius: 0.375rem;
|
border-radius: 0.375rem;
|
||||||
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
|
transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
@ -116,6 +117,15 @@ body {
|
||||||
border: 0;
|
border: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.button-sm {
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
.button-lg {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
.button-primary,
|
.button-primary,
|
||||||
.button-neutral,
|
.button-neutral,
|
||||||
.button-success,
|
.button-success,
|
||||||
|
|
|
||||||
|
|
@ -142,18 +142,17 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_child(
|
.with_child(
|
||||||
button::ButtonSet::new()
|
button::ButtonSet::new()
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::submit(Lc::t("btn_submit", &LOC)).with_style(
|
Button::submit(Lc::t("btn_submit", &LOC))
|
||||||
button::ButtonStyle::Solid(Intent::Primary),
|
.with_style(button::Style::Solid(Intent::Primary)),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::reset(Lc::t("btn_reset", &LOC)).with_style(
|
Button::reset(Lc::t("btn_reset", &LOC)).with_style(
|
||||||
button::ButtonStyle::Outline(Intent::Secondary),
|
button::Style::Outline(Intent::Neutral),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::plain(Lc::t("btn_cancel", &LOC))
|
Button::plain(Lc::t("btn_cancel", &LOC))
|
||||||
.with_style(button::ButtonStyle::Link),
|
.with_style(button::Style::Link),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -258,18 +257,17 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_child(
|
.with_child(
|
||||||
button::ButtonSet::new()
|
button::ButtonSet::new()
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::submit(Lc::t("btn_submit", &LOC)).with_style(
|
Button::submit(Lc::t("btn_submit", &LOC))
|
||||||
button::ButtonStyle::Solid(Intent::Primary),
|
.with_style(button::Style::Solid(Intent::Primary)),
|
||||||
),
|
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::reset(Lc::t("btn_reset", &LOC)).with_style(
|
Button::reset(Lc::t("btn_reset", &LOC)).with_style(
|
||||||
button::ButtonStyle::Outline(Intent::Secondary),
|
button::Style::Outline(Intent::Neutral),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::plain(Lc::t("btn_cancel", &LOC))
|
Button::plain(Lc::t("btn_cancel", &LOC))
|
||||||
.with_style(button::ButtonStyle::Link),
|
.with_style(button::Style::Link),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -299,24 +297,22 @@ async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
p { (Lc::t("dialog_delete_body", &LOC).using(cx)) }
|
p { (Lc::t("dialog_delete_body", &LOC).using(cx)) }
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.with_footer(
|
|
||||||
Button::plain(Lc::t("btn_cancel", &LOC))
|
|
||||||
.with_prop(PropsOp::set("data-dialog-dismiss", "modal"))
|
|
||||||
.with_style(button::ButtonStyle::Outline(
|
|
||||||
Intent::Secondary,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
.with_footer(
|
.with_footer(
|
||||||
Button::plain(Lc::t("btn_ok", &LOC))
|
Button::plain(Lc::t("btn_ok", &LOC))
|
||||||
.with_prop(PropsOp::set("data-dialog-dismiss", "modal"))
|
.with_prop(PropsOp::set("data-dialog-dismiss", "modal"))
|
||||||
.with_style(button::ButtonStyle::Solid(Intent::Primary)),
|
.with_style(button::Style::Solid(Intent::Primary)),
|
||||||
|
)
|
||||||
|
.with_footer(
|
||||||
|
Button::plain(Lc::t("btn_cancel", &LOC))
|
||||||
|
.with_prop(PropsOp::set("data-dialog-dismiss", "modal"))
|
||||||
|
.with_style(button::Style::Outline(Intent::Neutral)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.with_child(
|
.with_child(
|
||||||
Button::plain(Lc::t("btn_delete", &LOC))
|
Button::plain(Lc::t("btn_delete", &LOC))
|
||||||
.with_prop(PropsOp::set("data-dialog-toggle", "modal"))
|
.with_prop(PropsOp::set("data-dialog-toggle", "modal"))
|
||||||
.with_prop(PropsOp::set("data-dialog-target", "#delete-confirm"))
|
.with_prop(PropsOp::set("data-dialog-target", "#delete-confirm"))
|
||||||
.with_style(button::ButtonStyle::Solid(Intent::Danger)),
|
.with_style(button::Style::Solid(Intent::Severe)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -437,14 +433,14 @@ fn form_lists() -> Form {
|
||||||
button::ButtonSet::new()
|
button::ButtonSet::new()
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::submit(Lc::t("btn_submit", &LOC))
|
Button::submit(Lc::t("btn_submit", &LOC))
|
||||||
.with_style(button::ButtonStyle::Solid(Intent::Primary)),
|
.with_style(button::Style::Solid(Intent::Primary)),
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::reset(Lc::t("btn_reset", &LOC))
|
Button::reset(Lc::t("btn_reset", &LOC))
|
||||||
.with_style(button::ButtonStyle::Outline(Intent::Secondary)),
|
.with_style(button::Style::Outline(Intent::Neutral)),
|
||||||
)
|
)
|
||||||
.with_button(
|
.with_button(
|
||||||
Button::plain(Lc::t("btn_cancel", &LOC)).with_style(button::ButtonStyle::Link),
|
Button::plain(Lc::t("btn_cancel", &LOC)).with_style(button::Style::Link),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
//! Definiciones para crear botones ([`Button`]) y conjuntos de botones ([`ButtonSet`]).
|
//! Definiciones para crear botones ([`Button`]) y conjuntos de botones ([`ButtonSet`]).
|
||||||
|
|
||||||
mod props;
|
mod props;
|
||||||
pub use props::{ButtonKind, ButtonStyle};
|
pub use props::{Kind, Size, Style};
|
||||||
|
|
||||||
mod component;
|
mod component;
|
||||||
pub use component::Button;
|
pub use component::Button;
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,12 @@ use crate::prelude::*;
|
||||||
/// - [`Button::submit()`]: botón de envío (por defecto).
|
/// - [`Button::submit()`]: botón de envío (por defecto).
|
||||||
/// - [`Button::reset()`]: botón de restablecimiento de valores.
|
/// - [`Button::reset()`]: botón de restablecimiento de valores.
|
||||||
/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado.
|
/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado.
|
||||||
|
/// - [`Button::anchor()`]: enlace de navegación real con el aspecto de un botón.
|
||||||
|
///
|
||||||
|
/// No confundir [`Button::anchor()`] con [`button::Style::Link`]: el primero renderiza un `<a
|
||||||
|
/// href=...>` real que navega; el segundo es sólo un estilo visual (clase `button-link`) que se
|
||||||
|
/// aplica con [`with_style()`](Self::with_style) sobre cualquiera de las otras variantes, que
|
||||||
|
/// siguen siendo un `<button>`.
|
||||||
///
|
///
|
||||||
/// Un botón puede usarse dentro o fuera de un formulario.
|
/// Un botón puede usarse dentro o fuera de un formulario.
|
||||||
///
|
///
|
||||||
|
|
@ -18,6 +24,7 @@ use crate::prelude::*;
|
||||||
/// let save = Button::submit(Lc::n("Save"));
|
/// let save = Button::submit(Lc::n("Save"));
|
||||||
/// let cancel = Button::plain(Lc::n("Cancel"));
|
/// let cancel = Button::plain(Lc::n("Cancel"));
|
||||||
/// let clear = Button::reset(Lc::n("Clear"));
|
/// let clear = Button::reset(Lc::n("Clear"));
|
||||||
|
/// let edit = Button::anchor(Lc::n("Edit"), "/items/1/edit");
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del
|
/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del
|
||||||
|
|
@ -36,10 +43,13 @@ pub struct Button {
|
||||||
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
||||||
props: Props,
|
props: Props,
|
||||||
/// Devuelve el comportamiento del botón al activarse.
|
/// Devuelve el comportamiento del botón al activarse.
|
||||||
kind: button::ButtonKind,
|
kind: button::Kind,
|
||||||
|
/// Devuelve el tamaño visual del botón.
|
||||||
|
#[getters(copy)]
|
||||||
|
size: button::Size,
|
||||||
/// Devuelve el estilo visual del botón.
|
/// Devuelve el estilo visual del botón.
|
||||||
#[getters(copy)]
|
#[getters(copy)]
|
||||||
style: button::ButtonStyle,
|
style: button::Style,
|
||||||
/// Devuelve el nombre del botón.
|
/// Devuelve el nombre del botón.
|
||||||
name: AttrName,
|
name: AttrName,
|
||||||
/// Devuelve el valor del botón.
|
/// Devuelve el valor del botón.
|
||||||
|
|
@ -48,6 +58,10 @@ pub struct Button {
|
||||||
label: Lc,
|
label: Lc,
|
||||||
/// Devuelve el texto emergente del botón (atributo `title`).
|
/// Devuelve el texto emergente del botón (atributo `title`).
|
||||||
title: Lc,
|
title: Lc,
|
||||||
|
/// Devuelve la ruta de destino cuando el botón se renderiza como enlace de navegación
|
||||||
|
/// (`<a href=...>` en vez de `<button>`). Vacía por defecto: en ese caso `prepare()` renderiza
|
||||||
|
/// un `<button>` normal, ignorando este campo.
|
||||||
|
href: Route,
|
||||||
/// Devuelve si el botón recibe el foco automáticamente al cargar la página.
|
/// Devuelve si el botón recibe el foco automáticamente al cargar la página.
|
||||||
autofocus: bool,
|
autofocus: bool,
|
||||||
/// Devuelve si el botón está deshabilitado.
|
/// Devuelve si el botón está deshabilitado.
|
||||||
|
|
@ -64,18 +78,43 @@ impl Component for Button {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup(&mut self, _cx: &Context) {
|
fn setup(&mut self, cx: &Context) {
|
||||||
use button::ButtonStyle;
|
use button::{Size, Style};
|
||||||
|
|
||||||
|
self.alter_prop(PropsOp::prepend_classes(match self.size() {
|
||||||
|
Size::None => "",
|
||||||
|
Size::Small => "button-sm",
|
||||||
|
Size::Large => "button-lg",
|
||||||
|
}));
|
||||||
self.alter_prop(PropsOp::prepend_classes(match self.style() {
|
self.alter_prop(PropsOp::prepend_classes(match self.style() {
|
||||||
ButtonStyle::None => "button".to_string(),
|
Style::None => "button".to_string(),
|
||||||
ButtonStyle::Solid(intent) => util::join!("button button-", intent.as_str()),
|
Style::Solid(intent) => util::join!("button button-", intent.color(cx)),
|
||||||
ButtonStyle::Outline(intent) => util::join!("button button-outline-", intent.as_str()),
|
Style::Outline(intent) => util::join!("button button-outline-", intent.color(cx)),
|
||||||
ButtonStyle::Link => "button button-link".to_string(),
|
Style::Link => "button button-link".to_string(),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
if let Some(route) = self.href().try_resolve(cx) {
|
||||||
|
let disabled = *self.disabled();
|
||||||
|
let href = (!disabled).then_some(route);
|
||||||
|
let aria_disabled = disabled.then_some("true");
|
||||||
|
let tabindex = disabled.then_some("-1");
|
||||||
|
|
||||||
|
return Ok(html! {
|
||||||
|
a
|
||||||
|
(self.props())
|
||||||
|
href=[href]
|
||||||
|
title=[self.title().lookup(cx)]
|
||||||
|
autofocus[*self.autofocus()]
|
||||||
|
aria-disabled=[aria_disabled]
|
||||||
|
tabindex=[tabindex]
|
||||||
|
{
|
||||||
|
(self.label().using(cx))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Ok(html! {
|
Ok(html! {
|
||||||
button
|
button
|
||||||
type=(self.kind())
|
type=(self.kind())
|
||||||
|
|
@ -86,9 +125,7 @@ impl Component for Button {
|
||||||
autofocus[*self.autofocus()]
|
autofocus[*self.autofocus()]
|
||||||
disabled[*self.disabled()]
|
disabled[*self.disabled()]
|
||||||
{
|
{
|
||||||
@if let Some(label) = self.label().lookup(cx) {
|
(self.label().using(cx))
|
||||||
(label)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
@ -101,7 +138,7 @@ impl Button {
|
||||||
/// datos al servidor.
|
/// datos al servidor.
|
||||||
pub fn submit(label: Lc) -> Self {
|
pub fn submit(label: Lc) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: button::ButtonKind::Submit,
|
kind: button::Kind::Submit,
|
||||||
label,
|
label,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
@ -112,7 +149,7 @@ impl Button {
|
||||||
/// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales.
|
/// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales.
|
||||||
pub fn reset(label: Lc) -> Self {
|
pub fn reset(label: Lc) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: button::ButtonKind::Reset,
|
kind: button::Kind::Reset,
|
||||||
label,
|
label,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
|
|
@ -124,12 +161,28 @@ impl Button {
|
||||||
/// definirse mediante JavaScript.
|
/// definirse mediante JavaScript.
|
||||||
pub fn plain(label: Lc) -> Self {
|
pub fn plain(label: Lc) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kind: button::ButtonKind::Plain,
|
kind: button::Kind::Plain,
|
||||||
label,
|
label,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Crea un **enlace de navegación** con el aspecto de un botón (`<a href=...>`).
|
||||||
|
///
|
||||||
|
/// A diferencia de [`Button::submit()`], [`Button::reset()`] y [`Button::plain()`], que
|
||||||
|
/// siempre renderizan un `<button>`, este constructor produce un enlace real. Navega a `route`
|
||||||
|
/// en vez de interactuar con un formulario. Se aplican las mismas clases de estilo (ver
|
||||||
|
/// [`with_style()`](Self::with_style)), por lo que el tema activo puede mostrarlo igual que
|
||||||
|
/// cualquier otra variante de `Button`. No confundir con [`button::Style::Link`], que es sólo
|
||||||
|
/// un estilo visual sobre un `<button>`.
|
||||||
|
pub fn anchor(label: Lc, route: impl Into<Route>) -> Self {
|
||||||
|
Self {
|
||||||
|
label,
|
||||||
|
href: route.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< Button BUILDER >*************************************************************************
|
// **< Button BUILDER >*************************************************************************
|
||||||
|
|
||||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||||
|
|
@ -148,14 +201,21 @@ impl Button {
|
||||||
|
|
||||||
/// Establece el comportamiento del botón al activarse.
|
/// Establece el comportamiento del botón al activarse.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_kind(mut self, kind: button::ButtonKind) -> Self {
|
pub fn with_kind(mut self, kind: button::Kind) -> Self {
|
||||||
self.kind = kind;
|
self.kind = kind;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Establece el estilo visual del botón (usa [`button::ButtonStyle::None`] para quitarlo).
|
/// Establece el tamaño visual del botón (usa [`button::Size::None`] para quitarlo).
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_style(mut self, style: button::ButtonStyle) -> Self {
|
pub fn with_size(mut self, size: button::Size) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el estilo visual del botón (usa [`button::Style::None`] para quitarlo).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_style(mut self, style: button::Style) -> Self {
|
||||||
self.style = style;
|
self.style = style;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
@ -194,6 +254,15 @@ impl Button {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Establece la ruta de destino y convierte el botón en enlace de navegación (`<a href=...>`).
|
||||||
|
/// Puedes usar un [`Route`] vacío (por defecto) para que vuelva a renderizarse como `<button>`.
|
||||||
|
/// Ver [`Button::anchor()`] para el constructor equivalente.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_href(mut self, route: impl Into<Route>) -> Self {
|
||||||
|
self.href = route.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Establece si el botón recibe el foco automáticamente al cargar la página.
|
/// Establece si el botón recibe el foco automáticamente al cargar la página.
|
||||||
#[builder_fn]
|
#[builder_fn]
|
||||||
pub fn with_autofocus(mut self, autofocus: bool) -> Self {
|
pub fn with_autofocus(mut self, autofocus: bool) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -2,27 +2,11 @@ use crate::prelude::*;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
// **< ButtonStyle >********************************************************************************
|
// **< Kind >***************************************************************************************
|
||||||
|
|
||||||
/// Estilo visual de un [`Button`](super::Button).
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum ButtonStyle {
|
|
||||||
/// Sin clase de estilo (estilo por defecto del tema).
|
|
||||||
#[default]
|
|
||||||
None,
|
|
||||||
/// Botón sólido: genera la clase `button-{color}`.
|
|
||||||
Solid(Intent),
|
|
||||||
/// Botón con contorno: genera la clase `button-outline-{color}`.
|
|
||||||
Outline(Intent),
|
|
||||||
/// Botón tipo enlace: genera la clase `button-link`.
|
|
||||||
Link,
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< ButtonKind >*********************************************************************************
|
|
||||||
|
|
||||||
/// Comportamiento de un [`Button`](super::Button) al activarse.
|
/// Comportamiento de un [`Button`](super::Button) al activarse.
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonKind {
|
pub enum Kind {
|
||||||
/// Envía un formulario al servidor. Es el **tipo por defecto**.
|
/// Envía un formulario al servidor. Es el **tipo por defecto**.
|
||||||
#[default]
|
#[default]
|
||||||
Submit,
|
Submit,
|
||||||
|
|
@ -33,12 +17,44 @@ pub enum ButtonKind {
|
||||||
Plain,
|
Plain,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for ButtonKind {
|
impl fmt::Display for Kind {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
ButtonKind::Submit => "submit",
|
Kind::Submit => "submit",
|
||||||
ButtonKind::Reset => "reset",
|
Kind::Reset => "reset",
|
||||||
ButtonKind::Plain => "button",
|
Kind::Plain => "button",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Size >***************************************************************************************
|
||||||
|
|
||||||
|
/// Tamaño visual de un [`Button`](super::Button).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Size {
|
||||||
|
/// Sin clase de tamaño (tamaño por defecto del tema).
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Botón compacto: genera la clase `button-sm`.
|
||||||
|
Small,
|
||||||
|
/// Botón grande: genera la clase `button-lg`.
|
||||||
|
Large,
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Style >**************************************************************************************
|
||||||
|
|
||||||
|
/// Estilo visual de un [`Button`](super::Button).
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Style {
|
||||||
|
/// Sin clase de estilo (estilo por defecto del tema).
|
||||||
|
#[default]
|
||||||
|
None,
|
||||||
|
/// Botón sólido: genera la clase `button-{color}`.
|
||||||
|
Solid(Intent),
|
||||||
|
/// Botón con contorno: genera la clase `button-outline-{color}`.
|
||||||
|
Outline(Intent),
|
||||||
|
/// Botón tipo enlace: genera la clase `button-link`. Es sólo un estilo visual; el elemento
|
||||||
|
/// sigue siendo un `<button>`. Para un enlace de navegación real, usa
|
||||||
|
/// [`Button::anchor()`](super::Button::anchor) en su lugar.
|
||||||
|
Link,
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,17 +48,16 @@ async fn title_attribute_can_be_cleared_with_lc_none() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn style_class_reflects_intent_and_style() {
|
async fn style_class_reflects_intent_and_style() {
|
||||||
let mut button =
|
let mut button = Button::submit(Lc::n("Save")).with_style(button::Style::Solid(Intent::Severe));
|
||||||
Button::submit(Lc::n("Save")).with_style(button::ButtonStyle::Solid(Intent::Danger));
|
|
||||||
let html = button.render(&mut Context::default()).await.into_string();
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
assert!(html.contains("button-danger"));
|
assert!(html.contains("button-severe"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn outline_style_generates_outline_class() {
|
async fn outline_style_generates_outline_class() {
|
||||||
let mut button =
|
let mut button =
|
||||||
Button::submit(Lc::n("Save")).with_style(button::ButtonStyle::Outline(Intent::Primary));
|
Button::submit(Lc::n("Save")).with_style(button::Style::Outline(Intent::Primary));
|
||||||
let html = button.render(&mut Context::default()).await.into_string();
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
assert!(html.contains("button-outline-primary"));
|
assert!(html.contains("button-outline-primary"));
|
||||||
|
|
@ -66,12 +65,84 @@ async fn outline_style_generates_outline_class() {
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn link_style_generates_link_class_without_intent() {
|
async fn link_style_generates_link_class_without_intent() {
|
||||||
let mut button = Button::plain(Lc::n("Cancel")).with_style(button::ButtonStyle::Link);
|
let mut button = Button::plain(Lc::n("Cancel")).with_style(button::Style::Link);
|
||||||
let html = button.render(&mut Context::default()).await.into_string();
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
assert!(html.contains("button-link"));
|
assert!(html.contains("button-link"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn size_small_generates_button_sm_class() {
|
||||||
|
let mut button = Button::submit(Lc::n("Save")).with_size(button::Size::Small);
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(html.contains("button-sm"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn size_large_generates_button_lg_class() {
|
||||||
|
let mut button = Button::submit(Lc::n("Save")).with_size(button::Size::Large);
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(html.contains("button-lg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn size_none_omits_size_class_by_default() {
|
||||||
|
let mut button = Button::submit(Lc::n("Save"));
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(!html.contains("button-sm"));
|
||||||
|
assert!(!html.contains("button-lg"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn anchor_renders_as_a_tag_with_href() {
|
||||||
|
let mut button = Button::anchor(Lc::n("Edit"), "/items/1/edit");
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(html.starts_with("<a "));
|
||||||
|
assert!(html.contains(r#"href="/items/1/edit""#));
|
||||||
|
assert!(html.contains("Edit"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn button_without_href_renders_as_button_tag() {
|
||||||
|
let mut button = Button::submit(Lc::n("Save"));
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(html.starts_with("<button"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn anchor_falls_back_to_button_when_href_is_reset() {
|
||||||
|
// `Route::default()` resuelve a una ruta vacía; `prepare()` debe ignorar `href` y volver a
|
||||||
|
// renderizar un `<button>`, tal y como documenta `Button::with_href()`.
|
||||||
|
let mut button = Button::anchor(Lc::n("Edit"), "/items/1/edit").with_href(Route::default());
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(html.starts_with("<button"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn enabled_anchor_has_no_aria_disabled_or_tabindex() {
|
||||||
|
let mut button = Button::anchor(Lc::n("Edit"), "/items/1/edit");
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(!html.contains("aria-disabled"));
|
||||||
|
assert!(!html.contains("tabindex"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn disabled_anchor_omits_href_and_sets_aria_disabled() {
|
||||||
|
let mut button = Button::anchor(Lc::n("Edit"), "/items/1/edit").with_disabled(true);
|
||||||
|
let html = button.render(&mut Context::default()).await.into_string();
|
||||||
|
|
||||||
|
assert!(!html.contains("href="));
|
||||||
|
assert!(html.contains(r#"aria-disabled="true""#));
|
||||||
|
assert!(html.contains(r#"tabindex="-1""#));
|
||||||
|
}
|
||||||
|
|
||||||
// **< ButtonSet >**********************************************************************************
|
// **< ButtonSet >**********************************************************************************
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue