✨ (base): Añade with_width() a Container
This commit is contained in:
parent
9143f1eb26
commit
c6cd4fcd7d
11 changed files with 325 additions and 169 deletions
|
|
@ -44,7 +44,7 @@ body {
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header, Aside, Content y Footer se apilan en el mismo orden ocupando todo el ancho disponible. */
|
/* Header, Aside, Content and Footer are stacked in that order, using the full available width. */
|
||||||
.wrapper {
|
.wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
@ -125,10 +125,10 @@ body {
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Geometría base de .dropdown-button/.dropdown-toggle (ver "Dropdown component" más abajo). Se
|
/* Base geometry of .dropdown-button/.dropdown-toggle (see "Dropdown component" below). It is
|
||||||
* declara aquí, antes de "Buttons", para que las clases .button-sm/.button-lg/.button-* ganen el
|
* declared here, before "Buttons", so that the .button-sm/.button-lg/.button-* classes win the
|
||||||
* empate de especificidad frente a ella cuando se combinan en el mismo elemento (CSS resuelve un
|
* specificity tie against it when combined on the same element (CSS resolves a tie by order of
|
||||||
* empate por orden de aparición en el fichero, no por el orden de las clases en el HTML). */
|
* appearance in the file, not by the order of the classes in the HTML). */
|
||||||
.dropdown-button,
|
.dropdown-button,
|
||||||
.dropdown-toggle {
|
.dropdown-toggle {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
|
|
@ -247,6 +247,79 @@ body {
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Container component
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Classes added by the `Container` component from `Width`. All of them are centered and fill the
|
||||||
|
* available width; `.container` and `.container-{name}` also get a maximum width from the
|
||||||
|
* breakpoint they name (`.container` from the first one). The minimum widths match the default
|
||||||
|
* `Theme::breakpoint_entry()` table, so a child theme that renames or resizes the breakpoints must
|
||||||
|
* also redefine these classes. */
|
||||||
|
.container,
|
||||||
|
.container-fluid,
|
||||||
|
.container-sm,
|
||||||
|
.container-md,
|
||||||
|
.container-lg,
|
||||||
|
.container-xl,
|
||||||
|
.container-xxl,
|
||||||
|
.container-xxxl {
|
||||||
|
width: 100%;
|
||||||
|
margin-inline: auto;
|
||||||
|
padding-inline: 1rem;
|
||||||
|
}
|
||||||
|
@media (min-width: 576px) {
|
||||||
|
.container,
|
||||||
|
.container-sm {
|
||||||
|
max-width: 540px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.container,
|
||||||
|
.container-sm,
|
||||||
|
.container-md {
|
||||||
|
max-width: 720px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 992px) {
|
||||||
|
.container,
|
||||||
|
.container-sm,
|
||||||
|
.container-md,
|
||||||
|
.container-lg {
|
||||||
|
max-width: 960px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1200px) {
|
||||||
|
.container,
|
||||||
|
.container-sm,
|
||||||
|
.container-md,
|
||||||
|
.container-lg,
|
||||||
|
.container-xl {
|
||||||
|
max-width: 1140px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1400px) {
|
||||||
|
.container,
|
||||||
|
.container-sm,
|
||||||
|
.container-md,
|
||||||
|
.container-lg,
|
||||||
|
.container-xl,
|
||||||
|
.container-xxl {
|
||||||
|
max-width: 1320px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1920px) {
|
||||||
|
.container,
|
||||||
|
.container-sm,
|
||||||
|
.container-md,
|
||||||
|
.container-lg,
|
||||||
|
.container-xl,
|
||||||
|
.container-xxl,
|
||||||
|
.container-xxxl {
|
||||||
|
max-width: 1800px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Dialog component
|
* Dialog component
|
||||||
*/
|
*/
|
||||||
|
|
@ -318,10 +391,10 @@ body {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* La geometría base de .dropdown-button/.dropdown-toggle vive junto a "Buttons" (antes de esta
|
/* The base geometry of .dropdown-button/.dropdown-toggle lives next to "Buttons" (before this
|
||||||
* sección), para que las clases .button-sm/.button-lg/.button-* ganen el empate de especificidad
|
* section), so that the .button-sm/.button-lg/.button-* classes win the specificity tie against it
|
||||||
* frente a ella (CSS resuelve un empate por orden de aparición en el fichero, no por el orden de
|
* (CSS resolves a tie by order of appearance in the file, not by the order of the classes in the
|
||||||
* las clases en el HTML). */
|
* HTML). */
|
||||||
.dropdown-toggle::after {
|
.dropdown-toggle::after {
|
||||||
content: "\25BC";
|
content: "\25BC";
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
@ -928,9 +1001,9 @@ input:disabled + label {
|
||||||
color: var(--val-color--text--muted);
|
color: var(--val-color--text--muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .page-link-icon muestra el texto de navegación de los botones anterior/siguiente. El texto
|
/* .page-link-icon shows the navigation text of the previous/next buttons. The full accessible text
|
||||||
accesible completo va aparte, en el aria-label del enlace. Por eso sustituir el texto visible por
|
goes separately, in the link's aria-label. So replacing the visible text with other content does
|
||||||
otro contenido no afecta a la accesibilidad, p. ej. para usar "‹"/"›" se puede aplicar:
|
not affect accessibility, e.g. to use "‹"/"›" you can apply:
|
||||||
|
|
||||||
.page-link-icon {
|
.page-link-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
menus_item_label = Etiqueta
|
menus_item_label = Etiqueta
|
||||||
menus_item_link = Enlace
|
menus_item_link = Enlace
|
||||||
menus_item_blank = Enlace externo
|
menus_item_blank = Externo
|
||||||
menus_item_disabled = Enlace deshabilitado
|
menus_item_disabled = Deshabilitado
|
||||||
|
|
||||||
menus_test_title = Desplegable
|
menus_test_title = Desplegable
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
use pagetop_bootsier::theme::*;
|
|
||||||
|
|
||||||
include_locales!(LOC from "examples/locale");
|
include_locales!(LOC from "examples/locale");
|
||||||
|
|
||||||
|
|
@ -16,87 +15,84 @@ impl Extension for SuperMenu {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn initialize(&self) {
|
async fn initialize(&self) {
|
||||||
let navbar_menu = bs::Navbar::brand_left(bs::navbar::Brand::new())
|
let navbar_menu = Navbar::brand_left(Brand::new())
|
||||||
.with_expand(BreakPoint::LG)
|
.with_expand(Breakpoint::Lg)
|
||||||
.with_item(bs::navbar::Item::nav(
|
.with_item(navbar::Item::nav(
|
||||||
bs::Nav::new()
|
Nav::new()
|
||||||
.with_item(bs::nav::Item::link(Lc::t("menus_item_link", &LOC), "/"))
|
.with_item(nav::Item::link(Lc::t("menus_item_link", &LOC), "/"))
|
||||||
.with_item(bs::nav::Item::link_blank(
|
.with_item(nav::Item::link_blank(
|
||||||
Lc::t("menus_item_blank", &LOC),
|
Lc::t("menus_item_blank", &LOC),
|
||||||
"https://docs.rs/pagetop",
|
"https://docs.rs/pagetop",
|
||||||
))
|
))
|
||||||
.with_item(bs::nav::Item::dropdown(
|
.with_item(nav::Item::dropdown(
|
||||||
bs::Dropdown::new()
|
Dropdown::new()
|
||||||
.with_title(Lc::t("menus_test_title", &LOC))
|
.with_title(Lc::t("menus_test_title", &LOC))
|
||||||
.with_item(bs::dropdown::Item::header(Lc::t("menus_dev_header", &LOC)))
|
.with_item(dropdown::Item::header(Lc::t("menus_dev_header", &LOC)))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_dev_getting_started", &LOC),
|
Lc::t("menus_dev_getting_started", &LOC),
|
||||||
"/dev/getting-started",
|
"/dev/getting-started",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_dev_guides", &LOC),
|
Lc::t("menus_dev_guides", &LOC),
|
||||||
"/dev/guides",
|
"/dev/guides",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::link_blank(
|
.with_item(dropdown::Item::link_blank(
|
||||||
Lc::t("menus_dev_forum", &LOC),
|
Lc::t("menus_dev_forum", &LOC),
|
||||||
"https://forum.example.dev",
|
"https://forum.example.dev",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::divider())
|
.with_item(dropdown::Item::divider())
|
||||||
.with_item(bs::dropdown::Item::header(Lc::t("menus_sdk_header", &LOC)))
|
.with_item(dropdown::Item::header(Lc::t("menus_sdk_header", &LOC)))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_sdk_rust", &LOC),
|
Lc::t("menus_sdk_rust", &LOC),
|
||||||
"/dev/sdks/rust",
|
"/dev/sdks/rust",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_sdk_js", &LOC),
|
Lc::t("menus_sdk_js", &LOC),
|
||||||
"/dev/sdks/js",
|
"/dev/sdks/js",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_sdk_python", &LOC),
|
Lc::t("menus_sdk_python", &LOC),
|
||||||
"/dev/sdks/python",
|
"/dev/sdks/python",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::divider())
|
.with_item(dropdown::Item::divider())
|
||||||
.with_item(bs::dropdown::Item::header(Lc::t(
|
.with_item(dropdown::Item::header(Lc::t("menus_plugin_header", &LOC)))
|
||||||
"menus_plugin_header",
|
.with_item(dropdown::Item::link(
|
||||||
&LOC,
|
|
||||||
)))
|
|
||||||
.with_item(bs::dropdown::Item::link(
|
|
||||||
Lc::t("menus_plugin_auth", &LOC),
|
Lc::t("menus_plugin_auth", &LOC),
|
||||||
"/dev/sdks/rust/plugins/auth",
|
"/dev/sdks/rust/plugins/auth",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::link(
|
.with_item(dropdown::Item::link(
|
||||||
Lc::t("menus_plugin_cache", &LOC),
|
Lc::t("menus_plugin_cache", &LOC),
|
||||||
"/dev/sdks/rust/plugins/cache",
|
"/dev/sdks/rust/plugins/cache",
|
||||||
))
|
))
|
||||||
.with_item(bs::dropdown::Item::divider())
|
.with_item(dropdown::Item::divider())
|
||||||
.with_item(bs::dropdown::Item::label(Lc::t("menus_item_label", &LOC)))
|
.with_item(dropdown::Item::label(Lc::t("menus_item_label", &LOC)))
|
||||||
.with_item(bs::dropdown::Item::link_disabled(
|
.with_item(dropdown::Item::link_disabled(
|
||||||
Lc::t("menus_item_disabled", &LOC),
|
Lc::t("menus_item_disabled", &LOC),
|
||||||
"#",
|
"#",
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_item(bs::nav::Item::link_disabled(
|
.with_item(nav::Item::link_disabled(
|
||||||
Lc::t("menus_item_disabled", &LOC),
|
Lc::t("menus_item_disabled", &LOC),
|
||||||
"#",
|
"#",
|
||||||
)),
|
)),
|
||||||
))
|
))
|
||||||
.with_item(bs::navbar::Item::nav(
|
.with_item(navbar::Item::nav(
|
||||||
bs::Nav::new()
|
Nav::new()
|
||||||
// Empuja este menú (y lo que le siga) al extremo final de la barra.
|
// Empuja este menú (y lo que le siga) al extremo final de la barra.
|
||||||
.with_prop(FlexItem::push_end())
|
.with_prop(FlexItem::push_end())
|
||||||
.with_item(bs::nav::Item::link(
|
.with_item(nav::Item::link(
|
||||||
Lc::t("menus_item_sign_up", &LOC),
|
Lc::t("menus_item_sign_up", &LOC),
|
||||||
"/auth/sign-up",
|
"/auth/sign-up",
|
||||||
))
|
))
|
||||||
.with_item(bs::nav::Item::link(
|
.with_item(nav::Item::link(
|
||||||
Lc::t("menus_item_login", &LOC),
|
Lc::t("menus_item_login", &LOC),
|
||||||
"/auth/login",
|
"/auth/login",
|
||||||
)),
|
)),
|
||||||
));
|
));
|
||||||
|
|
||||||
InRegion::Global(&CoreRegions::Header).add(
|
InRegion::Global(&CoreRegions::Header).add(
|
||||||
bs::Container::new()
|
Container::new()
|
||||||
.with_width(bs::container::Width::FluidMax(UnitValue::RelRem(75.0)))
|
.with_width(container::Width::FluidMax(UnitValue::RelRem(75.0)))
|
||||||
.with_child(navbar_menu),
|
.with_child(navbar_menu),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -146,12 +146,12 @@ impl Theme for Bootsier {
|
||||||
theme::BootsierColors::from(intent).as_str()
|
theme::BootsierColors::from(intent).as_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn setup_component(&self, component: &mut dyn Component, cx: &mut Context) {
|
fn setup_component(&self, component: &mut dyn Component, _cx: &mut Context) {
|
||||||
setup_component!(component, {
|
setup_component!(component, {
|
||||||
Badge => |c| theme::bs::badge::setup(c),
|
Badge => |c| theme::bs::badge::setup(c),
|
||||||
Brand => |c| theme::bs::brand::setup(c),
|
Brand => |c| theme::bs::brand::setup(c),
|
||||||
Button => |c| theme::bs::button::setup(c),
|
Button => |c| theme::bs::button::setup(c),
|
||||||
Container => |c| theme::bs::container::setup(c, cx),
|
Container => |c| theme::bs::container::setup(c),
|
||||||
Dialog => |c| theme::bs::dialog::setup(c),
|
Dialog => |c| theme::bs::dialog::setup(c),
|
||||||
Dropdown => |c| theme::bs::dropdown::setup(c),
|
Dropdown => |c| theme::bs::dropdown::setup(c),
|
||||||
Image => |c| theme::bs::image::setup(c),
|
Image => |c| theme::bs::image::setup(c),
|
||||||
|
|
|
||||||
|
|
@ -103,8 +103,6 @@ pub use bs::badge::BadgeBootsier;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use bs::button::ButtonBootsier;
|
pub use bs::button::ButtonBootsier;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use bs::container::ContainerBootsier;
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub use bs::dropdown::DropdownBootsier;
|
pub use bs::dropdown::DropdownBootsier;
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use bs::form::input::InputBootsier;
|
pub use bs::form::input::InputBootsier;
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,6 @@ pub use button::{Button, ButtonBootsier};
|
||||||
pub mod container;
|
pub mod container;
|
||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use container::Container;
|
pub use container::Container;
|
||||||
#[doc(inline)]
|
|
||||||
pub use container::ContainerBootsier;
|
|
||||||
|
|
||||||
// Dialog.
|
// Dialog.
|
||||||
pub mod dialog;
|
pub mod dialog;
|
||||||
|
|
|
||||||
|
|
@ -1,108 +1,37 @@
|
||||||
//! Definiciones para crear contenedores de componentes ([`Container`]).
|
//! Definiciones para crear contenedores de componentes ([`Container`]).
|
||||||
|
//!
|
||||||
|
//! El comportamiento del ancho del contenedor se establece con [`Container::with_width()`], usando
|
||||||
|
//! [`Width`]. Sin él, Bootsier aplica los anchos máximos predefinidos de Bootstrap para cada punto
|
||||||
|
//! de corte ([`Width::Responsive`]).
|
||||||
|
//!
|
||||||
|
//! También se pueden aplicar al componente clases predefinidas para:
|
||||||
|
//!
|
||||||
|
//! - Modificar el color de fondo ([`Bg`](crate::theme::class::Bg)).
|
||||||
|
//! - Definir la apariencia del texto ([`Text`](crate::theme::class::Text)).
|
||||||
|
//! - Establecer bordes ([`Border`](crate::theme::class::Border)).
|
||||||
|
//! - Redondear las esquinas ([`Rounded`](crate::theme::class::Rounded)).
|
||||||
|
//!
|
||||||
|
//! ```rust,no_run
|
||||||
|
//! use pagetop::prelude::*;
|
||||||
|
//! use pagetop_bootsier::theme::*;
|
||||||
|
//!
|
||||||
|
//! let main = bs::Container::main()
|
||||||
|
//! .with_id("main-page")
|
||||||
|
//! .with_width(bs::container::Width::From(Breakpoint::Lg))
|
||||||
|
//! .with_prop(PropsOp::add_classes(class::Bg::with(BootsierColors::Light)))
|
||||||
|
//! .with_prop(PropsOp::add_classes(class::Text::with(BootsierColors::Dark)))
|
||||||
|
//! .with_prop(PropsOp::add_classes(class::Border::with(ScaleSize::One)))
|
||||||
|
//! .with_prop(PropsOp::add_classes(class::Rounded::new()));
|
||||||
|
//! ```
|
||||||
|
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::theme::*;
|
pub use pagetop::base::component::container::{Container, Kind, Width};
|
||||||
|
|
||||||
pub use pagetop::base::component::container::{Container, Kind};
|
|
||||||
|
|
||||||
const EXTRA_WIDTH: &str = "bootsier.container.width";
|
|
||||||
|
|
||||||
/// Extensión de Bootsier para [`Container`].
|
|
||||||
///
|
|
||||||
/// Permite establecer el comportamiento del ancho del contenedor usando el método
|
|
||||||
/// [`with_width()`](Self::with_width).
|
|
||||||
///
|
|
||||||
/// También habilita al componente para aceptar clases predefinidas para:
|
|
||||||
///
|
|
||||||
/// - Modificar el color de fondo ([`Bg`](crate::theme::class::Bg)).
|
|
||||||
/// - Definir la apariencia del texto ([`Text`](crate::theme::class::Text)).
|
|
||||||
/// - Establecer bordes ([`Border`](crate::theme::class::Border)).
|
|
||||||
/// - Redondear las esquinas ([`Rounded`](crate::theme::class::Rounded)).
|
|
||||||
///
|
|
||||||
/// ```rust,no_run
|
|
||||||
/// use pagetop::prelude::*;
|
|
||||||
/// use pagetop_bootsier::theme::*;
|
|
||||||
///
|
|
||||||
/// let main = bs::Container::main()
|
|
||||||
/// .with_id("main-page")
|
|
||||||
/// .with_width(bs::container::Width::From(Breakpoint::Lg))
|
|
||||||
/// .with_prop(PropsOp::add_classes(class::Bg::with(BootsierColors::Light)))
|
|
||||||
/// .with_prop(PropsOp::add_classes(class::Text::with(BootsierColors::Dark)))
|
|
||||||
/// .with_prop(PropsOp::add_classes(class::Border::with(ScaleSize::One)))
|
|
||||||
/// .with_prop(PropsOp::add_classes(class::Rounded::new()));
|
|
||||||
/// ```
|
|
||||||
#[builder_impl]
|
|
||||||
pub trait ContainerBootsier {
|
|
||||||
/// Establece el comportamiento del ancho para el contenedor.
|
|
||||||
///
|
|
||||||
/// Determina si el contenedor aplica los anchos máximos predefinidos para cada punto de corte,
|
|
||||||
/// o si ocupa siempre el 100% del ancho disponible, o lo hace hasta un ancho máximo explícito.
|
|
||||||
/// Ver [`Width`] para las variantes disponibles.
|
|
||||||
fn with_width(self, width: Width) -> Self;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[builder_impl]
|
|
||||||
impl ContainerBootsier for Container {
|
|
||||||
fn with_width(mut self, width: Width) -> Self {
|
|
||||||
self.alter_prop(PropsOp::set_extra(EXTRA_WIDTH, width));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Width >**************************************************************************************
|
|
||||||
|
|
||||||
/// Define cómo se comporta el ancho de un contenedor ([`Container`]).
|
|
||||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
|
||||||
pub enum Width {
|
|
||||||
/// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de corte.
|
|
||||||
/// Por debajo del menor punto de corte ocupa el 100% del ancho disponible.
|
|
||||||
#[default]
|
|
||||||
Default,
|
|
||||||
/// Aplica los anchos máximos predefinidos a partir del punto de corte indicado. Por debajo de
|
|
||||||
/// ese punto de corte ocupa el 100% del ancho disponible.
|
|
||||||
From(Breakpoint),
|
|
||||||
/// Ocupa el 100% del ancho disponible siempre.
|
|
||||||
Fluid,
|
|
||||||
/// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito.
|
|
||||||
FluidMax(UnitValue),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Width {
|
|
||||||
const CONTAINER: &str = "container";
|
|
||||||
|
|
||||||
/// Añade la clase asociada al ancho del contenedor a la cadena de clases.
|
|
||||||
///
|
|
||||||
/// El nombre del punto de corte se resuelve en el tema activo de `cx`.
|
|
||||||
#[inline]
|
|
||||||
pub fn push_to(self, cx: &Context, classes: &mut String) {
|
|
||||||
match self {
|
|
||||||
Self::Default => {
|
|
||||||
push_breakpoint_class(cx, Breakpoint::Xs, classes, Self::CONTAINER, "")
|
|
||||||
}
|
|
||||||
Self::From(bp) => push_breakpoint_class(cx, bp, classes, Self::CONTAINER, ""),
|
|
||||||
Self::Fluid | Self::FluidMax(_) => {
|
|
||||||
push_breakpoint_class(cx, Breakpoint::Xs, classes, Self::CONTAINER, "fluid")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Devuelve la clase asociada al ancho del contenedor.
|
|
||||||
pub fn to_class(self, cx: &Context) -> String {
|
|
||||||
let mut class = String::new();
|
|
||||||
self.push_to(cx, &mut class);
|
|
||||||
class
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// **< Container SETUP >****************************************************************************
|
// **< Container SETUP >****************************************************************************
|
||||||
|
|
||||||
pub(crate) fn setup(container: &mut Container, cx: &Context) {
|
pub(crate) fn setup(container: &mut Container) {
|
||||||
let width = container.props().extra_or(EXTRA_WIDTH, Width::default());
|
if container.width().is_none() {
|
||||||
container.alter_prop(PropsOp::prepend_classes(width.to_class(cx)));
|
container.alter_prop(PropsOp::prepend_classes("container"));
|
||||||
if let Width::FluidMax(w) = width
|
|
||||||
&& w.is_measurable()
|
|
||||||
{
|
|
||||||
container.alter_prop(PropsOp::add_style("max-width", w.to_string()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
|
|
||||||
use crate::config;
|
use crate::config;
|
||||||
use crate::theme::{ContainerBootsier, bs};
|
use crate::theme::bs;
|
||||||
use crate::{ADMINLTE_VERSION, LOCALES_BOOTSIER};
|
use crate::{ADMINLTE_VERSION, LOCALES_BOOTSIER};
|
||||||
|
|
||||||
// Regiones de Bootsier: se renderiza sin el `<div role="region">` envolvente que aplica
|
// Regiones de Bootsier: se renderiza sin el `<div role="region">` envolvente que aplica
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
// Verifies that Bootsier builds its breakpoint-based Bootstrap classes from the breakpoint names
|
// Verifies that the breakpoint-based Bootstrap classes of `Container`, `Navbar`, `Dropdown` and
|
||||||
// of `Bootsier::breakpoint_entry()`, and that `Xxxl` (absent in Bootstrap) resolves as `xxl`.
|
// `Offcanvas` use the breakpoint names of `Bootsier::breakpoint_entry()`, and that `Xxxl` (absent
|
||||||
|
// in Bootstrap) resolves as `xxl`.
|
||||||
|
|
||||||
use pagetop::prelude::*;
|
use pagetop::prelude::*;
|
||||||
use pagetop_bootsier::theme::*;
|
use pagetop_bootsier::theme::*;
|
||||||
|
|
@ -8,23 +9,43 @@ fn cx() -> Context {
|
||||||
Context::default().with_theme(&pagetop_bootsier::Bootsier)
|
Context::default().with_theme(&pagetop_bootsier::Bootsier)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn container_html(width: impl Into<Option<bs::container::Width>>) -> String {
|
||||||
|
let mut container = bs::Container::new()
|
||||||
|
.with_width(width)
|
||||||
|
.with_child(Html::with(|_| html! { p { "Content" } }));
|
||||||
|
container.render(&mut cx()).await.into_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn container_without_width_gets_the_bootstrap_default() {
|
||||||
|
assert!(container_html(None).await.contains(r#"class="container""#));
|
||||||
|
assert!(
|
||||||
|
container_html(bs::container::Width::Responsive)
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container""#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn container_width_uses_breakpoint_name() {
|
async fn container_width_uses_breakpoint_name() {
|
||||||
let cx = cx();
|
assert!(
|
||||||
assert_eq!(bs::container::Width::Default.to_class(&cx), "container");
|
container_html(bs::container::Width::Fluid)
|
||||||
assert_eq!(bs::container::Width::Fluid.to_class(&cx), "container-fluid");
|
.await
|
||||||
assert_eq!(
|
.contains(r#"class="container-fluid""#)
|
||||||
bs::container::Width::From(Breakpoint::Lg).to_class(&cx),
|
);
|
||||||
"container-lg"
|
assert!(
|
||||||
|
container_html(bs::container::Width::From(Breakpoint::Lg))
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container-lg""#)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pagetop::test]
|
#[pagetop::test]
|
||||||
async fn container_width_xxxl_resolves_as_xxl() {
|
async fn container_width_xxxl_resolves_as_xxl() {
|
||||||
let cx = cx();
|
assert!(
|
||||||
assert_eq!(
|
container_html(bs::container::Width::From(Breakpoint::Xxxl))
|
||||||
bs::container::Width::From(Breakpoint::Xxxl).to_class(&cx),
|
.await
|
||||||
"container-xxl"
|
.contains(r#"class="container-xxl""#)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,28 +25,61 @@ pub enum Kind {
|
||||||
Article,
|
Article,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Width >**************************************************************************************
|
||||||
|
|
||||||
|
/// Define cómo se comporta el ancho de un contenedor ([`Container`]).
|
||||||
|
///
|
||||||
|
/// Cada variante se traduce en una clase CSS (`container`, `container-{nombre}` o
|
||||||
|
/// `container-fluid`), con el nombre que el tema activo da al punto de corte, y el tema decide qué
|
||||||
|
/// anchos máximos le corresponden. [`FluidMax`](Self::FluidMax) añade además un `max-width` en
|
||||||
|
/// línea.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub enum Width {
|
||||||
|
/// Aplica los anchos máximos predefinidos por el tema para cada punto de corte. Por debajo del
|
||||||
|
/// menor punto de corte ocupa el 100% del ancho disponible.
|
||||||
|
Responsive,
|
||||||
|
/// Aplica los anchos máximos predefinidos a partir del punto de corte indicado. Por debajo de
|
||||||
|
/// ese punto de corte ocupa el 100% del ancho disponible.
|
||||||
|
From(Breakpoint),
|
||||||
|
/// Ocupa el 100% del ancho disponible siempre.
|
||||||
|
Fluid,
|
||||||
|
/// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito.
|
||||||
|
FluidMax(UnitValue),
|
||||||
|
}
|
||||||
|
|
||||||
// **< Container >**********************************************************************************
|
// **< Container >**********************************************************************************
|
||||||
|
|
||||||
/// Componente para crear un **contenedor de componentes**.
|
/// Componente para crear un **contenedor de componentes**.
|
||||||
///
|
///
|
||||||
/// Envuelve un conjunto de componentes en un contenedor establecido que se crea aplicando uno de
|
/// Envuelve un conjunto de componentes en un contenedor establecido que se crea aplicando uno de
|
||||||
/// los tipos definidos en [`Kind`].
|
/// los tipos definidos en [`Kind`]. Opcionalmente, su ancho se controla con [`Width`] mediante
|
||||||
|
/// [`with_width()`](Self::with_width); sin él, el contenedor no añade ninguna clase de ancho.
|
||||||
///
|
///
|
||||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
/// Si no contiene elementos, el componente **no se renderiza**.
|
||||||
///
|
///
|
||||||
/// # Ejemplo
|
/// # Ejemplos
|
||||||
///
|
///
|
||||||
/// ```rust,no_run
|
/// ```rust,no_run
|
||||||
/// use pagetop::prelude::*;
|
/// use pagetop::prelude::*;
|
||||||
///
|
///
|
||||||
/// let main = Container::main().with_id("main-page");
|
/// let main = Container::main().with_id("main-page");
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// Contenedor centrado que ocupa todo el ancho hasta un máximo explícito:
|
||||||
|
///
|
||||||
|
/// ```rust,no_run
|
||||||
|
/// # use pagetop::prelude::*;
|
||||||
|
/// let page = Container::new().with_width(container::Width::FluidMax(UnitValue::RelRem(75.0)));
|
||||||
|
/// ```
|
||||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||||
pub struct Container {
|
pub struct Container {
|
||||||
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
||||||
props: Props,
|
props: Props,
|
||||||
/// Devuelve el tipo semántico del contenedor.
|
/// Devuelve el tipo semántico del contenedor.
|
||||||
kind: Kind,
|
kind: Kind,
|
||||||
|
/// Devuelve el comportamiento del ancho, si se ha fijado.
|
||||||
|
#[getters(copy)]
|
||||||
|
width: Option<Width>,
|
||||||
/// Devuelve la lista de componentes (`children`) del contenedor.
|
/// Devuelve la lista de componentes (`children`) del contenedor.
|
||||||
children: Children,
|
children: Children,
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +94,25 @@ impl Component for Container {
|
||||||
self.props.get_id()
|
self.props.get_id()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup(&mut self, cx: &mut Context) {
|
||||||
|
if let Some(width) = self.width() {
|
||||||
|
let class: CowStr = match width {
|
||||||
|
Width::Responsive => "container".into(),
|
||||||
|
Width::From(bp) => match bp.resolved(cx) {
|
||||||
|
Some(entry) => util::join!("container-", entry.name).into(),
|
||||||
|
None => "container".into(),
|
||||||
|
},
|
||||||
|
Width::Fluid | Width::FluidMax(_) => "container-fluid".into(),
|
||||||
|
};
|
||||||
|
self.alter_prop(PropsOp::prepend_classes(class));
|
||||||
|
if let Width::FluidMax(max) = width
|
||||||
|
&& max.is_measurable()
|
||||||
|
{
|
||||||
|
self.alter_prop(PropsOp::add_style("max-width", max.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||||
let output = self.children().render(cx).await;
|
let output = self.children().render(cx).await;
|
||||||
|
|
@ -135,6 +187,15 @@ impl Container {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Define el comportamiento del ancho del contenedor. Con `None` no se añade ninguna clase de
|
||||||
|
/// ancho y cada tema decide cómo tratarlo.
|
||||||
|
///
|
||||||
|
/// Ver [`Width`] para las variantes disponibles.
|
||||||
|
pub fn with_width(mut self, width: impl Into<Option<Width>>) -> Self {
|
||||||
|
self.width = width.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
|
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
|
||||||
/// una operación [`ChildOp`].
|
/// una operación [`ChildOp`].
|
||||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,83 @@ async fn main_kind_renders_a_main_element() {
|
||||||
assert!(html.starts_with("<main"));
|
assert!(html.starts_with("<main"));
|
||||||
assert!(html.ends_with("</main>"));
|
assert!(html.ends_with("</main>"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// **< Container::with_width >**********************************************************************
|
||||||
|
|
||||||
|
async fn container_html(width: impl Into<Option<container::Width>>) -> String {
|
||||||
|
let mut container = Container::new().with_width(width).with_child(Lc::n("x"));
|
||||||
|
container
|
||||||
|
.render(&mut Context::default())
|
||||||
|
.await
|
||||||
|
.into_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_is_none_by_default_and_adds_no_class() {
|
||||||
|
assert_eq!(Container::new().width(), None);
|
||||||
|
// A neutral box: without a width, the component adds no class at all.
|
||||||
|
assert!(!container_html(None).await.contains("container"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_can_be_set_and_reset() {
|
||||||
|
let width = container::Width::From(Breakpoint::Lg);
|
||||||
|
assert_eq!(Container::new().with_width(width).width(), Some(width));
|
||||||
|
assert_eq!(
|
||||||
|
Container::new().with_width(width).with_width(None).width(),
|
||||||
|
None
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_responsive_adds_the_container_class() {
|
||||||
|
assert!(
|
||||||
|
container_html(container::Width::Responsive)
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container""#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_from_uses_the_breakpoint_name() {
|
||||||
|
assert!(
|
||||||
|
container_html(container::Width::From(Breakpoint::Lg))
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container-lg""#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_from_a_breakpoint_without_min_width_has_no_name() {
|
||||||
|
// `Xs` has no minimum width, so the class carries no breakpoint name.
|
||||||
|
assert!(
|
||||||
|
container_html(container::Width::From(Breakpoint::Xs))
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container""#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_fluid_adds_the_container_fluid_class() {
|
||||||
|
assert!(
|
||||||
|
container_html(container::Width::Fluid)
|
||||||
|
.await
|
||||||
|
.contains(r#"class="container-fluid""#)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_fluid_max_adds_the_class_and_a_max_width_style() {
|
||||||
|
let html = container_html(container::Width::FluidMax(UnitValue::RelRem(75.0))).await;
|
||||||
|
|
||||||
|
assert!(html.contains("container-fluid"));
|
||||||
|
assert!(html.contains("max-width: 75rem"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pagetop::test]
|
||||||
|
async fn width_fluid_max_ignores_a_non_measurable_value() {
|
||||||
|
let html = container_html(container::Width::FluidMax(UnitValue::Auto)).await;
|
||||||
|
|
||||||
|
assert!(html.contains("container-fluid"));
|
||||||
|
assert!(!html.contains("max-width"));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue