✨ (bootsier): Añade botones con componente Button
Mueve `ButtonAction`, `ButtonColor` y `ButtonSize` a `aux/button.rs`. Normaliza la documentación de `aux/border.rs` y `aux/color.rs`.
This commit is contained in:
parent
863e7de3df
commit
3ceb8892a2
6 changed files with 258 additions and 18 deletions
|
|
@ -9,6 +9,10 @@ pub use aux::*;
|
||||||
|
|
||||||
pub mod classes;
|
pub mod classes;
|
||||||
|
|
||||||
|
// Button.
|
||||||
|
mod button;
|
||||||
|
pub use button::Button;
|
||||||
|
|
||||||
// Container.
|
// Container.
|
||||||
pub mod container;
|
pub mod container;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
|
|
|
||||||
|
|
@ -17,4 +17,4 @@ mod rounded;
|
||||||
pub use rounded::RoundedRadius;
|
pub use rounded::RoundedRadius;
|
||||||
|
|
||||||
mod button;
|
mod button;
|
||||||
pub use button::{ButtonColor, ButtonSize};
|
pub use button::{ButtonAction, ButtonColor, ButtonSize};
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,15 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::Color;
|
use crate::theme::aux::Color;
|
||||||
|
|
||||||
/// Colores `border-*` para los bordes ([`classes::Border`](crate::theme::classes::Border)).
|
/// Esquema de color para los bordes ([`classes::Border`](crate::theme::classes::Border)).
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum BorderColor {
|
pub enum BorderColor {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// Genera internamente clases `border-{color}`.
|
/// Genera la clase `border-{color}`.
|
||||||
Theme(Color),
|
Theme(Color),
|
||||||
/// Genera internamente clases `border-{color}-subtle` (un tono suavizado del color).
|
/// Genera la clase `border-{color}-subtle` (un tono suavizado del color).
|
||||||
Subtle(Color),
|
Subtle(Color),
|
||||||
/// Color negro.
|
/// Color negro.
|
||||||
Black,
|
Black,
|
||||||
|
|
|
||||||
|
|
@ -2,17 +2,42 @@ use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::aux::Color;
|
use crate::theme::aux::Color;
|
||||||
|
|
||||||
|
// **< ButtonAction >*********************************************************************************
|
||||||
|
|
||||||
|
/// Comportamiento de un [`Button`](crate::theme::Button) al activarse.
|
||||||
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum ButtonAction {
|
||||||
|
/// Envía un formulario al servidor. Es el **tipo por defecto**.
|
||||||
|
#[default]
|
||||||
|
Submit,
|
||||||
|
/// Restablece todos los campos de un formulario a sus valores iniciales.
|
||||||
|
Reset,
|
||||||
|
/// Botón de propósito general, sin efecto predeterminado. Su comportamiento podría definirse
|
||||||
|
/// mediante JavaScript.
|
||||||
|
Plain,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ButtonAction {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(match self {
|
||||||
|
ButtonAction::Submit => "submit",
|
||||||
|
ButtonAction::Reset => "reset",
|
||||||
|
ButtonAction::Plain => "button",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// **< ButtonColor >********************************************************************************
|
// **< ButtonColor >********************************************************************************
|
||||||
|
|
||||||
/// Variantes de color `btn-*` para botones.
|
/// Esquema de color para [`Button`](crate::theme::Button).
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ButtonColor {
|
pub enum ButtonColor {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
#[default]
|
#[default]
|
||||||
Default,
|
Default,
|
||||||
/// Genera internamente clases `btn-{color}` (botón relleno).
|
/// Genera la clase `btn-{color}` (botón sólido).
|
||||||
Background(Color),
|
Background(Color),
|
||||||
/// Genera `btn-outline-{color}` (fondo transparente y contorno con borde).
|
/// Genera la clase `btn-outline-{color}` (fondo transparente con contorno coloreado).
|
||||||
Outline(Color),
|
Outline(Color),
|
||||||
/// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto.
|
/// Aplica estilo de los enlaces (`btn-link`), sin caja ni fondo, heredando el color de texto.
|
||||||
Link,
|
Link,
|
||||||
|
|
@ -33,7 +58,6 @@ impl ButtonColor {
|
||||||
classes.push(' ');
|
classes.push(' ');
|
||||||
}
|
}
|
||||||
match self {
|
match self {
|
||||||
Self::Default => {}
|
|
||||||
Self::Background(c) => {
|
Self::Background(c) => {
|
||||||
classes.push_str(Self::BTN_PREFIX);
|
classes.push_str(Self::BTN_PREFIX);
|
||||||
classes.push_str(c.as_str());
|
classes.push_str(c.as_str());
|
||||||
|
|
@ -42,9 +66,8 @@ impl ButtonColor {
|
||||||
classes.push_str(Self::BTN_OUTLINE_PREFIX);
|
classes.push_str(Self::BTN_OUTLINE_PREFIX);
|
||||||
classes.push_str(c.as_str());
|
classes.push_str(c.as_str());
|
||||||
}
|
}
|
||||||
Self::Link => {
|
Self::Link => classes.push_str(Self::BTN_LINK),
|
||||||
classes.push_str(Self::BTN_LINK);
|
Self::Default => unreachable!(),
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,7 +105,7 @@ pub enum ButtonSize {
|
||||||
Default,
|
Default,
|
||||||
/// Botón compacto.
|
/// Botón compacto.
|
||||||
Small,
|
Small,
|
||||||
/// Botón destacado/grande.
|
/// Botón grande.
|
||||||
Large,
|
Large,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -169,7 +169,7 @@ impl Opacity {
|
||||||
|
|
||||||
// **< ColorBg >************************************************************************************
|
// **< ColorBg >************************************************************************************
|
||||||
|
|
||||||
/// Colores `bg-*` para el fondo.
|
/// Esquema de color para el fondo.
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ColorBg {
|
pub enum ColorBg {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
|
|
@ -181,9 +181,9 @@ pub enum ColorBg {
|
||||||
BodySecondary,
|
BodySecondary,
|
||||||
/// Fondo predefinido del tema (`bg-body-tertiary`).
|
/// Fondo predefinido del tema (`bg-body-tertiary`).
|
||||||
BodyTertiary,
|
BodyTertiary,
|
||||||
/// Genera internamente clases `bg-{color}` (p. ej., `bg-primary`).
|
/// Genera la clase `bg-{color}` (p. ej., `bg-primary`).
|
||||||
Theme(Color),
|
Theme(Color),
|
||||||
/// Genera internamente clases `bg-{color}-subtle` (un tono suavizado del color).
|
/// Genera la clase `bg-{color}-subtle` (un tono suavizado del color).
|
||||||
Subtle(Color),
|
Subtle(Color),
|
||||||
/// Color negro.
|
/// Color negro.
|
||||||
Black,
|
Black,
|
||||||
|
|
@ -253,7 +253,7 @@ impl ColorBg {
|
||||||
|
|
||||||
// **< ColorText >**********************************************************************************
|
// **< ColorText >**********************************************************************************
|
||||||
|
|
||||||
/// Colores `text-*` para el texto.
|
/// Esquema de color para el texto.
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum ColorText {
|
pub enum ColorText {
|
||||||
/// No define ninguna clase.
|
/// No define ninguna clase.
|
||||||
|
|
@ -267,9 +267,9 @@ pub enum ColorText {
|
||||||
BodySecondary,
|
BodySecondary,
|
||||||
/// Color predefinido del tema (`text-body-tertiary`).
|
/// Color predefinido del tema (`text-body-tertiary`).
|
||||||
BodyTertiary,
|
BodyTertiary,
|
||||||
/// Genera internamente clases `text-{color}`.
|
/// Genera la clase `text-{color}`.
|
||||||
Theme(Color),
|
Theme(Color),
|
||||||
/// Genera internamente clases `text-{color}-emphasis` (mayor contraste acorde al tema).
|
/// Genera la clase `text-{color}-emphasis` (mayor contraste acorde al tema).
|
||||||
Emphasis(Color),
|
Emphasis(Color),
|
||||||
/// Color negro.
|
/// Color negro.
|
||||||
Black,
|
Black,
|
||||||
|
|
|
||||||
213
extensions/pagetop-bootsier/src/theme/button.rs
Normal file
213
extensions/pagetop-bootsier/src/theme/button.rs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
|
use crate::theme::{ButtonAction, ButtonColor, ButtonSize};
|
||||||
|
|
||||||
|
/// Componente para crear un **botón**.
|
||||||
|
///
|
||||||
|
/// Renderiza un botón con soporte para las variantes disponibles en [`ButtonAction`] (`submit`,
|
||||||
|
/// `reset` y botón genérico) y con la variedad de estilos del tema a través de [`ButtonColor`] y
|
||||||
|
/// [`ButtonSize`].
|
||||||
|
///
|
||||||
|
/// El comportamiento del botón se establece al crearlo:
|
||||||
|
///
|
||||||
|
/// - [`Button::submit()`]: botón de envío (por defecto).
|
||||||
|
/// - [`Button::reset()`]: botón de restablecimiento de valores.
|
||||||
|
/// - [`Button::plain()`]: botón genérico sin comportamiento predeterminado.
|
||||||
|
///
|
||||||
|
/// El botón puede usarse dentro o fuera de un formulario.
|
||||||
|
///
|
||||||
|
/// # Ejemplo
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// # use pagetop_bootsier::prelude::*;
|
||||||
|
/// let save = Button::submit(L10n::n("Save"))
|
||||||
|
/// .with_color(ButtonColor::Background(Color::Primary));
|
||||||
|
///
|
||||||
|
/// let cancel = Button::plain(L10n::n("Cancel"))
|
||||||
|
/// .with_color(ButtonColor::Outline(Color::Secondary));
|
||||||
|
///
|
||||||
|
/// let clear = Button::reset(L10n::n("Clear"))
|
||||||
|
/// .with_size(ButtonSize::Small);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Cuando el botón activa el envío, el navegador incluye el par `name=value` en los datos del
|
||||||
|
/// formulario **sólo si** tiene el atributo `name` definido. Es la forma habitual de identificar
|
||||||
|
/// cuál de los botones de envío fue pulsado. En el servidor se deserializa como `Option<String>`:
|
||||||
|
///
|
||||||
|
/// ```rust,ignore
|
||||||
|
/// #[derive(serde::Deserialize)]
|
||||||
|
/// struct FormData {
|
||||||
|
/// #[serde(default)]
|
||||||
|
/// action: Option<String>, // p. ej., "save" o "delete"; `None` si el botón no tenía `name`.
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
|
pub struct Button {
|
||||||
|
#[getters(skip)]
|
||||||
|
id: AttrId,
|
||||||
|
/// Devuelve las clases CSS del botón.
|
||||||
|
classes: Classes,
|
||||||
|
/// Devuelve el comportamiento del botón al activarse.
|
||||||
|
kind: ButtonAction,
|
||||||
|
/// Devuelve el esquema de color del botón.
|
||||||
|
color: ButtonColor,
|
||||||
|
/// Devuelve el tamaño visual del botón.
|
||||||
|
size: ButtonSize,
|
||||||
|
/// Devuelve el nombre del botón.
|
||||||
|
name: AttrName,
|
||||||
|
/// Devuelve el valor del botón.
|
||||||
|
value: AttrValue,
|
||||||
|
/// Devuelve la etiqueta del botón.
|
||||||
|
label: Attr<L10n>,
|
||||||
|
/// Devuelve si el botón recibe el foco automáticamente al cargar la página.
|
||||||
|
autofocus: bool,
|
||||||
|
/// Devuelve si el botón está deshabilitado.
|
||||||
|
disabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Button {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn id(&self) -> Option<String> {
|
||||||
|
self.id.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self, _cx: &Context) {
|
||||||
|
let mut classes = "btn".to_string();
|
||||||
|
(*self.color()).push_class(&mut classes);
|
||||||
|
(*self.size()).push_class(&mut classes);
|
||||||
|
self.alter_classes(ClassesOp::Prepend, classes);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
|
Ok(html! {
|
||||||
|
button
|
||||||
|
id=[self.id()]
|
||||||
|
type=(self.kind())
|
||||||
|
class=[self.classes().get()]
|
||||||
|
name=[self.name().get()]
|
||||||
|
value=[self.value().get()]
|
||||||
|
autofocus[*self.autofocus()]
|
||||||
|
disabled[*self.disabled()]
|
||||||
|
{
|
||||||
|
@if let Some(label) = self.label().lookup(cx) {
|
||||||
|
(label)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Button {
|
||||||
|
/// Crea un botón de **envío** (`type="submit"`).
|
||||||
|
///
|
||||||
|
/// Es la acción predeterminada al pulsar un botón en la mayoría de los formularios: envía los
|
||||||
|
/// datos al servidor.
|
||||||
|
pub fn submit(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Submit,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un botón de **restablecimiento** (`type="reset"`).
|
||||||
|
///
|
||||||
|
/// Al pulsarlo, devuelve todos los campos del formulario a sus valores iniciales.
|
||||||
|
pub fn reset(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Reset,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crea un **botón genérico** (`type="button"`).
|
||||||
|
///
|
||||||
|
/// No tiene un comportamiento predeterminado sobre el formulario. Su comportamiento puede
|
||||||
|
/// definirse mediante JavaScript.
|
||||||
|
pub fn plain(label: L10n) -> Self {
|
||||||
|
Self {
|
||||||
|
kind: ButtonAction::Plain,
|
||||||
|
label: Attr::some(label),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// **< Button BUILDER >*************************************************************************
|
||||||
|
|
||||||
|
/// Establece el identificador único (`id`) del botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_id(mut self, id: impl AsRef<str>) -> Self {
|
||||||
|
self.id.alter_id(id);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Modifica la lista de clases CSS aplicadas al botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_classes(mut self, op: ClassesOp, classes: impl AsRef<str>) -> Self {
|
||||||
|
self.classes.alter_classes(op, classes);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el esquema de color del botón.
|
||||||
|
///
|
||||||
|
/// Usa [`ButtonColor::Background`] para botones sólidos o [`ButtonColor::Outline`] para
|
||||||
|
/// variantes con contorno.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_color(mut self, color: ButtonColor) -> Self {
|
||||||
|
self.color = color;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el tamaño visual del botón.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_size(mut self, size: ButtonSize) -> Self {
|
||||||
|
self.size = size;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el nombre del botón (atributo `name`).
|
||||||
|
///
|
||||||
|
/// Cuando el formulario tiene varios botones de envío, el navegador incluye en el envío el par
|
||||||
|
/// `name=value` sólo del botón que activó el formulario. Permite identificar cuál fue pulsado.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_name(mut self, name: impl AsRef<str>) -> Self {
|
||||||
|
self.name.alter_name(name);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece el valor del botón (atributo `value`).
|
||||||
|
///
|
||||||
|
/// Es el dato que el navegador transmite al servidor junto con el `name` cuando este botón
|
||||||
|
/// activa el envío. Útil para distinguir entre varios botones de envío en un mismo formulario.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_value(mut self, value: impl AsRef<str>) -> Self {
|
||||||
|
self.value.alter_str(value);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece o elimina la etiqueta visible del botón (basta pasar `None` para quitarla).
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_label(mut self, label: impl Into<Option<L10n>>) -> Self {
|
||||||
|
self.label.alter_opt(label.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el botón recibe el foco automáticamente al cargar la página.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_autofocus(mut self, autofocus: bool) -> Self {
|
||||||
|
self.autofocus = autofocus;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Establece si el botón está deshabilitado.
|
||||||
|
#[builder_fn]
|
||||||
|
pub fn with_disabled(mut self, disabled: bool) -> Self {
|
||||||
|
self.disabled = disabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue