diff --git a/assets/css/basic.css b/assets/css/basic.css
index 72436c5b..cfb3d107 100644
--- a/assets/css/basic.css
+++ b/assets/css/basic.css
@@ -10,18 +10,20 @@
--val-fw--base: 400;
/* Line height */
--val-lh--base: 1.5;
- /* Colors */
- --val-color--bg: #fafafa;
- --val-color--text: #212529;
- --val-color--text--muted: color-mix(in srgb, var(--val-color--text) 50%, var(--val-color--bg));
- --val-color--border: #212529;
+ /* Core Colors */
--val-color--primary: #0d6efd;
--val-color--secondary: #6c757d;
- --val-color--light: #dee2e6;
+ --val-color--success: #198754;
--val-color--info: #0dcaf0;
--val-color--warning: #ffc107;
--val-color--danger: #dc3545;
+ /* Other Colors */
+ --val-color--bg: #fafafa;
+ --val-color--bg--muted: #dee2e6;
+ --val-color--text: #212529;
+ --val-color--text--muted: color-mix(in srgb, var(--val-color--text) 50%, var(--val-color--bg));
--val-color--switch-off: #adb5bd;
+ --val-color--border: #212529;
}
*, *::before, *::after {
diff --git a/src/core/theme.rs b/src/core/theme.rs
index 959dac8d..fa9708cd 100644
--- a/src/core/theme.rs
+++ b/src/core/theme.rs
@@ -117,285 +117,12 @@
//!
//! [`ReservedRegions`]: crate::response::ReservedRegions
-use crate::AutoDefault;
-use crate::core::AnyInfo;
-use crate::locale::Lc;
+mod color;
+pub use color::{ColorName, CoreColors};
-// **< RegionName >*********************************************************************************
-
-/// Interfaz común para las regiones lógicas del `
`.
-///
-/// Una [`RegionName`] representa un contenedor lógico identificado por un nombre de región. Su
-/// contenido se obtiene del [`Context`], donde los componentes suelen registrarse usando
-/// implementaciones de métodos como [`Contextual::with_child_in()`].
-///
-/// El contenido de una región viene determinado únicamente por su nombre, no por su tipo. Distintas
-/// implementaciones de [`RegionName`] que devuelvan el mismo nombre comparten el mismo conjunto de
-/// componentes registrados en el [`Context`]. Un *enum* propio que implemente [`RegionName`] está
-/// pensado para **añadir** regiones que PageTop no ofrece (con un nombre propio que no colisione
-/// con los de [`CoreRegions`] o [`ReservedRegions`]).
-///
-/// El tema decide qué regiones mostrar en el ``, normalmente usando una plantilla
-/// ([`TemplateName`]) al renderizar la página ([`Page`]).
-///
-/// Requiere [`AnyInfo`] para que un [`RegionRef`] pueda recuperarse mediante
-/// [`AnyCast::downcast_ref()`] hacia su tipo concreto (por ejemplo, para que un tema distinga en
-/// [`Theme::handle_component()`] qué variante concreta está renderizando el componente [`Region`]).
-///
-/// [`Context`]: crate::core::component::Context
-/// [`Contextual::with_child_in()`]: crate::core::component::Contextual::with_child_in
-/// [`ReservedRegions`]: crate::response::ReservedRegions
-/// [`Page`]: crate::response::Page
-/// [`AnyCast::downcast_ref()`]: crate::core::AnyCast::downcast_ref
-/// [`Theme::handle_component()`]: crate::core::theme::Theme::handle_component
-/// [`Region`]: crate::base::component::layout::Region
-pub trait RegionName: Send + Sync + AnyInfo {
- /// Devuelve el nombre de la región.
- ///
- /// Este nombre es el identificador lógico de la región y se usa como clave en el
- /// [`Context`](crate::core::component::Context) para recuperar y renderizar el contenido
- /// registrado bajo ese nombre. Cualquier implementación de [`RegionName`] que devuelva el mismo
- /// nombre compartirá el mismo conjunto de componentes.
- fn name(&self) -> &'static str;
-
- /// Devuelve un *texto localizado* como etiqueta de accesibilidad asociada a la región.
- ///
- /// En la implementación predeterminada de [`Region`](crate::base::component::layout::Region),
- /// este valor se usa como `aria-label` del contenedor de la región.
- fn label(&self) -> Lc;
-}
-
-/// Referencia estática a una región.
-pub type RegionRef = &'static dyn RegionName;
-
-// **< CoreRegions >********************************************************************************
-
-/// Regiones básicas que PageTop proporciona por defecto.
-///
-/// Comparten sus nombres (`"header"`, `"aside"`, `"content"`, `"footer"`) con otras regiones que
-/// implementen [`RegionName`], por lo que comparten también el contenido registrado bajo esos
-/// nombres. Por defecto, son las regiones usadas por [`Template`].
-///
-/// A estas regiones hay que sumar también las regiones internas reservadas por [`ReservedRegions`]
-/// (`"page-top"` y `"page-bottom"`), que [`Page::render()`] renderiza en cualquier caso.
-///
-/// [`Template`]: crate::base::component::layout::Template
-/// [`ReservedRegions`]: crate::response::ReservedRegions
-/// [`Page::render()`]: crate::response::Page::render
-#[derive(AutoDefault)]
-pub enum CoreRegions {
- /// Región estándar para la **cabecera** del documento, de nombre `"header"`.
- ///
- /// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc.
- Header,
-
- /// Región de **contenido secundario**, de nombre `"aside"`.
- ///
- /// Se renderiza por defecto entre `Header` y `Content`. Un tema podría maquetarla, por ejemplo,
- /// como columna lateral junto a `Content` y emplearla para menús secundarios o cualquier otro
- /// contenido complementario al principal.
- Aside,
-
- /// Región principal de **contenido**, de nombre `"content"`.
- ///
- /// Es la región donde se renderiza el contenido principal del documento. En general será la
- /// región mínima imprescindible para que una página tenga sentido.
- #[default]
- Content,
-
- /// Región estándar para el **pie de página**, de nombre `"footer"`.
- ///
- /// Suele contener información legal, enlaces secundarios, créditos, etc.
- Footer,
-}
-
-impl RegionName for CoreRegions {
- #[inline]
- fn name(&self) -> &'static str {
- match self {
- Self::Header => "header",
- Self::Aside => "aside",
- Self::Content => "content",
- Self::Footer => "footer",
- }
- }
-
- #[inline]
- fn label(&self) -> Lc {
- match self {
- Self::Header => Lc::l("region_header"),
- Self::Aside => Lc::l("region_aside"),
- Self::Content => Lc::l("region_content"),
- Self::Footer => Lc::l("region_footer"),
- }
- }
-}
-
-// **< TemplateName >*******************************************************************************
-
-/// Interfaz común para las plantillas lógicas de una página.
-///
-/// Representa una variante identificada por un nombre. Un tema puede usar este nombre para decidir
-/// la composición del cuerpo de una página ([`Page`](crate::response::Page)), es decir, qué
-/// regiones ([`RegionName`]) renderizar y en qué orden.
-///
-/// Requiere [`AnyInfo`] por el mismo motivo que [`RegionName`], para que un [`TemplateRef`] pueda
-/// recuperarse mediante [`AnyCast::downcast_ref()`](crate::core::AnyCast::downcast_ref) hacia su
-/// tipo concreto (por ejemplo, para que un tema distinga en
-/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) qué variante concreta
-/// está renderizando el componente [`Template`](crate::base::component::layout::Template)).
-pub trait TemplateName: Send + Sync + AnyInfo {
- /// Devuelve el nombre de la plantilla.
- fn name(&self) -> &'static str;
-
- /// Devuelve un *texto localizado* como etiqueta descriptiva de la plantilla.
- fn label(&self) -> Lc;
-}
-
-/// Referencia estática a una plantilla.
-pub type TemplateRef = &'static dyn TemplateName;
-
-// **< CoreTemplates >******************************************************************************
-
-/// Plantillas que PageTop proporciona por defecto.
-#[derive(AutoDefault)]
-pub enum CoreTemplates {
- /// Plantilla predeterminada, de nombre `"standard"`.
- ///
- /// Se emplea cuando no se selecciona ninguna otra plantilla explícitamente.
- #[default]
- Standard,
-
- /// Plantilla para la **interfaz de administración**, de nombre `"admin"`.
- ///
- /// Se utiliza para páginas de administración o paneles de control.
- Admin,
-}
-
-impl TemplateName for CoreTemplates {
- #[inline]
- fn name(&self) -> &'static str {
- match self {
- Self::Standard => "standard",
- Self::Admin => "admin",
- }
- }
-
- #[inline]
- fn label(&self) -> Lc {
- match self {
- Self::Standard => Lc::l("template-standard"),
- Self::Admin => Lc::l("template-admin"),
- }
- }
-}
-
-// **< render_component! >**************************************************************************
-
-/// Sobrescribe el renderizado de componentes en
-/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component).
-///
-/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
-/// coincidencia, devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión
-/// asociada. Si ningún tipo coincide, devuelve `None` para que el sistema continúe con la cadena de
-/// herencia o con el renderizado por defecto del propio componente.
-///
-/// # Ejemplo
-///
-/// ```rust,ignore
-/// fn handle_component(
-/// &self,
-/// component: &dyn Component,
-/// cx: &mut Context,
-/// ) -> Option> {
-/// render_component!(component, {
-/// Button => |btn| { Ok(html! { button.btn.btn-primary { (btn.label()) } }) },
-/// Heading => |h| self.render_heading(h, cx),
-/// })
-/// }
-///
-/// fn render_heading(&self, h: &Heading, cx: &mut Context) -> Result {
-/// Ok(html! { h2.display-4 { (h.text()) } })
-/// }
-/// ```
-#[macro_export]
-macro_rules! render_component {
- ($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
- 'render_component: {
- // Reborrow explícito como referencia compartida para que `downcast_ref` funcione
- // correctamente con `&mut dyn Component` (limitación del compilador con trait objects).
- let __c = &*($component);
- $(
- if let Some($var) = __c.downcast_ref::<$type>() {
- break 'render_component Some($body);
- }
- )*
- None
- }
- };
-}
-
-// **< setup_component! >***************************************************************************
-
-/// Muta un componente dentro de
-/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component).
-///
-/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
-/// coincidencia, ejecuta el bloque asociado y detiene la evaluación. Si ningún tipo coincide, no
-/// hace nada.
-///
-/// Usa acceso mutable al componente mediante [`downcast_mut`](crate::core::AnyCast::downcast_mut),
-/// lo que permite modificar su estado. El tema puede devolver `None` tras la mutación para que otro
-/// nivel de la cadena se encargue del renderizado.
-///
-/// # Ejemplos
-///
-/// Solo mutación: el tema ajusta el componente y delega el renderizado al siguiente nivel:
-///
-/// ```rust,ignore
-/// fn handle_component(
-/// &self,
-/// component: &mut dyn Component,
-/// cx: &mut Context,
-/// ) -> Option> {
-/// setup_component!(component, { Button => |btn| { btn.add_class("btn-primary"); } });
-/// None
-/// }
-/// ```
-///
-/// Mutación y renderizado combinados: el `Button` se muta y se renderiza aquí; el `Heading` se
-/// muta pero continúa la cadena para que otro nivel lo renderice:
-///
-/// ```rust,ignore
-/// fn handle_component(
-/// &self,
-/// component: &mut dyn Component,
-/// cx: &mut Context,
-/// ) -> Option> {
-/// setup_component!(component, {
-/// Button => |btn| { btn.add_class("btn-primary"); },
-/// Heading => |h| { h.add_class("display-4"); },
-/// });
-/// render_component!(component, {
-/// Button => |btn| Ok(html! { button.btn { (btn.label()) } }),
-/// })
-/// }
-/// ```
-#[macro_export]
-macro_rules! setup_component {
- ($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
- 'setup_component: {
- $(
- if let Some($var) = ($component).downcast_mut::<$type>() {
- $body;
- break 'setup_component;
- }
- )*
- }
- };
-}
-
-// **< Definitions >********************************************************************************
+mod layout;
+pub use layout::{CoreRegions, RegionName, RegionRef};
+pub use layout::{CoreTemplates, TemplateName, TemplateRef};
mod definition;
pub use definition::{Theme, ThemeRef};
diff --git a/src/core/theme/color.rs b/src/core/theme/color.rs
new file mode 100644
index 00000000..1a4cb4a2
--- /dev/null
+++ b/src/core/theme/color.rs
@@ -0,0 +1,40 @@
+use crate::AutoDefault;
+
+// **< ColorName >**********************************************************************************
+
+/// Interfaz común para los colores de la paleta de un tema.
+///
+/// PageTop ofrece una implementación predeterminada en [`CoreColors`], aunque probablemente cada
+/// tema proporcionará su propia lista de colores implementando este *trait*.
+pub trait ColorName {
+ /// Devuelve el nombre asociado al color (p. ej. `"primary"`, `"danger"`, etc.). Normalmente se
+ /// usará para generar la clase CSS del componente.
+ fn name(self) -> &'static str;
+}
+
+// **< CoreColors >*********************************************************************************
+
+/// Paleta de colores predeterminada de PageTop.
+#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
+pub enum CoreColors {
+ #[default]
+ Primary,
+ Secondary,
+ Success,
+ Info,
+ Warning,
+ Danger,
+}
+
+impl ColorName for CoreColors {
+ fn name(self) -> &'static str {
+ match self {
+ Self::Primary => "primary",
+ Self::Secondary => "secondary",
+ Self::Success => "success",
+ Self::Info => "info",
+ Self::Warning => "warning",
+ Self::Danger => "danger",
+ }
+ }
+}
diff --git a/src/core/theme/definition.rs b/src/core/theme/definition.rs
index bf2c27f9..10563e35 100644
--- a/src/core/theme/definition.rs
+++ b/src/core/theme/definition.rs
@@ -195,8 +195,9 @@ pub trait Theme: Extension + Send + Sync {
/// - `Some(Ok(markup))` con el HTML generado por el tema para el componente.
/// - `Some(Err(e))` si el tema intentó renderizarlo pero falló.
///
- /// Para renderizar usa [`render_component!`], que devuelve `None` si ningún tipo coincide. Para
- /// mutar sin renderizar usa [`setup_component!`] y devuelve `None` explícitamente:
+ /// Para renderizar usa [`render_component!`](crate::render_component), que devuelve `None` si
+ /// ningún tipo coincide. Para mutar sin renderizar usa
+ /// [`setup_component!`](crate::setup_component) y devuelve `None` explícitamente:
///
/// ```rust,ignore
/// fn handle_component(
@@ -330,3 +331,106 @@ pub trait Theme: Extension + Send + Sync {
/// Referencia estática a un tema.
pub type ThemeRef = &'static dyn Theme;
+
+// **< render_component! >**************************************************************************
+
+/// Sobrescribe el renderizado de componentes en [`Theme::handle_component()`].
+///
+/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
+/// coincidencia, devuelve `Some(Ok(markup))` o `Some(Err(e))` según el resultado de la expresión
+/// asociada. Si ningún tipo coincide, devuelve `None` para que el sistema continúe con la cadena de
+/// herencia o con el renderizado por defecto del propio componente.
+///
+/// # Ejemplo
+///
+/// ```rust,ignore
+/// fn handle_component(
+/// &self,
+/// component: &dyn Component,
+/// cx: &mut Context,
+/// ) -> Option> {
+/// render_component!(component, {
+/// Button => |btn| { Ok(html! { button.btn.btn-primary { (btn.label()) } }) },
+/// Heading => |h| self.render_heading(h, cx),
+/// })
+/// }
+///
+/// fn render_heading(&self, h: &Heading, cx: &mut Context) -> Result {
+/// Ok(html! { h2.display-4 { (h.text()) } })
+/// }
+/// ```
+#[macro_export]
+macro_rules! render_component {
+ ($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
+ 'render_component: {
+ // Reborrow explícito como referencia compartida para que `downcast_ref` funcione
+ // correctamente con `&mut dyn Component` (limitación del compilador con trait objects).
+ let __c = &*($component);
+ $(
+ if let Some($var) = __c.downcast_ref::<$type>() {
+ break 'render_component Some($body);
+ }
+ )*
+ None
+ }
+ };
+}
+
+// **< setup_component! >***************************************************************************
+
+/// Muta un componente dentro de [`Theme::handle_component()`].
+///
+/// Evalúa `$component` contra cada tipo de componente listado en orden. En cuanto encuentra
+/// coincidencia, ejecuta el bloque asociado y detiene la evaluación. Si ningún tipo coincide, no
+/// hace nada.
+///
+/// Usa acceso mutable al componente mediante [`downcast_mut`](crate::core::AnyCast::downcast_mut),
+/// lo que permite modificar su estado. El tema puede devolver `None` tras la mutación para que otro
+/// nivel de la cadena se encargue del renderizado.
+///
+/// # Ejemplos
+///
+/// Solo mutación: el tema ajusta el componente y delega el renderizado al siguiente nivel:
+///
+/// ```rust,ignore
+/// fn handle_component(
+/// &self,
+/// component: &mut dyn Component,
+/// cx: &mut Context,
+/// ) -> Option> {
+/// setup_component!(component, { Button => |btn| { btn.add_class("btn-primary"); } });
+/// None
+/// }
+/// ```
+///
+/// Mutación y renderizado combinados: el `Button` se muta y se renderiza aquí; el `Heading` se
+/// muta pero continúa la cadena para que otro nivel lo renderice:
+///
+/// ```rust,ignore
+/// fn handle_component(
+/// &self,
+/// component: &mut dyn Component,
+/// cx: &mut Context,
+/// ) -> Option> {
+/// setup_component!(component, {
+/// Button => |btn| { btn.add_class("btn-primary"); },
+/// Heading => |h| { h.add_class("display-4"); },
+/// });
+/// render_component!(component, {
+/// Button => |btn| Ok(html! { button.btn { (btn.label()) } }),
+/// })
+/// }
+/// ```
+#[macro_export]
+macro_rules! setup_component {
+ ($component:expr, { $($type:ty => |$var:ident| $body:expr),* $(,)? }) => {
+ 'setup_component: {
+ $(
+ if let Some($var) = ($component).downcast_mut::<$type>() {
+ $body;
+ break 'setup_component;
+ }
+ )*
+ }
+ };
+}
diff --git a/src/core/theme/layout.rs b/src/core/theme/layout.rs
new file mode 100644
index 00000000..8587c300
--- /dev/null
+++ b/src/core/theme/layout.rs
@@ -0,0 +1,172 @@
+use crate::AutoDefault;
+use crate::core::AnyInfo;
+use crate::locale::Lc;
+
+// **< RegionName >*********************************************************************************
+
+/// Interfaz común para las regiones lógicas del ``.
+///
+/// Una [`RegionName`] representa un contenedor lógico identificado por un nombre de región. Su
+/// contenido se obtiene del [`Context`], donde los componentes suelen registrarse usando
+/// implementaciones de métodos como [`Contextual::with_child_in()`].
+///
+/// El contenido de una región viene determinado únicamente por su nombre, no por su tipo. Distintas
+/// implementaciones de [`RegionName`] que devuelvan el mismo nombre comparten el mismo conjunto de
+/// componentes registrados en el [`Context`]. Un *enum* propio que implemente [`RegionName`] está
+/// pensado para **añadir** regiones que PageTop no ofrece (con un nombre propio que no colisione
+/// con los de [`CoreRegions`] o [`ReservedRegions`]).
+///
+/// El tema decide qué regiones mostrar en el ``, normalmente usando una plantilla
+/// ([`TemplateName`]) al renderizar la página ([`Page`]).
+///
+/// Requiere [`AnyInfo`] para que un [`RegionRef`] pueda recuperarse mediante
+/// [`AnyCast::downcast_ref()`] hacia su tipo concreto (por ejemplo, para que un tema distinga en
+/// [`Theme::handle_component()`] qué variante concreta está renderizando el componente [`Region`]).
+///
+/// [`Context`]: crate::core::component::Context
+/// [`Contextual::with_child_in()`]: crate::core::component::Contextual::with_child_in
+/// [`ReservedRegions`]: crate::response::ReservedRegions
+/// [`Page`]: crate::response::Page
+/// [`AnyCast::downcast_ref()`]: crate::core::AnyCast::downcast_ref
+/// [`Theme::handle_component()`]: crate::core::theme::Theme::handle_component
+/// [`Region`]: crate::base::component::layout::Region
+pub trait RegionName: Send + Sync + AnyInfo {
+ /// Devuelve el nombre de la región.
+ ///
+ /// Este nombre es el identificador lógico de la región y se usa como clave en el
+ /// [`Context`](crate::core::component::Context) para recuperar y renderizar el contenido
+ /// registrado bajo ese nombre. Cualquier implementación de [`RegionName`] que devuelva el mismo
+ /// nombre compartirá el mismo conjunto de componentes.
+ fn name(&self) -> &'static str;
+
+ /// Devuelve un *texto localizado* como etiqueta de accesibilidad asociada a la región.
+ ///
+ /// En la implementación predeterminada de [`Region`](crate::base::component::layout::Region),
+ /// este valor se usa como `aria-label` del contenedor de la región.
+ fn label(&self) -> Lc;
+}
+
+/// Referencia estática a una región.
+pub type RegionRef = &'static dyn RegionName;
+
+// **< CoreRegions >********************************************************************************
+
+/// Regiones básicas que PageTop proporciona por defecto.
+///
+/// Comparten sus nombres (`"header"`, `"aside"`, `"content"`, `"footer"`) con otras regiones que
+/// implementen [`RegionName`], por lo que comparten también el contenido registrado bajo esos
+/// nombres. Por defecto, son las regiones usadas por [`Template`].
+///
+/// A estas regiones hay que sumar también las regiones internas reservadas por [`ReservedRegions`]
+/// (`"page-top"` y `"page-bottom"`), que [`Page::render()`] renderiza en cualquier caso.
+///
+/// [`Template`]: crate::base::component::layout::Template
+/// [`ReservedRegions`]: crate::response::ReservedRegions
+/// [`Page::render()`]: crate::response::Page::render
+#[derive(AutoDefault)]
+pub enum CoreRegions {
+ /// Región estándar para la **cabecera** del documento, de nombre `"header"`.
+ ///
+ /// Suele emplearse para mostrar un logotipo, navegación principal, barras superiores, etc.
+ Header,
+
+ /// Región de **contenido secundario**, de nombre `"aside"`.
+ ///
+ /// Se renderiza por defecto entre `Header` y `Content`. Un tema podría maquetarla, por ejemplo,
+ /// como columna lateral junto a `Content` y emplearla para menús secundarios o cualquier otro
+ /// contenido complementario al principal.
+ Aside,
+
+ /// Región principal de **contenido**, de nombre `"content"`.
+ ///
+ /// Es la región donde se renderiza el contenido principal del documento. En general será la
+ /// región mínima imprescindible para que una página tenga sentido.
+ #[default]
+ Content,
+
+ /// Región estándar para el **pie de página**, de nombre `"footer"`.
+ ///
+ /// Suele contener información legal, enlaces secundarios, créditos, etc.
+ Footer,
+}
+
+impl RegionName for CoreRegions {
+ #[inline]
+ fn name(&self) -> &'static str {
+ match self {
+ Self::Header => "header",
+ Self::Aside => "aside",
+ Self::Content => "content",
+ Self::Footer => "footer",
+ }
+ }
+
+ #[inline]
+ fn label(&self) -> Lc {
+ match self {
+ Self::Header => Lc::l("region_header"),
+ Self::Aside => Lc::l("region_aside"),
+ Self::Content => Lc::l("region_content"),
+ Self::Footer => Lc::l("region_footer"),
+ }
+ }
+}
+
+// **< TemplateName >*******************************************************************************
+
+/// Interfaz común para las plantillas lógicas de una página.
+///
+/// Representa una variante identificada por un nombre. Un tema puede usar este nombre para decidir
+/// la composición del cuerpo de una página ([`Page`](crate::response::Page)), es decir, qué
+/// regiones ([`RegionName`]) renderizar y en qué orden.
+///
+/// Requiere [`AnyInfo`] por el mismo motivo que [`RegionName`], para que un [`TemplateRef`] pueda
+/// recuperarse mediante [`AnyCast::downcast_ref()`](crate::core::AnyCast::downcast_ref) hacia su
+/// tipo concreto (por ejemplo, para que un tema distinga en
+/// [`Theme::handle_component()`](crate::core::theme::Theme::handle_component) qué variante concreta
+/// está renderizando el componente [`Template`](crate::base::component::layout::Template)).
+pub trait TemplateName: Send + Sync + AnyInfo {
+ /// Devuelve el nombre de la plantilla.
+ fn name(&self) -> &'static str;
+
+ /// Devuelve un *texto localizado* como etiqueta descriptiva de la plantilla.
+ fn label(&self) -> Lc;
+}
+
+/// Referencia estática a una plantilla.
+pub type TemplateRef = &'static dyn TemplateName;
+
+// **< CoreTemplates >******************************************************************************
+
+/// Plantillas que PageTop proporciona por defecto.
+#[derive(AutoDefault)]
+pub enum CoreTemplates {
+ /// Plantilla predeterminada, de nombre `"standard"`.
+ ///
+ /// Se emplea cuando no se selecciona ninguna otra plantilla explícitamente.
+ #[default]
+ Standard,
+
+ /// Plantilla para la **interfaz de administración**, de nombre `"admin"`.
+ ///
+ /// Se utiliza para páginas de administración o paneles de control.
+ Admin,
+}
+
+impl TemplateName for CoreTemplates {
+ #[inline]
+ fn name(&self) -> &'static str {
+ match self {
+ Self::Standard => "standard",
+ Self::Admin => "admin",
+ }
+ }
+
+ #[inline]
+ fn label(&self) -> Lc {
+ match self {
+ Self::Standard => Lc::l("template-standard"),
+ Self::Admin => Lc::l("template-admin"),
+ }
+ }
+}