♻️ (base): Reorganiza el componente Intro
This commit is contained in:
parent
404739a3fd
commit
16eec316c4
12 changed files with 439 additions and 339 deletions
|
|
@ -22,6 +22,10 @@ body {
|
||||||
overflow-x: clip;
|
overflow-x: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Media queries: 768px and 992px match the default `Md` and `Lg` breakpoints of the core.
|
||||||
|
*/
|
||||||
|
|
||||||
.intro {
|
.intro {
|
||||||
position: relative;
|
position: relative;
|
||||||
color: var(--intro-color);
|
color: var(--intro-color);
|
||||||
|
|
@ -44,20 +48,6 @@ body {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.intro a {
|
|
||||||
color: currentColor;
|
|
||||||
text-decoration: underline;
|
|
||||||
transition: font-size 0.2s, text-decoration-color 0.2s;
|
|
||||||
}
|
|
||||||
.intro a:focus-visible {
|
|
||||||
outline: var(--intro-focus-outline);
|
|
||||||
outline-offset: var(--intro-focus-outline-offset);
|
|
||||||
}
|
|
||||||
.intro a:hover,
|
|
||||||
.intro a:hover:visited {
|
|
||||||
text-decoration-color: var(--intro-color-link);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Intro Header
|
* Intro Header
|
||||||
*/
|
*/
|
||||||
|
|
@ -117,7 +107,7 @@ body {
|
||||||
width: 280px;
|
width: 280px;
|
||||||
height: 280px;
|
height: 280px;
|
||||||
}
|
}
|
||||||
@media (min-width: 64rem) {
|
@media (width >= 992px) {
|
||||||
.intro-header {
|
.intro-header {
|
||||||
background-image: var(--intro-bg-img);
|
background-image: var(--intro-bg-img);
|
||||||
background-image: var(--intro-bg-img-set);
|
background-image: var(--intro-bg-img-set);
|
||||||
|
|
@ -166,12 +156,12 @@ body {
|
||||||
.intro-content-body:after {
|
.intro-content-body:after {
|
||||||
bottom: -1rem;
|
bottom: -1rem;
|
||||||
}
|
}
|
||||||
@media (width <= 48rem) {
|
@media (width < 768px) {
|
||||||
.intro-content-body {
|
.intro-content-body {
|
||||||
margin-top: -9.8rem;
|
margin-top: -9.8rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (width >= 64rem) {
|
@media (width >= 992px) {
|
||||||
.intro-content {
|
.intro-content {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +198,10 @@ body {
|
||||||
min-height: 7.6875rem;
|
min-height: 7.6875rem;
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
.intro-button-link:focus-visible {
|
||||||
|
outline: var(--intro-focus-outline);
|
||||||
|
outline-offset: var(--intro-focus-outline-offset);
|
||||||
|
}
|
||||||
.intro-button-link::before {
|
.intro-button-link::before {
|
||||||
content: '';
|
content: '';
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
@ -310,7 +304,7 @@ body {
|
||||||
.intro-button-link:hover span {
|
.intro-button-link:hover span {
|
||||||
animation-play-state: paused;
|
animation-play-state: paused;
|
||||||
}
|
}
|
||||||
@media (width <= 48rem) {
|
@media (width < 768px) {
|
||||||
.intro-header {
|
.intro-header {
|
||||||
padding-bottom: 9rem;;
|
padding-bottom: 9rem;;
|
||||||
}
|
}
|
||||||
|
|
@ -324,7 +318,7 @@ body {
|
||||||
padding-top: .5rem;
|
padding-top: .5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (width > 48rem) {
|
@media (width >= 768px) {
|
||||||
.intro-button {
|
.intro-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
|
|
@ -361,12 +355,12 @@ body {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
margin: 0 0 1.5rem;
|
margin: 0 0 1.5rem;
|
||||||
}
|
}
|
||||||
@media (width > 48rem) {
|
@media (width >= 768px) {
|
||||||
.intro-button + .intro-text-body {
|
.intro-button + .intro-text-body {
|
||||||
padding-top: 7rem;
|
padding-top: 7rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (width >= 64rem) {
|
@media (width >= 992px) {
|
||||||
.intro-header {
|
.intro-header {
|
||||||
padding-bottom: 9rem;;
|
padding-bottom: 9rem;;
|
||||||
}
|
}
|
||||||
|
|
@ -376,9 +370,18 @@ body {
|
||||||
}
|
}
|
||||||
.intro-text {
|
.intro-text {
|
||||||
box-shadow: var(--intro-shadow);
|
box-shadow: var(--intro-shadow);
|
||||||
max-width: 60rem;
|
max-width: min(60rem, 100vw - 4rem);
|
||||||
margin: 0 auto 6rem;
|
margin: 0 auto 6rem;
|
||||||
}
|
}
|
||||||
|
.intro-text-narrow {
|
||||||
|
max-width: min(50rem, 100vw - 4rem);
|
||||||
|
}
|
||||||
|
.intro-text-wide {
|
||||||
|
max-width: min(67rem, 100vw - 4rem);
|
||||||
|
}
|
||||||
|
.intro-text-full {
|
||||||
|
max-width: min(74rem, 100vw - 4rem);
|
||||||
|
}
|
||||||
.intro-text-body {
|
.intro-text-body {
|
||||||
padding-left: 4.5rem;
|
padding-left: 4.5rem;
|
||||||
padding-right: 4.5rem;
|
padding-right: 4.5rem;
|
||||||
|
|
@ -479,6 +482,20 @@ body {
|
||||||
.intro-footer-body a:visited {
|
.intro-footer-body a:visited {
|
||||||
color: var(--intro-color-gray);
|
color: var(--intro-color-gray);
|
||||||
}
|
}
|
||||||
|
/* The footer has its own dark surface, so its links don't inherit the theme's link colors. */
|
||||||
|
.intro-footer-links a {
|
||||||
|
color: currentColor;
|
||||||
|
text-decoration: underline;
|
||||||
|
transition: font-size 0.2s, text-decoration-color 0.2s;
|
||||||
|
}
|
||||||
|
.intro-footer-links a:focus-visible {
|
||||||
|
outline: var(--intro-focus-outline);
|
||||||
|
outline-offset: var(--intro-focus-outline-offset);
|
||||||
|
}
|
||||||
|
.intro-footer-links a:hover,
|
||||||
|
.intro-footer-links a:hover:visited {
|
||||||
|
text-decoration-color: var(--intro-color-link);
|
||||||
|
}
|
||||||
.intro-footer-logo,
|
.intro-footer-logo,
|
||||||
.intro-footer-links {
|
.intro-footer-links {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -496,12 +513,12 @@ body {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
@media (width <= 48rem) {
|
@media (width < 768px) {
|
||||||
.intro-footer-logo {
|
.intro-footer-logo {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@media (width <= 64rem) {
|
@media (width < 992px) {
|
||||||
.intro-footer-body {
|
.intro-footer-body {
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,9 @@ impl Extension for FormControls {
|
||||||
async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
async fn form_controls(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
Page::new(request)
|
Page::new(request)
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::t("title", &LOC))
|
.with_title(Lc::t("title", &LOC))
|
||||||
.with_slogan(Lc::t("slogan", &LOC))
|
.with_slogan(Lc::t("slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
// Bloque 1: casillas, interruptores y botones de opción.
|
// Bloque 1: casillas, interruptores y botones de opción.
|
||||||
.with_child(
|
.with_child(
|
||||||
Block::new()
|
Block::new()
|
||||||
|
|
|
||||||
|
|
@ -14,11 +14,9 @@ impl Extension for IntroColors {
|
||||||
async fn intro_colors(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
async fn intro_colors(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
Page::new(request)
|
Page::new(request)
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::n("PageTop"))
|
.with_title(Lc::n("PageTop"))
|
||||||
.with_slogan(Lc::t("colors_slogan", &LOC))
|
.with_slogan(Lc::t("colors_slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
.with_child(
|
.with_child(
|
||||||
Block::new()
|
Block::new()
|
||||||
.with_title(Lc::t("colors_block", &LOC).with_arg("n", "1"))
|
.with_title(Lc::t("colors_block", &LOC).with_arg("n", "1"))
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,9 @@ async fn intro_flex(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_assets(demo_box_styles())
|
.with_assets(demo_box_styles())
|
||||||
.with_assets(demo_row_styles())
|
.with_assets(demo_row_styles())
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::n("PageTop"))
|
.with_title(Lc::n("PageTop"))
|
||||||
.with_slogan(Lc::t("flex_slogan", &LOC))
|
.with_slogan(Lc::t("flex_slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
.with_child(direction_block())
|
.with_child(direction_block())
|
||||||
.with_child(justify_block())
|
.with_child(justify_block())
|
||||||
.with_child(align_block())
|
.with_child(align_block())
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,9 @@ async fn intro_grid(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_assets(demo_box_styles())
|
.with_assets(demo_box_styles())
|
||||||
.with_assets(demo_row_styles())
|
.with_assets(demo_row_styles())
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::n("PageTop"))
|
.with_title(Lc::n("PageTop"))
|
||||||
.with_slogan(Lc::t("grid_slogan", &LOC))
|
.with_slogan(Lc::t("grid_slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
.with_child(columns_block())
|
.with_child(columns_block())
|
||||||
.with_child(rows_gap_block())
|
.with_child(rows_gap_block())
|
||||||
.with_child(placement_block())
|
.with_child(placement_block())
|
||||||
|
|
@ -472,7 +470,8 @@ fn layout_block() -> Block {
|
||||||
// **< HELPERS >************************************************************************************
|
// **< HELPERS >************************************************************************************
|
||||||
|
|
||||||
// Aspecto fijo de las cajas de muestra: esquinas rectas y borde claro, a diferencia de las cajas
|
// Aspecto fijo de las cajas de muestra: esquinas rectas y borde claro, a diferencia de las cajas
|
||||||
// redondeadas de `intro-flex.rs`, para distinguir a simple vista los ejemplos de Grid de los de Flex.
|
// redondeadas de `intro-flex.rs`, para distinguir a simple vista los ejemplos de Grid de los de
|
||||||
|
// Flex.
|
||||||
fn demo_box_styles() -> AssetsOp {
|
fn demo_box_styles() -> AssetsOp {
|
||||||
AssetsOp::add_responsive_styles(
|
AssetsOp::add_responsive_styles(
|
||||||
None,
|
None,
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,9 @@ async fn intro_responsive(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_assets(demo_row_styles())
|
.with_assets(demo_row_styles())
|
||||||
.with_assets(demo_code_styles())
|
.with_assets(demo_code_styles())
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::n("PageTop"))
|
.with_title(Lc::n("PageTop"))
|
||||||
.with_slogan(Lc::t("responsive_slogan", &LOC))
|
.with_slogan(Lc::t("responsive_slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
.with_child(Html::with(|cx| {
|
.with_child(Html::with(|cx| {
|
||||||
html! {
|
html! {
|
||||||
p class="intro-text-lead" {
|
p class="intro-text-lead" {
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,9 @@ async fn intro_spacing(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||||
.with_assets(demo_row_styles())
|
.with_assets(demo_row_styles())
|
||||||
.with_assets(demo_code_styles())
|
.with_assets(demo_code_styles())
|
||||||
.with_child(
|
.with_child(
|
||||||
Intro::default()
|
Intro::custom()
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_title(Lc::n("PageTop"))
|
.with_title(Lc::n("PageTop"))
|
||||||
.with_slogan(Lc::t("spacing_slogan", &LOC))
|
.with_slogan(Lc::t("spacing_slogan", &LOC))
|
||||||
.with_button(None::<(Lc, Route)>)
|
|
||||||
.with_child(Html::with(|cx| {
|
.with_child(Html::with(|cx| {
|
||||||
html! {
|
html! {
|
||||||
p class="intro-text-lead" {
|
p class="intro-text-lead" {
|
||||||
|
|
|
||||||
|
|
@ -47,8 +47,9 @@ pub mod image;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use image::Image;
|
pub use image::Image;
|
||||||
|
|
||||||
mod intro;
|
pub mod intro;
|
||||||
pub use intro::{Intro, IntroOpening};
|
#[doc(inline)]
|
||||||
|
pub use intro::Intro;
|
||||||
|
|
||||||
mod messages;
|
mod messages;
|
||||||
pub use messages::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`].
|
mod props;
|
||||||
///
|
pub use props::{Kind, Width};
|
||||||
/// 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,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Componente para divulgar PageTop (como hace [`Welcome`](crate::base::extension::Welcome)), o
|
mod component;
|
||||||
/// mostrar presentaciones.
|
pub use component::Intro;
|
||||||
///
|
|
||||||
/// 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
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",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::async_trait;
|
use crate::async_trait;
|
||||||
use crate::base::component::{Html, Intro, IntroOpening, layout};
|
use crate::base::component::{Html, Intro, layout};
|
||||||
use crate::core::component::{ChildOp, Component, ComponentError, ComponentRender};
|
use crate::core::component::{ChildOp, Component, ComponentError, ComponentRender};
|
||||||
use crate::core::component::{Context, Contextual};
|
use crate::core::component::{Context, Contextual};
|
||||||
use crate::core::extension::Extension;
|
use crate::core::extension::Extension;
|
||||||
|
|
@ -413,11 +413,9 @@ pub trait Theme: Extension + Send + Sync {
|
||||||
page.alter_title(title).alter_child_in(
|
page.alter_title(title).alter_child_in(
|
||||||
&CoreRegions::Content,
|
&CoreRegions::Content,
|
||||||
ChildOp::Prepend(
|
ChildOp::Prepend(
|
||||||
Intro::new()
|
Intro::custom()
|
||||||
.with_title(Lc::l("error_code").with_arg("code", code.to_string()))
|
.with_title(Lc::l("error_code").with_arg("code", code.to_string()))
|
||||||
.with_slogan(Lc::n(code.to_string()))
|
.with_slogan(Lc::n(code.to_string()))
|
||||||
.with_button(None)
|
|
||||||
.with_opening(IntroOpening::Custom)
|
|
||||||
.with_child(Html::with(move |cx| {
|
.with_child(Html::with(move |cx| {
|
||||||
html! {
|
html! {
|
||||||
h1 { (alert.using(cx)) }
|
h1 { (alert.using(cx)) }
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue