✨ (pagetop): Añade Grid y extrae Flex de Container
This commit is contained in:
parent
dfff12881f
commit
307ca6f62c
44 changed files with 2554 additions and 735 deletions
|
|
@ -30,10 +30,16 @@ pub mod dropdown;
|
|||
#[doc(inline)]
|
||||
pub use dropdown::Dropdown;
|
||||
|
||||
mod flex;
|
||||
pub use flex::Flex;
|
||||
|
||||
pub mod form;
|
||||
#[doc(inline)]
|
||||
pub use form::Form;
|
||||
|
||||
mod grid;
|
||||
pub use grid::Grid;
|
||||
|
||||
mod html;
|
||||
pub use html::Html;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,9 +47,6 @@ pub struct Container {
|
|||
props: Props,
|
||||
/// Devuelve el tipo semántico del contenedor.
|
||||
kind: Kind,
|
||||
/// Devuelve el posicionamiento Flexbox como contenedor, si tiene alguno.
|
||||
#[getters(copy)]
|
||||
flex: Flex,
|
||||
/// Devuelve la lista de componentes (`children`) del contenedor.
|
||||
children: Children,
|
||||
}
|
||||
|
|
@ -70,7 +67,7 @@ impl Component for Container {
|
|||
if output.is_empty() {
|
||||
return Ok(html! {});
|
||||
}
|
||||
let container_props = self.props().unpack_with_flex(cx, self.flex());
|
||||
let container_props = self.props().unpack(cx);
|
||||
Ok(match self.kind() {
|
||||
Kind::Default => html! { div (container_props) { (output) } },
|
||||
Kind::Main => html! { main (container_props) { (output) } },
|
||||
|
|
@ -138,12 +135,6 @@ impl Container {
|
|||
self
|
||||
}
|
||||
|
||||
/// Establece el posicionamiento Flexbox como contenedor (usa `None` para quitarlo).
|
||||
pub fn with_flex(mut self, flex: impl Into<Option<Flex>>) -> Self {
|
||||
self.flex = self.flex.merge(flex);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
|
||||
/// una operación [`ChildOp`].
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
|
|
|
|||
299
src/base/component/flex.rs
Normal file
299
src/base/component/flex.rs
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
use crate::html::align;
|
||||
use crate::html::flex::{Behavior, ContentJustify, Direction};
|
||||
use crate::prelude::*;
|
||||
|
||||
// **< DisplayFlex >********************************************************************************
|
||||
|
||||
// Posicionamiento Flexbox del contenedor `Flex`. La API pública sólo expone los constructores
|
||||
// `Flex::new()`, `Flex::at()`, `Flex::inline()` y `Flex::inline_at()`, nunca la variante en sí.
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
enum DisplayFlex {
|
||||
#[default]
|
||||
Always,
|
||||
AlwaysInline,
|
||||
At(Breakpoint),
|
||||
InlineAt(Breakpoint),
|
||||
}
|
||||
|
||||
// **< Flex >***************************************************************************************
|
||||
|
||||
/// Componente que crea un **contenedor Flexbox** para posicionar componentes.
|
||||
///
|
||||
/// Es el único componente de PageTop que ofrece posicionamiento Flexbox. Sus hijos pueden ser
|
||||
/// **cualquier componente**, no sólo otro `Flex`. Para colocarlos dentro del contenedor
|
||||
/// (crecimiento, reducción, alineación individual, orden, tamaño, desplazamiento) se usa
|
||||
/// [`flex::FlexItem`] vía `with_prop()`, exactamente igual que en cualquier otro contenedor.
|
||||
/// `FlexItem` es una característica de [`PropsOp`], no exige ningún envoltorio propio.
|
||||
///
|
||||
/// Se aplica con clases CSS generadas dinámicamente (`display`, `flex-direction`, `flex-wrap`,
|
||||
/// `justify-content`, `align-items`, `align-content`, `gap`, `row-gap`, `column-gap`), registradas
|
||||
/// vía [`AssetsOp::add_responsive_style()`] en [`ResponsiveStyles`] y renderizadas como reglas en
|
||||
/// el `<head>` del documento. Son propiedades nativas que no requieren interpretación por parte de
|
||||
/// los temas, siempre funcionan igual, sin una sola línea de CSS ni de código específico.
|
||||
///
|
||||
/// `align`/`align_content`/`gap` usan tipos de [`pagetop::html::align`] ([`align::Items`],
|
||||
/// [`align::Content`], [`align::Gap`]), compartidos con [`Grid`], mismo CSS y mismo catálogo de
|
||||
/// valores en los dos. Ver la documentación de [`align`](crate::html::align) para el criterio
|
||||
/// completo.
|
||||
///
|
||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
||||
///
|
||||
/// [`AssetsOp::add_responsive_style()`]: crate::core::component::AssetsOp::add_responsive_style
|
||||
/// [`pagetop::html::align`]: crate::html::align
|
||||
/// [`ResponsiveStyles`]: crate::html::ResponsiveStyles
|
||||
/// [`flex::FlexItem`]: crate::html::flex::FlexItem
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let actions = Flex::new()
|
||||
/// .with_justify(flex::ContentJustify::End)
|
||||
/// .with_align(align::Items::Center)
|
||||
/// .with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))
|
||||
/// .with_child(Button::submit(Lc::n("Save")))
|
||||
/// .with_child(Button::plain(Lc::n("Cancel")));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Flex {
|
||||
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
||||
props: Props,
|
||||
// Determina si esta configuración debe aplicarse (y con qué variante de `display`) o si `Flex`
|
||||
// no está en absoluto configurado. `None` es el estado real de ausencia: lo que tiene un
|
||||
// componente que nunca ha llamado a ninguno de sus constructores explícitos. Sin getter
|
||||
// público; `new()`, `at()`, `inline()` e `inline_at()` son la única forma de activarlo.
|
||||
#[getters(skip)]
|
||||
display: Option<DisplayFlex>,
|
||||
/// Devuelve la dirección del eje principal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
direction: Responsive<Direction>,
|
||||
/// Devuelve el comportamiento cuando los elementos no caben en una sola línea, por punto de
|
||||
/// corte.
|
||||
#[getters(copy)]
|
||||
wrap: Responsive<Behavior>,
|
||||
/// Devuelve la alineación de los elementos en el eje principal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
justify: Responsive<ContentJustify>,
|
||||
/// Devuelve la alineación de los elementos en el eje transversal, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align: Responsive<align::Items>,
|
||||
/// Devuelve la alineación de las líneas cuando hay más de una, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align_content: Responsive<align::Content>,
|
||||
/// Devuelve el espaciado entre elementos, por punto de corte.
|
||||
#[getters(copy)]
|
||||
gap: Responsive<align::Gap>,
|
||||
/// Devuelve la lista de componentes (`children`) del contenedor.
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Flex {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let output = self.children().render(cx).await;
|
||||
if output.is_empty() {
|
||||
return Ok(html! {});
|
||||
}
|
||||
let mut classes = String::new();
|
||||
self.flex_classes(cx, &mut classes);
|
||||
let container_props = self.props().unpack_with_classes(cx, classes);
|
||||
Ok(html! { div (container_props) { (output) } })
|
||||
}
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Flex {
|
||||
/// Define una configuración con `display: flex`, sin punto de corte: se aplica siempre.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::Always),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: flex` que se aplica a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::At(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: inline-flex`, sin punto de corte: se aplica
|
||||
/// siempre.
|
||||
pub fn inline() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::AlwaysInline),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: inline-flex` que se aplica a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn inline_at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
display: Some(DisplayFlex::InlineAt(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// **< Flex BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica identificador, clases CSS, atributos HTML o valores extra del componente.
|
||||
pub fn with_prop(mut self, op: impl Into<PropsOp>) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la dirección del eje principal.
|
||||
pub fn with_direction(mut self, dir: Direction) -> Self {
|
||||
self.direction = self.direction.set(dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la dirección del eje principal a partir del punto de corte indicado.
|
||||
pub fn with_direction_at(mut self, bp: Breakpoint, dir: Direction) -> Self {
|
||||
self.direction = self.direction.set_at(bp, dir);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el comportamiento cuando los elementos no caben en una sola línea.
|
||||
pub fn with_wrap(mut self, wrap: Behavior) -> Self {
|
||||
self.wrap = self.wrap.set(wrap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el comportamiento cuando los elementos no caben en una sola línea, a partir del
|
||||
/// punto de corte indicado.
|
||||
pub fn with_wrap_at(mut self, bp: Breakpoint, wrap: Behavior) -> Self {
|
||||
self.wrap = self.wrap.set_at(bp, wrap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje principal.
|
||||
pub fn with_justify(mut self, justify: ContentJustify) -> Self {
|
||||
self.justify = self.justify.set(justify);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje principal, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_justify_at(mut self, bp: Breakpoint, justify: ContentJustify) -> Self {
|
||||
self.justify = self.justify.set_at(bp, justify);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje transversal.
|
||||
pub fn with_align(mut self, align: align::Items) -> Self {
|
||||
self.align = self.align.set(align);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje transversal, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_align_at(mut self, bp: Breakpoint, align: align::Items) -> Self {
|
||||
self.align = self.align.set_at(bp, align);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las líneas cuando hay más de una (ver
|
||||
/// [`Content`](crate::html::align::Content)).
|
||||
pub fn with_align_content(mut self, align_content: align::Content) -> Self {
|
||||
self.align_content = self.align_content.set(align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las líneas cuando hay más de una (ver
|
||||
/// [`Content`](crate::html::align::Content)), a partir del punto de corte indicado.
|
||||
pub fn with_align_content_at(mut self, bp: Breakpoint, align_content: align::Content) -> Self {
|
||||
self.align_content = self.align_content.set_at(bp, align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre elementos.
|
||||
pub fn with_gap(mut self, gap: align::Gap) -> Self {
|
||||
self.gap = self.gap.set(gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre elementos, a partir del punto de corte indicado.
|
||||
pub fn with_gap_at(mut self, bp: Breakpoint, gap: align::Gap) -> Self {
|
||||
self.gap = self.gap.set_at(bp, gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
|
||||
/// una operación [`ChildOp`].
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
self.children.alter_child(op.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Flex {
|
||||
// Calcula las clases CSS *responsive* de este contenedor Flex y las añade a `classes`,
|
||||
// separadas con un espacio de las que ya hubiera, para poder compartir un único acumulador
|
||||
// con `FlexItem::apply()` (ver `Props::unpack_with_classes()`) sin cadenas intermedias.
|
||||
#[rustfmt::skip]
|
||||
fn flex_classes(&self, cx: &mut Context, classes: &mut String) {
|
||||
// Sin `display` no hay contenedor Flex: el resto de propiedades (`flex-direction`, `gap`,
|
||||
// etc.) no tienen ningún efecto en CSS sin `display: flex`/`inline-flex`, así que ni se
|
||||
// generan.
|
||||
let Some(display) = self.display else {
|
||||
return;
|
||||
};
|
||||
|
||||
use crate::html::responsive::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
let (prefix, value) = match display {
|
||||
DisplayFlex::Always
|
||||
| DisplayFlex::At(_) => ("_flex_", "flex"),
|
||||
DisplayFlex::AlwaysInline
|
||||
| DisplayFlex::InlineAt(_) => ("_inline-flex_", "inline-flex"),
|
||||
};
|
||||
let entry = match display {
|
||||
DisplayFlex::At(bp) | DisplayFlex::InlineAt(bp) => bp.resolved(cx),
|
||||
_ => None,
|
||||
};
|
||||
let class = match entry {
|
||||
None => prefix.into(),
|
||||
Some(entry) => util::join!(prefix, entry.name, "_").into(),
|
||||
};
|
||||
styles(cx, classes, entry, class, "display", value.into());
|
||||
|
||||
apply!(cx, classes, self.direction, "_flex-direction_", "flex-direction");
|
||||
apply!(cx, classes, self.wrap, "_flex-wrap_", "flex-wrap");
|
||||
apply!(cx, classes, self.justify, "_flex-justify_", "justify-content");
|
||||
apply!(cx, classes, self.align, "_flex-align-items_", "align-items");
|
||||
apply!(cx, classes, self.align_content, "_flex-align-content_", "align-content");
|
||||
for (bp, gap) in self.gap.by_breakpoint() {
|
||||
let entry = bp.resolved(cx);
|
||||
for (property, value) in gap.styles().into_iter().flatten() {
|
||||
// El prefijo de `gap` no es literal (depende de la propiedad), así que se compone
|
||||
// aquí en un único `join!` en vez de pasar por `responsive_class!`.
|
||||
let token = value_to_token(&value);
|
||||
let class = match entry {
|
||||
None => util::join!("_flex-", property, "_", token, "_"),
|
||||
Some(e) => util::join!("_flex-", property, "_", token, "_", e.name, "_"),
|
||||
};
|
||||
styles(cx, classes, entry, class.into(), property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
376
src/base/component/grid.rs
Normal file
376
src/base/component/grid.rs
Normal file
|
|
@ -0,0 +1,376 @@
|
|||
use crate::html::align::{Content, Gap, Items};
|
||||
use crate::html::grid::{AutoFlow, AxisTrack, ContentJustify, DefaultJustify, Tracks};
|
||||
use crate::prelude::*;
|
||||
|
||||
// **< DisplayGrid >********************************************************************************
|
||||
|
||||
// Posicionamiento CSS Grid del contenedor `Grid`. La API pública sólo expone los constructores
|
||||
// `Grid::new()`, `Grid::at()`, `Grid::inline()` e `Grid::inline_at()`, nunca la variante en sí.
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
enum DisplayGrid {
|
||||
#[default]
|
||||
Always,
|
||||
AlwaysInline,
|
||||
At(Breakpoint),
|
||||
InlineAt(Breakpoint),
|
||||
}
|
||||
|
||||
// **< Grid >***************************************************************************************
|
||||
|
||||
/// Componente que crea una **rejilla CSS Grid** para posicionar componentes.
|
||||
///
|
||||
/// Es el único componente de PageTop que ofrece posicionamiento CSS Grid (con el mismo criterio que
|
||||
/// [`Flex`] para Flexbox). Sus hijos pueden ser **cualquier componente**, no sólo otro `Grid`. Para
|
||||
/// colocarlos dentro de la rejilla (columna, fila, alineación individual) se usa [`grid::GridItem`]
|
||||
/// vía `with_prop()`, exactamente igual que en cualquier otro contenedor. `GridItem` es una
|
||||
/// característica de [`PropsOp`], no exige ningún envoltorio propio.
|
||||
///
|
||||
/// Se aplica con clases CSS generadas dinámicamente (`display`, `grid-template-columns`,
|
||||
/// `grid-template-rows`, `grid-auto-columns`, `grid-auto-rows`, `grid-auto-flow`, `justify-items`,
|
||||
/// `align-items`, `justify-content`, `align-content`, `gap`, `row-gap`, `column-gap`), registradas
|
||||
/// vía [`AssetsOp::add_responsive_style()`] en [`ResponsiveStyles`] y renderizadas como reglas en
|
||||
/// el `<head>` del documento. Son propiedades nativas que no requieren interpretación por parte de
|
||||
/// los temas, siempre funcionan igual, sin una sola línea de CSS ni de código específico.
|
||||
///
|
||||
/// `align_items`/`align_content`/`gap` usan tipos de [`pagetop::html::align`] ([`align::Items`],
|
||||
/// [`align::Content`], [`align::Gap`]), compartidos con [`Flex`], mismo CSS y mismo catálogo de
|
||||
/// valores en los dos. Ver la documentación de [`align`](crate::html::align) para el criterio
|
||||
/// completo.
|
||||
///
|
||||
/// Si no contiene elementos, el componente **no se renderiza**.
|
||||
///
|
||||
/// [`AssetsOp::add_responsive_style()`]: crate::core::component::AssetsOp::add_responsive_style
|
||||
/// [`pagetop::html::align`]: crate::html::align
|
||||
/// [`grid::GridItem`]: crate::html::grid::GridItem
|
||||
/// [`ResponsiveStyles`]: crate::html::ResponsiveStyles
|
||||
/// [`Flex`]: crate::base::component::Flex
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let cards = Grid::new()
|
||||
/// .with_columns(grid::Tracks::repeat(3, grid::AxisTrack::Fraction(1.0)))
|
||||
/// .with_gap(align::Gap::Both(UnitValue::RelRem(1.0)))
|
||||
/// .with_child(Container::new())
|
||||
/// .with_child(Container::new())
|
||||
/// .with_child(Container::new());
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Debug, Getters)]
|
||||
pub struct Grid {
|
||||
/// Devuelve identificador, clases CSS, atributos HTML y valores extra del componente.
|
||||
props: Props,
|
||||
// Determina si esta configuración debe aplicarse (y con qué variante de `display`) o si `Grid`
|
||||
// no está en absoluto configurado. `None` es el estado real de ausencia: lo que tiene un
|
||||
// componente que nunca ha llamado a ninguno de sus constructores explícitos. Sin getter
|
||||
// público; `new()`, `at()`, `inline()` e `inline_at()` son la única forma de activarlo.
|
||||
#[getters(skip)]
|
||||
display: Option<DisplayGrid>,
|
||||
/// Devuelve las pistas de columna (`grid-template-columns`), por punto de corte.
|
||||
#[getters(copy)]
|
||||
columns: Responsive<Tracks>,
|
||||
/// Devuelve las pistas de fila (`grid-template-rows`), por punto de corte.
|
||||
#[getters(copy)]
|
||||
rows: Responsive<Tracks>,
|
||||
/// Devuelve el tamaño de las columnas generadas implícitamente (`grid-auto-columns`), por
|
||||
/// punto de corte.
|
||||
#[getters(copy)]
|
||||
auto_columns: Responsive<AxisTrack>,
|
||||
/// Devuelve el tamaño de las filas generadas implícitamente (`grid-auto-rows`), por punto de
|
||||
/// corte.
|
||||
#[getters(copy)]
|
||||
auto_rows: Responsive<AxisTrack>,
|
||||
/// Devuelve el algoritmo de colocación automática, por punto de corte.
|
||||
#[getters(copy)]
|
||||
auto_flow: Responsive<AutoFlow>,
|
||||
/// Devuelve la alineación de los elementos en el eje de columnas, por punto de corte.
|
||||
#[getters(copy)]
|
||||
justify_items: Responsive<DefaultJustify>,
|
||||
/// Devuelve la alineación de los elementos en el eje de filas, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align_items: Responsive<Items>,
|
||||
/// Devuelve la alineación de las pistas en el eje de columnas, por punto de corte.
|
||||
#[getters(copy)]
|
||||
justify_content: Responsive<ContentJustify>,
|
||||
/// Devuelve la alineación de las pistas en el eje de filas, por punto de corte.
|
||||
#[getters(copy)]
|
||||
align_content: Responsive<Content>,
|
||||
/// Devuelve el espaciado entre pistas, por punto de corte.
|
||||
#[getters(copy)]
|
||||
gap: Responsive<Gap>,
|
||||
/// Devuelve la lista de componentes (`children`) del contenedor.
|
||||
children: Children,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Component for Grid {
|
||||
fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
fn id(&self) -> Option<String> {
|
||||
self.props.get_id()
|
||||
}
|
||||
|
||||
async fn prepare(&self, cx: &mut Context) -> Result<Markup, ComponentError> {
|
||||
let output = self.children().render(cx).await;
|
||||
if output.is_empty() {
|
||||
return Ok(html! {});
|
||||
}
|
||||
let mut classes = String::new();
|
||||
self.grid_classes(cx, &mut classes);
|
||||
let container_props = self.props().unpack_with_classes(cx, classes);
|
||||
Ok(html! { div (container_props) { (output) } })
|
||||
}
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Grid {
|
||||
/// Define una configuración con `display: grid`, sin punto de corte: se aplica siempre.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayGrid::Always),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: grid` que se aplica a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
display: Some(DisplayGrid::At(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: inline-grid`, sin punto de corte: se aplica
|
||||
/// siempre.
|
||||
pub fn inline() -> Self {
|
||||
Self {
|
||||
display: Some(DisplayGrid::AlwaysInline),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
/// Define una configuración con `display: inline-grid` que se aplica a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn inline_at(bp: Breakpoint) -> Self {
|
||||
Self {
|
||||
display: Some(DisplayGrid::InlineAt(bp)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
// **< Grid BUILDER >***************************************************************************
|
||||
|
||||
/// Establece el identificador único del componente; igual a `with_prop(PropsOp::set_id(id))`.
|
||||
pub fn with_id(mut self, id: impl Into<CowStr>) -> Self {
|
||||
self.props.alter_id(id);
|
||||
self
|
||||
}
|
||||
|
||||
/// Modifica identificador, clases CSS, atributos HTML o valores extra del componente.
|
||||
pub fn with_prop(mut self, op: impl Into<PropsOp>) -> Self {
|
||||
self.props.alter_prop(op);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece las pistas de columna.
|
||||
pub fn with_columns(mut self, tracks: Tracks) -> Self {
|
||||
self.columns = self.columns.set(tracks);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece las pistas de columna a partir del punto de corte indicado.
|
||||
pub fn with_columns_at(mut self, bp: Breakpoint, tracks: Tracks) -> Self {
|
||||
self.columns = self.columns.set_at(bp, tracks);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece las pistas de fila.
|
||||
pub fn with_rows(mut self, tracks: Tracks) -> Self {
|
||||
self.rows = self.rows.set(tracks);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece las pistas de fila a partir del punto de corte indicado.
|
||||
pub fn with_rows_at(mut self, bp: Breakpoint, tracks: Tracks) -> Self {
|
||||
self.rows = self.rows.set_at(bp, tracks);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño de las columnas generadas implícitamente por la colocación automática.
|
||||
pub fn with_auto_columns(mut self, track: AxisTrack) -> Self {
|
||||
self.auto_columns = self.auto_columns.set(track);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño de las columnas generadas implícitamente, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_auto_columns_at(mut self, bp: Breakpoint, track: AxisTrack) -> Self {
|
||||
self.auto_columns = self.auto_columns.set_at(bp, track);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño de las filas generadas implícitamente por la colocación automática.
|
||||
pub fn with_auto_rows(mut self, track: AxisTrack) -> Self {
|
||||
self.auto_rows = self.auto_rows.set(track);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el tamaño de las filas generadas implícitamente, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_auto_rows_at(mut self, bp: Breakpoint, track: AxisTrack) -> Self {
|
||||
self.auto_rows = self.auto_rows.set_at(bp, track);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el algoritmo de colocación automática.
|
||||
pub fn with_auto_flow(mut self, auto_flow: AutoFlow) -> Self {
|
||||
self.auto_flow = self.auto_flow.set(auto_flow);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el algoritmo de colocación automática, a partir del punto de corte indicado.
|
||||
pub fn with_auto_flow_at(mut self, bp: Breakpoint, auto_flow: AutoFlow) -> Self {
|
||||
self.auto_flow = self.auto_flow.set_at(bp, auto_flow);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje de columnas.
|
||||
pub fn with_justify_items(mut self, justify_items: DefaultJustify) -> Self {
|
||||
self.justify_items = self.justify_items.set(justify_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje de columnas, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_justify_items_at(mut self, bp: Breakpoint, justify_items: DefaultJustify) -> Self {
|
||||
self.justify_items = self.justify_items.set_at(bp, justify_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje de filas.
|
||||
pub fn with_align_items(mut self, align_items: Items) -> Self {
|
||||
self.align_items = self.align_items.set(align_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de los elementos en el eje de filas, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_align_items_at(mut self, bp: Breakpoint, align_items: Items) -> Self {
|
||||
self.align_items = self.align_items.set_at(bp, align_items);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las pistas en el eje de columnas (ver [`ContentJustify`]).
|
||||
pub fn with_justify_content(mut self, justify_content: ContentJustify) -> Self {
|
||||
self.justify_content = self.justify_content.set(justify_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las pistas en el eje de columnas, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_justify_content_at(
|
||||
mut self,
|
||||
bp: Breakpoint,
|
||||
justify_content: ContentJustify,
|
||||
) -> Self {
|
||||
self.justify_content = self.justify_content.set_at(bp, justify_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las pistas en el eje de filas (ver
|
||||
/// [`Content`](crate::html::align::Content)).
|
||||
pub fn with_align_content(mut self, align_content: Content) -> Self {
|
||||
self.align_content = self.align_content.set(align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece la alineación de las pistas en el eje de filas, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_align_content_at(mut self, bp: Breakpoint, align_content: Content) -> Self {
|
||||
self.align_content = self.align_content.set_at(bp, align_content);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre pistas.
|
||||
pub fn with_gap(mut self, gap: Gap) -> Self {
|
||||
self.gap = self.gap.set(gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el espaciado entre pistas, a partir del punto de corte indicado.
|
||||
pub fn with_gap_at(mut self, bp: Breakpoint, gap: Gap) -> Self {
|
||||
self.gap = self.gap.set_at(bp, gap);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo componente al contenedor o modifica la lista de componentes (`children`) con
|
||||
/// una operación [`ChildOp`].
|
||||
pub fn with_child(mut self, op: impl Into<ChildOp>) -> Self {
|
||||
self.children.alter_child(op.into());
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Grid {
|
||||
// Calcula las clases CSS *responsive* de este contenedor Grid y las añade a `classes`,
|
||||
// separadas con un espacio de las que ya hubiera, para poder compartir un único acumulador con
|
||||
// `GridItem::apply()` (ver `Props::unpack_with_classes()`) sin cadenas intermedias.
|
||||
#[rustfmt::skip]
|
||||
fn grid_classes(&self, cx: &mut Context, classes: &mut String) {
|
||||
// Sin `display` no hay contenedor Grid: el resto de propiedades (`grid-template-columns`,
|
||||
// `gap`, etc.) no tienen ningún efecto en CSS sin `display: grid`/`inline-grid`, así que ni
|
||||
// se generan. Éste es el estado de un `Grid` que nunca ha llamado a ninguno de sus
|
||||
// constructores explícitos.
|
||||
let Some(display) = self.display else {
|
||||
return;
|
||||
};
|
||||
|
||||
use crate::html::responsive::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
let (prefix, value) = match display {
|
||||
DisplayGrid::Always
|
||||
| DisplayGrid::At(_) => ("_grid_", "grid"),
|
||||
DisplayGrid::AlwaysInline
|
||||
| DisplayGrid::InlineAt(_) => ("_inline-grid_", "inline-grid"),
|
||||
};
|
||||
let entry = match display {
|
||||
DisplayGrid::At(bp) | DisplayGrid::InlineAt(bp) => bp.resolved(cx),
|
||||
_ => None,
|
||||
};
|
||||
let class = match entry {
|
||||
None => prefix.into(),
|
||||
Some(entry) => util::join!(prefix, entry.name, "_").into(),
|
||||
};
|
||||
styles(cx, classes, entry, class, "display", value.into());
|
||||
|
||||
apply!(cx, classes, self.columns, "_grid-columns_", "grid-template-columns", val);
|
||||
apply!(cx, classes, self.rows, "_grid-rows_", "grid-template-rows", val);
|
||||
apply!(cx, classes, self.auto_columns, "_grid-auto-columns_", "grid-auto-columns", val);
|
||||
apply!(cx, classes, self.auto_rows, "_grid-auto-rows_", "grid-auto-rows", val);
|
||||
// `val`: a diferencia de `flex::Behavior` (una sola palabra en cualquier variante),
|
||||
// `AutoFlow::RowDense`/`ColumnDense` valen "row dense"/"column dense" (con espacio), así
|
||||
// que necesitan pasar por `value_to_token()` igual que `Tracks`/`ItemPlacement`.
|
||||
apply!(cx, classes, self.auto_flow, "_grid-auto-flow_", "grid-auto-flow", val);
|
||||
apply!(cx, classes, self.justify_items, "_grid-justify-items_", "justify-items");
|
||||
apply!(cx, classes, self.align_items, "_grid-align-items_", "align-items");
|
||||
apply!(cx, classes, self.justify_content, "_grid-justify-content_", "justify-content");
|
||||
apply!(cx, classes, self.align_content, "_grid-align-content_", "align-content");
|
||||
for (bp, gap) in self.gap.by_breakpoint() {
|
||||
let entry = bp.resolved(cx);
|
||||
for (property, value) in gap.styles().into_iter().flatten() {
|
||||
// El prefijo de `gap` no es literal (depende de la propiedad), así que se compone
|
||||
// aquí en un único `join!` en vez de pasar por `responsive_class!`.
|
||||
let token = value_to_token(&value);
|
||||
let class = match entry {
|
||||
None => util::join!("_grid-", property, "_", token, "_"),
|
||||
Some(e) => util::join!("_grid-", property, "_", token, "_", e.name, "_"),
|
||||
};
|
||||
styles(cx, classes, entry, class.into(), property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ use crate::prelude::*;
|
|||
/// ));
|
||||
/// ```
|
||||
///
|
||||
/// Barra con **botón de despliegue** y **marca de identidad**, en ese orden:
|
||||
/// Barra con **botón de despliegue**, contenido y **marca de identidad**:
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// # use pagetop::prelude::*;
|
||||
|
|
@ -81,9 +81,6 @@ pub struct Navbar {
|
|||
props: Props,
|
||||
/// Devuelve la disposición configurada para la barra de navegación.
|
||||
layout: navbar::Layout,
|
||||
/// Devuelve el posicionamiento Flexbox como contenedor, si tiene alguno.
|
||||
#[getters(copy)]
|
||||
flex: Flex,
|
||||
/// Devuelve la lista de contenidos.
|
||||
items: Children,
|
||||
}
|
||||
|
|
@ -99,8 +96,10 @@ impl Component for Navbar {
|
|||
}
|
||||
|
||||
fn setup(&mut self, cx: &mut Context) {
|
||||
// Asegura que la barra de navegación tiene un identificador único: lo necesita el botón de
|
||||
// despliegue para referenciar el contenido colapsable con `aria-controls`.
|
||||
// Asegura que la barra de navegación tiene un identificador único: siempre se usa para
|
||||
// derivar el `id` del área de contenido (`id_content`, ver `prepare()`); además, el botón
|
||||
// de despliegue lo necesita para referenciar ese contenido colapsable con `aria-controls`
|
||||
// si el *layout* lo incluye.
|
||||
self.alter_prop(PropsOp::ensure_id(cx.build_id::<Self>(1)));
|
||||
self.alter_prop(PropsOp::prepend_classes("navbar"));
|
||||
}
|
||||
|
|
@ -124,12 +123,9 @@ impl Component for Navbar {
|
|||
aria-expanded="false"
|
||||
aria-controls=(&id_content)
|
||||
aria-label=[Lc::l("navbar_toggle").lookup(cx)]
|
||||
{
|
||||
span class="navbar-toggle-icon" {}
|
||||
}
|
||||
{}
|
||||
};
|
||||
|
||||
// Posicionamiento Flexbox opcional (no del `<nav>`, cuya estructura la fija `layout()`).
|
||||
let content_props = Props::classes("navbar-content").with_id(id_content);
|
||||
|
||||
Ok(html! {
|
||||
|
|
@ -137,32 +133,32 @@ impl Component for Navbar {
|
|||
@match self.layout() {
|
||||
// Barra más sencilla: sólo contenido, siempre visible.
|
||||
navbar::Layout::Simple => {
|
||||
div (content_props.unpack_with_flex(cx, self.flex())) { (items) }
|
||||
div (content_props.unpack(cx)) { (items) }
|
||||
},
|
||||
|
||||
// Barra sencilla que se puede contraer/expandir.
|
||||
navbar::Layout::SimpleToggle => {
|
||||
(button)
|
||||
div (content_props.unpack_with_flex(cx, self.flex())) { (items) }
|
||||
div (content_props.unpack(cx)) { (items) }
|
||||
},
|
||||
|
||||
// Barra con marca, siempre visible, sin botón.
|
||||
navbar::Layout::SimpleBrandLeft(brand) => {
|
||||
(brand.render(cx).await)
|
||||
div (content_props.unpack_with_flex(cx, self.flex())) { (items) }
|
||||
div (content_props.unpack(cx)) { (items) }
|
||||
},
|
||||
|
||||
// Barra con marca y botón, en ese orden.
|
||||
navbar::Layout::BrandLeft(brand) => {
|
||||
(brand.render(cx).await)
|
||||
(button)
|
||||
div (content_props.unpack_with_flex(cx, self.flex())) { (items) }
|
||||
div (content_props.unpack(cx)) { (items) }
|
||||
},
|
||||
|
||||
// Barra con botón y marca, en ese orden.
|
||||
// Barra con botón, contenido y marca (en ese orden).
|
||||
navbar::Layout::BrandRight(brand) => {
|
||||
(button)
|
||||
div (content_props.unpack_with_flex(cx, self.flex())) { (items) }
|
||||
div (content_props.unpack(cx)) { (items) }
|
||||
(brand.render(cx).await)
|
||||
},
|
||||
}
|
||||
|
|
@ -194,8 +190,10 @@ impl Navbar {
|
|||
Self::default().with_layout(navbar::Layout::BrandLeft(Embed::with(brand)))
|
||||
}
|
||||
|
||||
/// Crea una barra de navegación con **botón de despliegue** y **marca de identidad**, en ese
|
||||
/// orden.
|
||||
/// Crea una barra de navegación con **botón de despliegue**, el contenido y, en último lugar,
|
||||
/// la **marca de identidad**. A diferencia de [`brand_left()`](Self::brand_left), aquí no queda
|
||||
/// junto al botón, sino que se renderiza tras el contenido, para acabar en el extremo derecho
|
||||
/// de la barra (el botón sólo es visible en viewports estrechos).
|
||||
pub fn brand_right(brand: Brand) -> Self {
|
||||
Self::default().with_layout(navbar::Layout::BrandRight(Embed::with(brand)))
|
||||
}
|
||||
|
|
@ -220,15 +218,6 @@ impl Navbar {
|
|||
self
|
||||
}
|
||||
|
||||
/// Establece el posicionamiento Flexbox como contenedor (usa `None` para quitarlo).
|
||||
///
|
||||
/// No afecta a la posición de la marca ni del botón de despliegue, que quedan fijados con
|
||||
/// [`with_layout()`](Self::with_layout).
|
||||
pub fn with_flex(mut self, flex: impl Into<Option<Flex>>) -> Self {
|
||||
self.flex = self.flex.merge(flex);
|
||||
self
|
||||
}
|
||||
|
||||
/// Añade un nuevo contenido a la barra de navegación o modifica la lista de contenidos de la
|
||||
/// barra con una operación [`TypedOp`].
|
||||
///
|
||||
|
|
|
|||
|
|
@ -451,9 +451,9 @@ impl Pager {
|
|||
// páginas ya cabe sin necesidad de truncar (ver más abajo), devuelve todas las páginas sin
|
||||
// elipsis. En caso contrario, siempre incluye la primera y la última página, la ventana de
|
||||
// páginas alrededor de la actual (`window` antes y después, recortada a los límites del
|
||||
// listado), y sustituye por una elipsis cualquier tramo oculto de dos o más páginas -- un
|
||||
// tramo de una sola página se muestra directamente, ya que ocultarla tras una elipsis no
|
||||
// ahorra espacio.
|
||||
// listado), y sustituye por una elipsis cualquier tramo oculto de dos o más páginas. Un tramo
|
||||
// de una sola página se muestra directamente, ya que ocultarla tras una elipsis no ahorra
|
||||
// espacio.
|
||||
fn page_items(&self, page: u64, total_pages: u64) -> Vec<PageItem> {
|
||||
// Acotado a `total_pages`: una ventana mayor no aporta nada (ya se mostrarían todas las
|
||||
// páginas) y evita operar con un valor arbitrariamente grande más abajo.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue