From 56a6691c87ce74aa3c8e86b1e6fe1e4e38abbd7f Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 28 Aug 2026 01:10:45 +0200 Subject: [PATCH 1/4] =?UTF-8?q?=F0=9F=9A=A7=20(theme):=20Renombra=20varian?= =?UTF-8?q?tes=20de=20Intent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Renombra `Intent::Secondary`/`Danger` a `Neutral`/`Severe` y actualiza Badge y su CSS por defecto (basic.css) al nuevo vocabulario. Introduce el punto de extensión `Theme::intent_color()`, que traduce una `Intent` al nombre de color de la paleta propia de cada tema, con `Intent::color(cx)` como atajo de uso desde los componentes. --- assets/css/basic.css | 28 ++++++++++++------------ src/base/component/badge.rs | 22 +++++++++---------- src/core/theme.rs | 35 ++++++++++++++++++++++-------- src/core/theme/definition.rs | 30 +++++++++++++++++++++++++- src/core/theme/intent.rs | 42 ++++++++++++++++++++++-------------- tests/component_badge.rs | 20 ++++++++--------- 6 files changed, 116 insertions(+), 61 deletions(-) diff --git a/assets/css/basic.css b/assets/css/basic.css index 2c69d35c..47cb26e9 100644 --- a/assets/css/basic.css +++ b/assets/css/basic.css @@ -81,11 +81,11 @@ body { color: #000; } .badge-primary { background-color: var(--val-color--primary); } -.badge-secondary { background-color: var(--val-color--secondary); } +.badge-neutral { background-color: var(--val-color--secondary); } .badge-success { background-color: var(--val-color--success); } .badge-info { background-color: var(--val-color--info); } .badge-warning { background-color: var(--val-color--warning); } -.badge-danger { background-color: var(--val-color--danger); } +.badge-severe { background-color: var(--val-color--danger); } /* * Buttons @@ -117,9 +117,9 @@ body { } .button-primary, -.button-secondary, +.button-neutral, .button-success, -.button-danger { +.button-severe { color: #fff; } .button-info, @@ -127,40 +127,40 @@ body { color: #000; } .button-primary { background-color: var(--val-color--primary); border-color: var(--val-color--primary); } -.button-secondary { background-color: var(--val-color--secondary); border-color: var(--val-color--secondary); } +.button-neutral { background-color: var(--val-color--secondary); border-color: var(--val-color--secondary); } .button-success { background-color: var(--val-color--success); border-color: var(--val-color--success); } .button-info { background-color: var(--val-color--info); border-color: var(--val-color--info); } .button-warning { background-color: var(--val-color--warning); border-color: var(--val-color--warning); } -.button-danger { background-color: var(--val-color--danger); border-color: var(--val-color--danger); } +.button-severe { background-color: var(--val-color--danger); border-color: var(--val-color--danger); } .button-primary:hover { background-color: color-mix(in srgb, var(--val-color--primary) 85%, black); border-color: color-mix(in srgb, var(--val-color--primary) 85%, black); } -.button-secondary:hover { background-color: color-mix(in srgb, var(--val-color--secondary) 85%, black); border-color: color-mix(in srgb, var(--val-color--secondary) 85%, black); } +.button-neutral:hover { background-color: color-mix(in srgb, var(--val-color--secondary) 85%, black); border-color: color-mix(in srgb, var(--val-color--secondary) 85%, black); } .button-success:hover { background-color: color-mix(in srgb, var(--val-color--success) 85%, black); border-color: color-mix(in srgb, var(--val-color--success) 85%, black); } .button-info:hover { background-color: color-mix(in srgb, var(--val-color--info) 85%, black); border-color: color-mix(in srgb, var(--val-color--info) 85%, black); } .button-warning:hover { background-color: color-mix(in srgb, var(--val-color--warning) 85%, black); border-color: color-mix(in srgb, var(--val-color--warning) 85%, black); } -.button-danger:hover { background-color: color-mix(in srgb, var(--val-color--danger) 85%, black); border-color: color-mix(in srgb, var(--val-color--danger) 85%, black); } +.button-severe:hover { background-color: color-mix(in srgb, var(--val-color--danger) 85%, black); border-color: color-mix(in srgb, var(--val-color--danger) 85%, black); } .button-outline-primary, -.button-outline-secondary, +.button-outline-neutral, .button-outline-success, .button-outline-info, .button-outline-warning, -.button-outline-danger { +.button-outline-severe { background-color: transparent; } .button-outline-primary { border-color: var(--val-color--primary); color: var(--val-color--primary); } -.button-outline-secondary { border-color: var(--val-color--secondary); color: var(--val-color--secondary); } +.button-outline-neutral { border-color: var(--val-color--secondary); color: var(--val-color--secondary); } .button-outline-success { border-color: var(--val-color--success); color: var(--val-color--success); } .button-outline-info { border-color: var(--val-color--info); color: var(--val-color--info); } .button-outline-warning { border-color: var(--val-color--warning); color: var(--val-color--warning); } -.button-outline-danger { border-color: var(--val-color--danger); color: var(--val-color--danger); } +.button-outline-severe { border-color: var(--val-color--danger); color: var(--val-color--danger); } .button-outline-primary:hover { background-color: var(--val-color--primary); border-color: var(--val-color--primary); color: #fff; } -.button-outline-secondary:hover { background-color: var(--val-color--secondary); border-color: var(--val-color--secondary); color: #fff; } +.button-outline-neutral:hover { background-color: var(--val-color--secondary); border-color: var(--val-color--secondary); color: #fff; } .button-outline-success:hover { background-color: var(--val-color--success); border-color: var(--val-color--success); color: #fff; } .button-outline-info:hover { background-color: var(--val-color--info); border-color: var(--val-color--info); color: #000; } .button-outline-warning:hover { background-color: var(--val-color--warning); border-color: var(--val-color--warning); color: #000; } -.button-outline-danger:hover { background-color: var(--val-color--danger); border-color: var(--val-color--danger); color: #fff; } +.button-outline-severe:hover { background-color: var(--val-color--danger); border-color: var(--val-color--danger); color: #fff; } .button-link { font-weight: 400; diff --git a/src/base/component/badge.rs b/src/base/component/badge.rs index a01a61c5..1a7c45a0 100644 --- a/src/base/component/badge.rs +++ b/src/base/component/badge.rs @@ -6,10 +6,10 @@ use crate::prelude::*; /// /// ```rust,no_run /// # use pagetop::prelude::*; -/// let badge = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Danger); +/// let badge = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Severe); /// /// // Equivalente usando el constructor directo del tipo. -/// let badge = Badge::danger(Lc::n("Admin")); +/// let badge = Badge::severe(Lc::n("Admin")); /// ``` #[derive(AutoDefault, Clone, Debug, Getters)] pub struct Badge { @@ -19,7 +19,7 @@ pub struct Badge { label: Lc, /// Devuelve la intención semántica del badge. #[getters(copy)] - #[default(Intent::Secondary)] + #[default(Intent::Neutral)] intent: Intent, } @@ -33,10 +33,10 @@ impl Component for Badge { self.props.get_id() } - fn setup(&mut self, _cx: &Context) { + fn setup(&mut self, cx: &Context) { self.alter_prop(PropsOp::prepend_classes(util::join!( "badge badge-", - self.intent().as_str() + self.intent().color(cx) ))); } @@ -67,11 +67,11 @@ impl Badge { } } - /// Crea un badge de tipo *secondary* con la etiqueta indicada. - pub fn secondary(label: Lc) -> Self { + /// Crea un badge de tipo *neutral* con la etiqueta indicada. + pub fn neutral(label: Lc) -> Self { Self { label, - intent: Intent::Secondary, + intent: Intent::Neutral, ..Default::default() } } @@ -103,11 +103,11 @@ impl Badge { } } - /// Crea un badge de tipo *danger* con la etiqueta indicada. - pub fn danger(label: Lc) -> Self { + /// Crea un badge de tipo *severe* con la etiqueta indicada. + pub fn severe(label: Lc) -> Self { Self { label, - intent: Intent::Danger, + intent: Intent::Severe, ..Default::default() } } diff --git a/src/core/theme.rs b/src/core/theme.rs index 09dd0a2e..1e8cba33 100644 --- a/src/core/theme.rs +++ b/src/core/theme.rs @@ -13,12 +13,14 @@ //! //! # Temas hijo, herencia y componentes //! -//! PageTop permite crear **temas hijo** que refinan el comportamiento de su tema padre. Un tema -//! hijo hereda automáticamente todos los métodos del padre y puede sobrescribirlos selectivamente. -//! Esta herencia sólo determina qué implementación de sus métodos se usa cuando el tema hijo no los -//! sobrescribe (como el renderizado del `` y del ``, los recursos incorporados, el uso -//! de [`Theme::handle_component()`], las páginas de error, etc.). Un tema hijo puede ser a su vez -//! padre de otro, basta declararlo cada vez con [`Theme::parent()`]. +//! PageTop permite crear **temas hijo** que refinan el comportamiento de su tema padre, +//! identificado por [`Theme::parent()`]. Un tema hijo hereda automáticamente todos los métodos del +//! padre y puede sobrescribirlos selectivamente. Esta herencia determina qué implementación de sus +//! métodos se usa cuando el tema hijo no los sobrescribe (ya sea el renderizado del `` o del +//! ``, la definición de los recursos necesarios, la asignación de colores por intención vía +//! [`Theme::intent_color()`], la captura de componentes con [`Theme::handle_component()`], las +//! páginas de error, etc.). Un tema hijo puede ser a su vez padre de otro, basta declararlo cada +//! vez con [`Theme::parent()`]. //! //! Como `parent()` se resuelve en tiempo de ejecución, PageTop no puede descartar en compilación //! referencias circulares (un tema acaba siendo padre de sí mismo, directa o transitivamente). Ese @@ -48,8 +50,8 @@ //! devuelva `Some(&Self)`. Basta con un `impl Theme for MyTheme {}` vacío, ya que todos los //! métodos de [`Theme`] tienen implementación por defecto. //! -//! Un tema puede personalizarse en tres pasos, cada uno necesario sólo si lo que ofrece PageTop por -//! defecto no basta: +//! Un tema puede personalizarse en cinco pasos, cada uno necesario sólo si lo que ofrece PageTop +//! por defecto no basta: //! //! 1. **Definir regiones nuevas**. Por defecto, PageTop define [`CoreRegions`] (`Header`, `Aside`, //! `Content`, `Footer`) como regiones de plantilla siempre disponibles, y [`ReservedRegions`] @@ -75,6 +77,21 @@ //! ejemplo, [`CoreTemplates`] o el propio *enum* del tema). `pagetop-bootsier` hace exactamente //! esto para maquetar `Standard` y `Admin` de forma distinta, sin necesitar sus propias //! variantes de plantilla. +//! 4. **Traducir [`Intent`] a la paleta de colores propia del tema** sobrescribiendo +//! [`Theme::intent_color()`]. Por defecto, este método devuelve el vocabulario semántico de +//! [`Intent`] (`"primary"`, `"severe"`, etc.); un tema con su propio catálogo de colores (por +//! ejemplo, uno basado en Bootstrap) debe traducir cada variante al nombre que le corresponda en +//! su paleta. Los componentes que generan clases CSS a partir de una [`Intent`] (`Button`, +//! `Badge`, `Dropdown`, etc.) consultan este método a través de [`Intent::color()`], así que la +//! clase resultante ya nace en la paleta del tema activo. +//! 5. **Reexportar, extender o añadir componentes**. Un tema puede reexportar tal cual los +//! componentes propios de PageTop que no requieran adaptación, extenderlos con un trait propio +//! para añadir métodos exclusivos (guardando su estado en valores extra con +//! [`PropsOp::set_extra()`](crate::html::PropsOp::set_extra) para consumirlos en el +//! `setup()`/`render()` vía [`Theme::handle_component()`]), o aportar componentes propios. +//! `pagetop-bootsier` combina las tres estrategias: reexporta `Form`/`Fieldset` sin cambios, +//! extiende `Button`/`Badge`/`Dropdown`/`Nav`/`Navbar` con sus propios traits (`ButtonBootsier`, +//! `BadgeBootsier`, etc.), y añade componentes propios como `Offcanvas`. //! //! Para forzar una plantilla completamente distinta en una página concreta, se puede llamar //! manualmente a [`with_template()`](crate::core::component::Contextual::with_template). @@ -86,7 +103,7 @@ //! plantilla distinta. //! //! El resto del comportamiento de un tema (por ejemplo, el renderizado del ``) se sobrescribe -//! de forma independiente de estos tres pasos y no es necesario para tener un tema funcional. +//! de forma independiente de estos pasos y no es necesario para tener un tema funcional. //! //! # Componentes que se procesan en todas las páginas //! diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs index 70767cac..b8a83d01 100644 --- a/src/core/theme/definition.rs +++ b/src/core/theme/definition.rs @@ -3,7 +3,7 @@ use crate::base::component::{Html, Intro, IntroOpening, layout}; use crate::core::component::{ChildOp, Component, ComponentError, ComponentRender}; use crate::core::component::{Context, Contextual}; use crate::core::extension::Extension; -use crate::core::theme::CoreRegions; +use crate::core::theme::{CoreRegions, Intent}; use crate::global; use crate::html::{Markup, html}; use crate::locale::Lc; @@ -65,6 +65,34 @@ pub trait Theme: Extension + Send + Sync { None } + /// Traduce una [`Intent`] al nombre de color de la paleta propia del tema. + /// + /// `Intent` no define ninguna cadena propia. Cada tema decide qué nombre le corresponde a cada + /// variante en su paleta (p. ej. un tema basado en Bootstrap traduce `Severe` a `"danger"`). + /// Los componentes que generan clases CSS a partir de una `Intent` (`Button`, `Badge`, + /// `Dropdown`, etc.) no llaman a este método directamente en su `setup()`, usan mejor + /// [`Intent::color()`](crate::core::theme::Intent::color) como la forma más sencilla de obtener + /// este mismo valor a través de [`Context::theme()`](crate::core::component::Context::theme), + /// para que la clase resultante ya nazca en la paleta del tema activo. + /// + /// La implementación por defecto devuelve el vocabulario semántico propio de PageTop + /// (`"primary"`, `"severe"`, etc.), que actúa como paleta base cuando ningún tema la + /// sobrescribe. + #[rustfmt::skip] + fn intent_color(&self, intent: Intent) -> &'static str { + if let Some(parent) = self.parent() { + return parent.intent_color(intent); + } + match intent { + Intent::Primary => "primary", + Intent::Neutral => "neutral", + Intent::Info => "info", + Intent::Success => "success", + Intent::Warning => "warning", + Intent::Severe => "severe", + } + } + /// Acciones específicas del tema antes de renderizar el `` de la página. /// /// Es un buen lugar para inicializar o ajustar recursos en función del contexto de la página, diff --git a/src/core/theme/intent.rs b/src/core/theme/intent.rs index ff6e4626..2fb03a94 100644 --- a/src/core/theme/intent.rs +++ b/src/core/theme/intent.rs @@ -1,31 +1,41 @@ use crate::AutoDefault; +use crate::core::component::{Context, Contextual}; /// Intención semántica de un componente visual. /// -/// Representa la intención que pretende comunicar un componente (énfasis principal, confirmación de -/// un evento exitoso, aviso, peligro, etc.). Cada tema decidirá cómo pintarlo. +/// Describe *qué comunica* un componente, y cada tema traduce la intención a su propia paleta de +/// colores. Define un vocabulario con variantes por énfasis (`Primary`, `Neutral`), un canal +/// informativo (`Info`) y una escala de severidad (`Success`, `Warning`, `Severe`). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Intent { + /// Énfasis principal, suele ser la acción que más destaca a la vista. #[default] Primary, - Secondary, - Success, + /// Presencia sin énfasis, una opción discreta o normal. + Neutral, + /// Informa de algo que merece leerse, ajeno a la escala de severidad. Encaja en notas o avisos + /// informativos. Info, + /// Destaca algo que ha ido bien, un estado favorable o un camino afirmativo. En una alerta + /// podría indicar la confirmación de una operación correcta o un estado saludable; en un botón, + /// la acción que confirma o aprueba algo positivamente. + Success, + /// Precaución, representa algo que requiere atención o cuidado. En una alerta, sería un + /// problema no bloqueante o un aviso; en un botón, una acción que conviene meditar sin llegar a + /// ser destructiva. Warning, - Danger, + /// Algo va mal, es peligroso o destructivo. En una alerta sería un error o un fallo bloqueante; + /// en un botón, la acción destructiva o irreversible (p. ej. eliminar). + Severe, } impl Intent { - /// Devuelve el nombre de la intención (`"primary"`, `"danger"`, etc.). - #[rustfmt::skip] - pub const fn as_str(&self) -> &'static str { - match self { - Self::Primary => "primary", - Self::Secondary => "secondary", - Self::Success => "success", - Self::Info => "info", - Self::Warning => "warning", - Self::Danger => "danger", - } + /// Devuelve el nombre del color asociado a la intención en el tema activo del contexto actual. + /// + /// Atajo de [`Theme::intent_color()`](crate::core::theme::Theme::intent_color) a través de + /// [`Context::theme()`]. La intención no tiene una cadena propia, es el tema activo quien + /// decide su traducción. + pub fn color(&self, cx: &Context) -> &'static str { + cx.theme().intent_color(*self) } } diff --git a/tests/component_badge.rs b/tests/component_badge.rs index 2d039e3b..f6a5e2d2 100644 --- a/tests/component_badge.rs +++ b/tests/component_badge.rs @@ -28,22 +28,22 @@ async fn renders_as_a_span_element() { } #[pagetop::test] -async fn default_intent_is_secondary() { +async fn default_intent_is_neutral() { let mut badge = Badge::labeled(Lc::n("Admin")); let html = badge.render(&mut Context::default()).await.into_string(); - assert!(html.contains(r#"class="badge badge-secondary""#)); + assert!(html.contains(r#"class="badge badge-neutral""#)); } #[pagetop::test] async fn each_direct_constructor_sets_its_intent() { let cases = [ (Badge::primary(Lc::n("x")), "badge-primary"), - (Badge::secondary(Lc::n("x")), "badge-secondary"), + (Badge::neutral(Lc::n("x")), "badge-neutral"), (Badge::success(Lc::n("x")), "badge-success"), (Badge::info(Lc::n("x")), "badge-info"), (Badge::warning(Lc::n("x")), "badge-warning"), - (Badge::danger(Lc::n("x")), "badge-danger"), + (Badge::severe(Lc::n("x")), "badge-severe"), ]; for (mut badge, expected_class) in cases { let html = badge.render(&mut Context::default()).await.into_string(); @@ -56,16 +56,16 @@ async fn each_direct_constructor_sets_its_intent() { #[pagetop::test] async fn with_intent_overrides_the_default() { - let mut badge = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Danger); + let mut badge = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Severe); let html = badge.render(&mut Context::default()).await.into_string(); - assert!(html.contains(r#"class="badge badge-danger""#)); + assert!(html.contains(r#"class="badge badge-severe""#)); } #[pagetop::test] async fn direct_constructor_is_equivalent_to_labeled_with_intent() { - let mut from_constructor = Badge::danger(Lc::n("Admin")); - let mut from_builder = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Danger); + let mut from_constructor = Badge::severe(Lc::n("Admin")); + let mut from_builder = Badge::labeled(Lc::n("Admin")).with_intent(Intent::Severe); assert_eq!( from_constructor @@ -89,9 +89,9 @@ async fn with_id_sets_the_identifier() { #[pagetop::test] async fn with_prop_adds_extra_classes_alongside_the_intent_class() { - let mut badge = Badge::danger(Lc::n("Admin")).with_prop(PropsOp::add_classes("custom")); + let mut badge = Badge::severe(Lc::n("Admin")).with_prop(PropsOp::add_classes("custom")); let html = badge.render(&mut Context::default()).await.into_string(); - assert!(html.contains("badge-danger")); + assert!(html.contains("badge-severe")); assert!(html.contains("custom")); } From 434d31c5ebc45bd091ba2daaa67b38e05562ac62 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 28 Aug 2026 11:16:58 +0200 Subject: [PATCH 2/4] =?UTF-8?q?=F0=9F=93=9D=20(doc):=20Sustituye=20"..."?= =?UTF-8?q?=20por=20"etc."?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/web.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/web.rs b/src/web.rs index 498f9570..0ede75a8 100644 --- a/src/web.rs +++ b/src/web.rs @@ -67,11 +67,11 @@ use std::task::{Context, Poll}; /// ``` /// /// `HttpRequest` no consume las extensiones de la petición, por lo que el resto de extractores del -/// handler que también las necesiten (como `Path`, `Extension`...) las siguen viendo +/// handler que también las necesiten (como `Path`, `Extension`, etc.) las siguen viendo /// intactas, sea cual sea la posición en la que se declare `HttpRequest`. Por convención, se /// declara como primer parámetro del handler. El único orden que sigue siendo obligatorio es el que -/// impone Axum: un extractor que consuma el cuerpo de la petición (`Form`, `RawForm`...) debe ir -/// siempre el último. +/// impone Axum: un extractor que consuma el cuerpo de la petición (`Form`, `RawForm`, etc.) debe +/// ir siempre el último. #[derive(Clone, Debug)] pub struct HttpRequest { uri: http::Uri, @@ -120,9 +120,9 @@ impl FromRequestParts for HttpRequest { type Rejection = Infallible; // Clona (no toma) las extensiones inyectadas por middleware, para que otros extractores del - // handler (`Path`, `Extension`...) las sigan viendo intactas sin importar en qué posición - // se declare `HttpRequest`. El clon se envuelve en un `Arc` compartido para que `HttpRequest` - // sea `Clone` a coste mínimo en el resto de su ciclo de vida. + // handler (`Path`, `Extension`, etc.) las sigan viendo intactas sin importar en qué + // posición se declare `HttpRequest`. El clon se envuelve en un `Arc` compartido para que + // `HttpRequest` sea `Clone` a coste mínimo en el resto de su ciclo de vida. async fn from_request_parts( parts: &mut http::request::Parts, _state: &S, From da959183f690aa479352dad3f8ff721c91ef7538 Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Fri, 28 Aug 2026 11:38:57 +0200 Subject: [PATCH 3/4] =?UTF-8?q?=E2=9C=A8=20(button):=20A=C3=B1ade=20Button?= =?UTF-8?q?::anchor()=20para=20navegaci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Renderiza un `` 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`). --- assets/css/basic.css | 10 +++ examples/form-controls.rs | 40 +++++----- src/base/component/button.rs | 2 +- src/base/component/button/component.rs | 103 +++++++++++++++++++++---- src/base/component/button/props.rs | 60 ++++++++------ tests/component_button.rs | 81 +++++++++++++++++-- 6 files changed, 229 insertions(+), 67 deletions(-) diff --git a/assets/css/basic.css b/assets/css/basic.css index 47cb26e9..50e870c2 100644 --- a/assets/css/basic.css +++ b/assets/css/basic.css @@ -101,6 +101,7 @@ body { background-color: var(--val-color--primary); border: 1px solid var(--val-color--primary); border-radius: 0.375rem; + text-decoration: none; cursor: pointer; transition: background-color .15s ease-in-out, border-color .15s ease-in-out; } @@ -116,6 +117,15 @@ body { 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-neutral, .button-success, diff --git a/examples/form-controls.rs b/examples/form-controls.rs index 6b064165..8273f207 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -142,18 +142,17 @@ async fn form_controls(request: HttpRequest) -> Result { .with_child( button::ButtonSet::new() .with_button( - Button::submit(Lc::t("btn_submit", &LOC)).with_style( - button::ButtonStyle::Solid(Intent::Primary), - ), + Button::submit(Lc::t("btn_submit", &LOC)) + .with_style(button::Style::Solid(Intent::Primary)), ) .with_button( Button::reset(Lc::t("btn_reset", &LOC)).with_style( - button::ButtonStyle::Outline(Intent::Secondary), + button::Style::Outline(Intent::Neutral), ), ) .with_button( 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 { .with_child( button::ButtonSet::new() .with_button( - Button::submit(Lc::t("btn_submit", &LOC)).with_style( - button::ButtonStyle::Solid(Intent::Primary), - ), + Button::submit(Lc::t("btn_submit", &LOC)) + .with_style(button::Style::Solid(Intent::Primary)), ) .with_button( Button::reset(Lc::t("btn_reset", &LOC)).with_style( - button::ButtonStyle::Outline(Intent::Secondary), + button::Style::Outline(Intent::Neutral), ), ) .with_button( 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 { 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( Button::plain(Lc::t("btn_ok", &LOC)) .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( Button::plain(Lc::t("btn_delete", &LOC)) .with_prop(PropsOp::set("data-dialog-toggle", "modal")) .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() .with_button( Button::submit(Lc::t("btn_submit", &LOC)) - .with_style(button::ButtonStyle::Solid(Intent::Primary)), + .with_style(button::Style::Solid(Intent::Primary)), ) .with_button( Button::reset(Lc::t("btn_reset", &LOC)) - .with_style(button::ButtonStyle::Outline(Intent::Secondary)), + .with_style(button::Style::Outline(Intent::Neutral)), ) .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), ), ) } diff --git a/src/base/component/button.rs b/src/base/component/button.rs index fdfc22ea..4bbb1787 100644 --- a/src/base/component/button.rs +++ b/src/base/component/button.rs @@ -1,7 +1,7 @@ //! Definiciones para crear botones ([`Button`]) y conjuntos de botones ([`ButtonSet`]). mod props; -pub use props::{ButtonKind, ButtonStyle}; +pub use props::{Kind, Size, Style}; mod component; pub use component::Button; diff --git a/src/base/component/button/component.rs b/src/base/component/button/component.rs index aef92e7d..34a1bb1f 100644 --- a/src/base/component/button/component.rs +++ b/src/base/component/button/component.rs @@ -7,6 +7,12 @@ use crate::prelude::*; /// - [`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. +/// - [`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 `` 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 `