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"), + } + } +}