♻️ (base): Reorganiza el componente Intro
This commit is contained in:
parent
404739a3fd
commit
16eec316c4
12 changed files with 439 additions and 339 deletions
|
|
@ -47,8 +47,9 @@ pub mod image;
|
|||
#[doc(inline)]
|
||||
pub use image::Image;
|
||||
|
||||
mod intro;
|
||||
pub use intro::{Intro, IntroOpening};
|
||||
pub mod intro;
|
||||
#[doc(inline)]
|
||||
pub use intro::Intro;
|
||||
|
||||
mod messages;
|
||||
pub use messages::Messages;
|
||||
|
|
|
|||
|
|
@ -1,292 +1,7 @@
|
|||
use crate::prelude::*;
|
||||
//! Definiciones para crear introducciones ([`Intro`]).
|
||||
|
||||
/// Tipo de apertura que se mostrará en la introducción del componente [`Intro`].
|
||||
///
|
||||
/// Permite elegir entre una apertura con textos predefinidos sobre PageTop (como hace la página de
|
||||
/// bienvenida [`Welcome`](crate::base::extension::Welcome)) o una introducción completamente
|
||||
/// personalizada.
|
||||
#[derive(AutoDefault, Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum IntroOpening {
|
||||
/// Modo por defecto. Muestra una introducción estándar de PageTop e incluye automáticamente
|
||||
/// *badges* con información de la última versión liberada, fecha del último lanzamiento y
|
||||
/// licencia de uso.
|
||||
#[default]
|
||||
PageTop,
|
||||
/// Modo totalmente personalizado. No añade *badges* ni textos predefinidos. Usa la imagen de
|
||||
/// PageTop pero el contenido lo define el propio desarrollador.
|
||||
Custom,
|
||||
}
|
||||
mod props;
|
||||
pub use props::{Kind, Width};
|
||||
|
||||
/// Componente para divulgar PageTop (como hace [`Welcome`](crate::base::extension::Welcome)), o
|
||||
/// mostrar presentaciones.
|
||||
///
|
||||
/// Usa la imagen de PageTop para mostrar:
|
||||
///
|
||||
/// - Una **figura decorativa** (que incluye la *mascota* de PageTop) antecediendo al contenido.
|
||||
/// - Una vista destacada del **título** de la página con un **eslogan** de presentación.
|
||||
/// - Un **botón opcional** de llamada a la acción con texto y enlace configurables.
|
||||
/// - Un **área para la presentación de contenidos**, con *badges* informativos de PageTop (si se
|
||||
/// opta por [`IntroOpening::PageTop`]) y bloques ([`Block`](crate::base::component::Block)) de
|
||||
/// contenido libre. Los párrafos que tengan la clase `.intro-text-lead` se mostrarán con una
|
||||
/// tipografía ampliada, ideal para presentaciones breves e impactantes.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// **Intro mínima por defecto**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default();
|
||||
/// ```
|
||||
///
|
||||
/// **Título, eslogan y botón personalizados**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default()
|
||||
/// .with_title(Lc::l("intro_custom_title"))
|
||||
/// .with_slogan(Lc::l("intro_custom_slogan"))
|
||||
/// .with_button(Some((
|
||||
/// Lc::l("intro_learn_more"),
|
||||
/// "/learn-more".into()
|
||||
/// )));
|
||||
/// ```
|
||||
///
|
||||
/// **Sin botón y en modo *Custom* (sin *badges* predefinidos)**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default()
|
||||
/// .with_button(None::<(Lc, Route)>)
|
||||
/// .with_opening(IntroOpening::Custom);
|
||||
/// ```
|
||||
///
|
||||
/// **Añadir contenidos hijo**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default()
|
||||
/// .with_child(
|
||||
/// Block::new()
|
||||
/// .with_title(Lc::l("intro_custom_block_title"))
|
||||
/// .with_child(Html::with(move |cx| {
|
||||
/// html! {
|
||||
/// p class="intro-text-lead" {
|
||||
/// (Lc::l("intro_custom_paragraph_1").using(cx))
|
||||
/// }
|
||||
/// p { (Lc::l("intro_custom_paragraph_2").using(cx)) }
|
||||
/// }
|
||||
/// })),
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Getters)]
|
||||
pub struct Intro {
|
||||
/// Devuelve el título de entrada.
|
||||
title: Lc,
|
||||
/// Devuelve el eslogan de la entrada.
|
||||
slogan: Lc,
|
||||
/// Devuelve el botón de llamada a la acción, si existe.
|
||||
button: Option<(Lc, Route)>,
|
||||
/// Devuelve el modo de apertura configurado.
|
||||
opening: IntroOpening,
|
||||
/// Devuelve la lista de componentes hijo de la intro.
|
||||
children: Children,
|
||||
}
|
||||
|
||||
impl Default for Intro {
|
||||
fn default() -> Self {
|
||||
const BUTTON_LINK: &str = "https://pagetop.cillero.es";
|
||||
|
||||
Intro {
|
||||
title: Lc::l("intro_default_title"),
|
||||
slogan: Lc::l("intro_default_slogan").with_arg("app", &global::SETTINGS.app.name),
|
||||
button: Some((Lc::l("intro_default_button"), BUTTON_LINK.into())),
|
||||
opening: IntroOpening::default(),
|
||||
children: Children::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Intro {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
cx.alter_assets(StyleSheet::from("/pagetop/css/intro.css").with_version(PAGETOP_VERSION));
|
||||
if *self.opening() == IntroOpening::PageTop {
|
||||
cx.alter_assets(JavaScript::on_load_async("intro-js", |cx|
|
||||
util::indoc!(r#"
|
||||
try {
|
||||
const resp = await fetch("https://crates.io/api/v1/crates/pagetop");
|
||||
const data = await resp.json();
|
||||
const date = new Date(data.versions[0].created_at);
|
||||
const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" });
|
||||
document.getElementById("intro-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch release date from crates.io:", e);
|
||||
} finally {
|
||||
document.getElementById("intro-badges").style.visibility = "visible";
|
||||
}
|
||||
"#)
|
||||
.replace("LANGID", cx.langid().to_string().as_str())
|
||||
.replace("LABEL", Lc::l("intro_release_label").using(cx).as_str())
|
||||
));
|
||||
}
|
||||
|
||||
Ok(html! {
|
||||
div class="intro" {
|
||||
div class="intro-header" {
|
||||
section class="intro-header-body" {
|
||||
h1 class="intro-header-title" {
|
||||
span { (self.title().using(cx)) }
|
||||
(self.slogan().using(cx))
|
||||
}
|
||||
}
|
||||
aside class="intro-header-img" aria-hidden="true" {
|
||||
div class="intro-header-mascot" {
|
||||
(PageTopSvg::Color.markup())
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-content" {
|
||||
section class="intro-content-body" {
|
||||
div class="intro-text" {
|
||||
@if let Some((txt, lnk)) = self.button() {
|
||||
div class="intro-button" {
|
||||
a
|
||||
class="intro-button-link"
|
||||
href=(lnk.resolve(cx))
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{
|
||||
span {} span {} span {}
|
||||
div class="intro-button-text" {
|
||||
(txt.using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-text-body" {
|
||||
@if *self.opening() == IntroOpening::PageTop {
|
||||
p class="intro-text-lead" {
|
||||
(Lc::l("intro_text1").using(cx))
|
||||
}
|
||||
div id="intro-badges" {
|
||||
img
|
||||
src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge"
|
||||
alt=[Lc::l("intro_pagetop_label").lookup(cx)] {} (" ")
|
||||
img
|
||||
id="intro-release"
|
||||
alt=[Lc::l("intro_release_label").lookup(cx)] {} (" ")
|
||||
img
|
||||
src=(format!(
|
||||
"https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label={}&style=for-the-badge",
|
||||
Lc::l("intro_license_label").lookup(cx).unwrap_or_default()
|
||||
))
|
||||
alt=[Lc::l("intro_license_label").lookup(cx)] {}
|
||||
}
|
||||
p class="intro-text-lead" {
|
||||
(Lc::l("intro_text2").using(cx))
|
||||
}
|
||||
}
|
||||
(self.children().render(cx).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-footer" {
|
||||
section class="intro-footer-body" {
|
||||
div class="intro-footer-logo" {
|
||||
(PageTopSvg::LineLight.markup())
|
||||
}
|
||||
div class="intro-footer-links" {
|
||||
a href="https://crates.io/crates/pagetop" target="_blank" rel="noopener noreferrer" { ("Crates.io") }
|
||||
a href="https://docs.rs/pagetop" target="_blank" rel="noopener noreferrer" { ("Docs.rs") }
|
||||
a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noopener noreferrer" { (Lc::l("intro_code").using(cx)) }
|
||||
em { (Lc::l("intro_have_fun").using(cx)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Intro {
|
||||
// **< Intro BUILDER >**************************************************************************
|
||||
|
||||
/// Establece el título de entrada.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_title(Lc::n("Intro title"));
|
||||
/// ```
|
||||
pub fn with_title(mut self, title: Lc) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el eslogan de entrada (línea secundaria del título).
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_slogan(Lc::n("A short slogan"));
|
||||
/// ```
|
||||
pub fn with_slogan(mut self, slogan: Lc) -> Self {
|
||||
self.slogan = slogan;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configura el botón opcional de llamada a la acción.
|
||||
///
|
||||
/// - Usa `Some((texto, ruta))` para mostrarlo, donde [`Route`] resuelve la ruta o URL final al
|
||||
/// pulsar el botón según el contexto de renderizado.
|
||||
/// - Usa `None` para ocultarlo.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// // Define un botón con texto y una ruta interna (preserva `lang` si corresponde).
|
||||
/// let intro = Intro::default().with_button(Some((Lc::n("Start"), "/start".into())));
|
||||
/// // Descarta el botón de la intro.
|
||||
/// let intro_no_button = Intro::default().with_button(None);
|
||||
/// ```
|
||||
pub fn with_button(mut self, button: impl Into<Option<(Lc, Route)>>) -> Self {
|
||||
self.button = button.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Selecciona el tipo de apertura: [`IntroOpening::PageTop`] (por defecto) o
|
||||
/// [`IntroOpening::Custom`].
|
||||
///
|
||||
/// - `PageTop`: añade *badges* automáticos y una presentación de lo que es PageTop.
|
||||
/// - `Custom`: introducción en blanco para añadir cualquier contenido.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_opening(IntroOpening::Custom);
|
||||
/// ```
|
||||
pub fn with_opening(mut self, opening: IntroOpening) -> Self {
|
||||
self.opening = opening;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente a la intro o modifica la lista de componentes (`children`) con una
|
||||
/// operación [`ChildOp`].
|
||||
///
|
||||
/// Si se añade un bloque ([`Block`]) se aplicarán estilos específicos para destacarlo.
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
self.children.alter_child(op.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
mod component;
|
||||
pub use component::Intro;
|
||||
|
|
|
|||
320
src/base/component/intro/component.rs
Normal file
320
src/base/component/intro/component.rs
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
/// Componente para divulgar PageTop (como hace [`Welcome`](crate::base::extension::Welcome)), o
|
||||
/// mostrar presentaciones.
|
||||
///
|
||||
/// Usa la imagen de PageTop para mostrar:
|
||||
///
|
||||
/// - Una **figura decorativa** (que incluye la *mascota* de PageTop) antecediendo al contenido.
|
||||
/// - Una vista destacada del **título** de la página con un **eslogan** de presentación.
|
||||
/// - Un **botón opcional** de llamada a la acción con texto y enlace configurables.
|
||||
/// - Un **área para la presentación de contenidos**, con *badges* informativos de PageTop (si se
|
||||
/// opta por [`intro::Kind::PageTop`]) y bloques ([`Block`](crate::base::component::Block)) de
|
||||
/// contenido libre. Los párrafos que tengan la clase `.intro-text-lead` se mostrarán con una
|
||||
/// tipografía ampliada, ideal para presentaciones breves e impactantes.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// **Intro mínima por defecto**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default();
|
||||
/// ```
|
||||
///
|
||||
/// **Título, eslogan y botón personalizados**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default()
|
||||
/// .with_title(Lc::l("intro_custom_title"))
|
||||
/// .with_slogan(Lc::l("intro_custom_slogan"))
|
||||
/// .with_button(Some((
|
||||
/// Lc::l("intro_learn_more"),
|
||||
/// "/learn-more".into()
|
||||
/// )));
|
||||
/// ```
|
||||
///
|
||||
/// **Modo *Custom* con título y botón propios**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::custom()
|
||||
/// .with_title(Lc::l("intro_custom_title"))
|
||||
/// .with_button(Some((Lc::l("intro_learn_more"), "/learn-more".into())));
|
||||
/// ```
|
||||
///
|
||||
/// **Área de contenidos al ancho máximo**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_width(intro::Width::Full);
|
||||
/// ```
|
||||
///
|
||||
/// **Añadir contenidos hijo**
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default()
|
||||
/// .with_child(
|
||||
/// Block::new()
|
||||
/// .with_title(Lc::l("intro_custom_block_title"))
|
||||
/// .with_child(Html::with(move |cx| {
|
||||
/// html! {
|
||||
/// p class="intro-text-lead" {
|
||||
/// (Lc::l("intro_custom_paragraph_1").using(cx))
|
||||
/// }
|
||||
/// p { (Lc::l("intro_custom_paragraph_2").using(cx)) }
|
||||
/// }
|
||||
/// })),
|
||||
/// );
|
||||
/// ```
|
||||
#[derive(Clone, Debug, Getters)]
|
||||
pub struct Intro {
|
||||
/// Devuelve el título de entrada.
|
||||
title: Lc,
|
||||
/// Devuelve el eslogan de la entrada.
|
||||
slogan: Lc,
|
||||
/// Devuelve el botón de llamada a la acción, si existe.
|
||||
button: Option<(Lc, Route)>,
|
||||
/// Devuelve el tipo de introducción, fijado al construirla.
|
||||
kind: intro::Kind,
|
||||
/// Devuelve el ancho máximo del área de contenidos.
|
||||
width: intro::Width,
|
||||
/// Devuelve la lista de componentes hijo de la intro.
|
||||
children: Children,
|
||||
}
|
||||
|
||||
impl Default for Intro {
|
||||
fn default() -> Self {
|
||||
const BUTTON_LINK: &str = "https://pagetop.cillero.es";
|
||||
|
||||
Intro {
|
||||
title: Lc::l("intro_default_title"),
|
||||
slogan: Lc::l("intro_default_slogan").with_arg("app", &global::SETTINGS.app.name),
|
||||
button: Some((Lc::l("intro_default_button"), BUTTON_LINK.into())),
|
||||
kind: intro::Kind::default(),
|
||||
width: intro::Width::default(),
|
||||
children: Children::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Intro {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
cx.alter_assets(StyleSheet::from("/pagetop/css/intro.css").with_version(PAGETOP_VERSION));
|
||||
if *self.kind() == intro::Kind::PageTop {
|
||||
cx.alter_assets(JavaScript::on_load_async("intro-js", |cx|
|
||||
util::indoc!(r#"
|
||||
try {
|
||||
const resp = await fetch("https://crates.io/api/v1/crates/pagetop");
|
||||
const data = await resp.json();
|
||||
const date = new Date(data.versions[0].created_at);
|
||||
const formatted = date.toLocaleDateString("LANGID", { year: "numeric", month: "2-digit", day: "2-digit" });
|
||||
document.getElementById("intro-release").src = `https://img.shields.io/badge/Release%20date-${encodeURIComponent(formatted)}-blue?label=LABEL&style=for-the-badge`;
|
||||
} catch (e) {
|
||||
console.error("Failed to fetch release date from crates.io:", e);
|
||||
} finally {
|
||||
document.getElementById("intro-badges").style.visibility = "visible";
|
||||
}
|
||||
"#)
|
||||
.replace("LANGID", cx.langid().to_string().as_str())
|
||||
.replace("LABEL", Lc::l("intro_release_label").using(cx).as_str())
|
||||
));
|
||||
}
|
||||
|
||||
let title = self.title().using(cx);
|
||||
let slogan = self.slogan().using(cx);
|
||||
|
||||
Ok(html! {
|
||||
div class="intro" {
|
||||
div class="intro-header" {
|
||||
@if !title.is_empty() || !slogan.is_empty() {
|
||||
section class="intro-header-body" {
|
||||
h1 class="intro-header-title" {
|
||||
@if !title.is_empty() {
|
||||
span { (title) }
|
||||
}
|
||||
(slogan)
|
||||
}
|
||||
}
|
||||
}
|
||||
aside class="intro-header-img" aria-hidden="true" {
|
||||
div class="intro-header-mascot" {
|
||||
(PageTopSvg::Color.markup())
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-content" {
|
||||
section class="intro-content-body" {
|
||||
div class=(self.width().classes()) {
|
||||
@if let Some((txt, lnk)) = self.button() {
|
||||
div class="intro-button" {
|
||||
a
|
||||
class="intro-button-link"
|
||||
href=(lnk.resolve(cx))
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
{
|
||||
span {} span {} span {}
|
||||
div class="intro-button-text" {
|
||||
(txt.using(cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-text-body" {
|
||||
@if *self.kind() == intro::Kind::PageTop {
|
||||
p class="intro-text-lead" {
|
||||
(Lc::l("intro_text1").using(cx))
|
||||
}
|
||||
div id="intro-badges" {
|
||||
img
|
||||
src="https://img.shields.io/crates/v/pagetop.svg?label=PageTop&style=for-the-badge"
|
||||
alt=[Lc::l("intro_pagetop_label").lookup(cx)] {} (" ")
|
||||
img
|
||||
id="intro-release"
|
||||
alt=[Lc::l("intro_release_label").lookup(cx)] {} (" ")
|
||||
img
|
||||
src=(util::join!(
|
||||
"https://img.shields.io/badge/license-MIT%2FApache-blue.svg?label=",
|
||||
Lc::l("intro_license_label").lookup(cx).unwrap_or_default(),
|
||||
"&style=for-the-badge"
|
||||
))
|
||||
alt=[Lc::l("intro_license_label").lookup(cx)] {}
|
||||
}
|
||||
p class="intro-text-lead" {
|
||||
(Lc::l("intro_text2").using(cx))
|
||||
}
|
||||
}
|
||||
(self.children().render(cx).await)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
div class="intro-footer" {
|
||||
section class="intro-footer-body" {
|
||||
div class="intro-footer-logo" {
|
||||
(PageTopSvg::LineLight.markup())
|
||||
}
|
||||
div class="intro-footer-links" {
|
||||
a href="https://crates.io/crates/pagetop" target="_blank" rel="noopener noreferrer" { ("Crates.io") }
|
||||
a href="https://docs.rs/pagetop" target="_blank" rel="noopener noreferrer" { ("Docs.rs") }
|
||||
a href="https://git.cillero.es/manuelcillero/pagetop" target="_blank" rel="noopener noreferrer" { (Lc::l("intro_code").using(cx)) }
|
||||
em { (Lc::l("intro_have_fun").using(cx)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Intro {
|
||||
// **< Intro BUILDER >**************************************************************************
|
||||
|
||||
/// Crea una introducción de tipo [`intro::Kind::Custom`].
|
||||
///
|
||||
/// Parte de una introducción sin el contenido de presentación de PageTop (párrafos y *badges*),
|
||||
/// sin título, sin eslogan y sin botón de llamada a la acción. Todo ello puede añadirse con
|
||||
/// [`with_title()`], [`with_slogan()`] y [`with_button()`].
|
||||
///
|
||||
/// El tipo se fija al construirla y no se puede cambiar después; para la introducción estándar
|
||||
/// de PageTop (tipo [`intro::Kind::PageTop`]) se usa [`Intro::default()`] o [`Intro::new()`].
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::custom().with_title(Lc::n("Intro title"));
|
||||
/// ```
|
||||
///
|
||||
/// [`with_title()`]: Self::with_title
|
||||
/// [`with_slogan()`]: Self::with_slogan
|
||||
/// [`with_button()`]: Self::with_button
|
||||
pub fn custom() -> Self {
|
||||
Self {
|
||||
kind: intro::Kind::Custom,
|
||||
title: Lc::none(),
|
||||
slogan: Lc::none(),
|
||||
button: None,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Establece el título de entrada.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_title(Lc::n("Intro title"));
|
||||
/// ```
|
||||
pub fn with_title(mut self, title: Lc) -> Self {
|
||||
self.title = title;
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el eslogan de entrada (línea secundaria del título).
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_slogan(Lc::n("A short slogan"));
|
||||
/// ```
|
||||
pub fn with_slogan(mut self, slogan: Lc) -> Self {
|
||||
self.slogan = slogan;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configura el botón opcional de llamada a la acción.
|
||||
///
|
||||
/// - Usa `Some((texto, ruta))` para mostrarlo, donde [`Route`] resuelve la ruta o URL final al
|
||||
/// pulsar el botón según el contexto de renderizado.
|
||||
/// - Usa `None` para ocultarlo.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// // Define un botón con texto y una ruta interna (preserva `lang` si corresponde).
|
||||
/// let intro = Intro::default().with_button(Some((Lc::n("Start"), "/start".into())));
|
||||
/// // Descarta el botón de la intro.
|
||||
/// let intro_no_button = Intro::default().with_button(None);
|
||||
/// ```
|
||||
pub fn with_button(mut self, button: impl Into<Option<(Lc, Route)>>) -> Self {
|
||||
self.button = button.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el ancho máximo del área de contenidos.
|
||||
///
|
||||
/// Ver [`intro::Width`] para las variantes disponibles.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
/// let intro = Intro::default().with_width(intro::Width::Wide);
|
||||
/// ```
|
||||
pub fn with_width(mut self, width: intro::Width) -> Self {
|
||||
self.width = width;
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente a la intro o modifica la lista de componentes (`children`) con una
|
||||
/// operación [`ChildOp`].
|
||||
///
|
||||
/// Si se añade un bloque ([`Block`]) se aplicarán estilos específicos para destacarlo.
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
self.children.alter_child(op.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
60
src/base/component/intro/props.rs
Normal file
60
src/base/component/intro/props.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use crate::prelude::*;
|
||||
|
||||
// **< Kind >***************************************************************************************
|
||||
|
||||
/// Tipo de introducción de un componente [`Intro`](super::Intro).
|
||||
///
|
||||
/// Distingue entre una presentación estándar sobre PageTop (la que usa, por ejemplo, la página de
|
||||
/// bienvenida [`Welcome`]) y una introducción completamente personalizada.
|
||||
///
|
||||
/// Se fija al construir la introducción, con [`Intro::default()`] o [`Intro::new()`] para
|
||||
/// [`Kind::PageTop`], o con [`Intro::custom()`] para [`Kind::Custom`], y se consulta con
|
||||
/// [`Intro::kind()`].
|
||||
///
|
||||
/// [`Welcome`]: crate::base::extension::Welcome
|
||||
/// [`Intro::default()`]: super::Intro::default
|
||||
/// [`Intro::new()`]: super::Intro::new
|
||||
/// [`Intro::custom()`]: super::Intro::custom
|
||||
/// [`Intro::kind()`]: super::Intro::kind
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Kind {
|
||||
/// Presentación predeterminada. Muestra una introducción estándar de PageTop e incluye
|
||||
/// automáticamente *badges* con información sobre la última versión liberada, fecha del último
|
||||
/// lanzamiento y licencia de uso.
|
||||
#[default]
|
||||
PageTop,
|
||||
/// Modo personalizado. Usa la imagen de PageTop pero sin contenido de ningún tipo.
|
||||
Custom,
|
||||
}
|
||||
|
||||
// **< Width >**************************************************************************************
|
||||
|
||||
/// Ancho máximo para el área de contenidos de un componente [`Intro`](super::Intro).
|
||||
///
|
||||
/// Sólo afecta a partir del punto de corte [`Breakpoint::Lg`], donde el área de contenidos se
|
||||
/// muestra como un panel centrado. Por debajo ocupa siempre todo el ancho disponible. En pantallas
|
||||
/// que no alcanzan el ancho máximo elegido, el panel se reduce para dejar un margen lateral.
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum Width {
|
||||
/// Ancho estrecho.
|
||||
Narrow,
|
||||
/// Ancho por defecto.
|
||||
#[default]
|
||||
Normal,
|
||||
/// Ancho amplio.
|
||||
Wide,
|
||||
/// Ancho más amplio de los disponibles.
|
||||
Full,
|
||||
}
|
||||
|
||||
impl Width {
|
||||
// Clases CSS del área de contenidos: la base más la modificadora, si el ancho no es el normal.
|
||||
pub(super) fn classes(self) -> &'static str {
|
||||
match self {
|
||||
Width::Narrow => "intro-text intro-text-narrow",
|
||||
Width::Normal => "intro-text",
|
||||
Width::Wide => "intro-text intro-text-wide",
|
||||
Width::Full => "intro-text intro-text-full",
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue