✨ (pagetop): Añade Margin y Padding nativos
- Nuevo módulo `html::spacing` con `Margin`/`Padding`: igual que `FlexItem`, se resuelven generando clases CSS dinámicamente y se aplican con `PropsOp::margin()`/`PropsOp::padding()` sobre cualquier componente. - Elimina `Margin`/`Padding` de `pagetop-bootsier`. - Añade el ejemplo `intro-spacing` con los patrones de uso típicos.
This commit is contained in:
parent
64113aff09
commit
50e4b42fe4
17 changed files with 881 additions and 322 deletions
255
examples/intro-spacing.rs
Normal file
255
examples/intro-spacing.rs
Normal file
|
|
@ -0,0 +1,255 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
include_locales!(LOC from "examples/locale");
|
||||
|
||||
struct IntroSpacing;
|
||||
|
||||
#[async_trait]
|
||||
impl Extension for IntroSpacing {
|
||||
fn dependencies(&self) -> Vec<ExtensionRef> {
|
||||
vec![&pagetop_bootsier::Bootsier]
|
||||
}
|
||||
|
||||
fn configure_router(&self, router: Router) -> Router {
|
||||
router.route("/", web::get(intro_spacing))
|
||||
}
|
||||
}
|
||||
|
||||
async fn intro_spacing(request: HttpRequest) -> Result<Markup, ErrorPage> {
|
||||
Page::new(request)
|
||||
.with_assets(demo_box_styles())
|
||||
.with_assets(demo_row_styles())
|
||||
.with_assets(demo_code_styles())
|
||||
.with_child(
|
||||
Intro::default()
|
||||
.with_opening(IntroOpening::Custom)
|
||||
.with_title(Lc::n("PageTop"))
|
||||
.with_slogan(Lc::t("spacing_slogan", &LOC))
|
||||
.with_button(None::<(Lc, Route)>)
|
||||
.with_child(Html::with(|cx| {
|
||||
html! {
|
||||
p class="intro-text-lead" {
|
||||
(Lc::t("spacing_note", &LOC).using(cx))
|
||||
}
|
||||
}
|
||||
}))
|
||||
.with_child(padding_block())
|
||||
.with_child(layout_block())
|
||||
.with_child(responsive_block())
|
||||
.with_child(combined_block()),
|
||||
)
|
||||
.render()
|
||||
.await
|
||||
}
|
||||
|
||||
fn padding_block() -> Block {
|
||||
let mut block = Block::new().with_title(Lc::t("spacing_block_title_padding", &LOC));
|
||||
|
||||
let padding_variants: [(&str, UnitValue, &str); 3] = [
|
||||
(
|
||||
"spacing_title_uniform_small",
|
||||
UnitValue::RelRem(0.5),
|
||||
"Padding::new().with_all(UnitValue::RelRem(0.5))",
|
||||
),
|
||||
(
|
||||
"spacing_title_uniform_medium",
|
||||
UnitValue::RelRem(1.0),
|
||||
"Padding::new().with_all(UnitValue::RelRem(1.0))",
|
||||
),
|
||||
(
|
||||
"spacing_title_uniform_large",
|
||||
UnitValue::RelRem(2.0),
|
||||
"Padding::new().with_all(UnitValue::RelRem(2.0))",
|
||||
),
|
||||
];
|
||||
for (title_key, size, code) in padding_variants {
|
||||
block = block
|
||||
.with_child(caption(Lc::t(title_key, &LOC), Lc::n(code)))
|
||||
.with_child(
|
||||
demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))).with_child(
|
||||
demo_box(box_sample()).with_prop(Padding::new().with_all(size).into()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
block
|
||||
.with_child(caption(
|
||||
Lc::t("spacing_title_sides", &LOC),
|
||||
Lc::n(concat!(
|
||||
"Padding::new()",
|
||||
".with_top(UnitValue::RelRem(0.25))",
|
||||
".with_end(UnitValue::RelRem(2.5))",
|
||||
".with_bottom(UnitValue::RelRem(1.5))",
|
||||
".with_start(UnitValue::RelRem(0.5))",
|
||||
)),
|
||||
))
|
||||
.with_child(
|
||||
demo_row(Flex::new()).with_child(
|
||||
demo_box(box_sample()).with_prop(
|
||||
Padding::new()
|
||||
.with_top(UnitValue::RelRem(0.25))
|
||||
.with_end(UnitValue::RelRem(2.5))
|
||||
.with_bottom(UnitValue::RelRem(1.5))
|
||||
.with_start(UnitValue::RelRem(0.5))
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn layout_block() -> Block {
|
||||
Block::new()
|
||||
.with_title(Lc::t("spacing_block_title_layout", &LOC))
|
||||
.with_child(caption(
|
||||
Lc::t("spacing_title_margin_gap", &LOC),
|
||||
Lc::n("Margin::new().with_x(UnitValue::RelRem(1.0))"),
|
||||
))
|
||||
.with_child(
|
||||
demo_row(Flex::new())
|
||||
.with_child(demo_box(box_sample()))
|
||||
.with_child(
|
||||
demo_box(box_sample())
|
||||
.with_prop(Margin::new().with_x(UnitValue::RelRem(1.0)).into()),
|
||||
)
|
||||
.with_child(demo_box(box_sample())),
|
||||
)
|
||||
.with_child(caption(
|
||||
Lc::t("spacing_title_margin_center", &LOC),
|
||||
Lc::n("Margin::new().with_x(UnitValue::Auto)"),
|
||||
))
|
||||
.with_child(
|
||||
demo_row(Flex::new()).with_child(
|
||||
demo_box(box_sample())
|
||||
.with_prop(Margin::new().with_x(UnitValue::Auto).into())
|
||||
.with_prop(PropsOp::flex_item(
|
||||
FlexItem::new().with_size(flex::ItemSize::Custom(UnitValue::RelRem(8.0))),
|
||||
)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn responsive_block() -> Block {
|
||||
Block::new()
|
||||
.with_title(Lc::t("spacing_block_title_responsive", &LOC))
|
||||
.with_child(caption(
|
||||
Lc::t("spacing_title_responsive", &LOC),
|
||||
Lc::n(concat!(
|
||||
"Padding::new()",
|
||||
".with_all(UnitValue::RelRem(0.5))",
|
||||
".with_all_at(Breakpoint::Md, UnitValue::RelRem(2.0))",
|
||||
".with_all_at(Breakpoint::Lg, UnitValue::RelRem(4.0))",
|
||||
)),
|
||||
))
|
||||
.with_child(
|
||||
demo_row(Flex::new()).with_child(
|
||||
demo_box(box_sample()).with_prop(
|
||||
Padding::new()
|
||||
.with_all(UnitValue::RelRem(0.5))
|
||||
.with_all_at(Breakpoint::Md, UnitValue::RelRem(2.0))
|
||||
.with_all_at(Breakpoint::Lg, UnitValue::RelRem(4.0))
|
||||
.into(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn combined_block() -> Block {
|
||||
Block::new()
|
||||
.with_title(Lc::t("spacing_block_title_combined", &LOC))
|
||||
.with_child(caption(
|
||||
Lc::t("spacing_title_combined", &LOC),
|
||||
Lc::n(concat!(
|
||||
"Container::new()",
|
||||
".with_prop(Margin::new().with_y(UnitValue::RelRem(1.0)).into())",
|
||||
".with_prop(Padding::new().with_all(UnitValue::RelRem(1.5)).into())",
|
||||
)),
|
||||
))
|
||||
.with_child(
|
||||
demo_row(Flex::new()).with_child(
|
||||
Container::new()
|
||||
.with_prop(PropsOp::add_classes("spacing-demo-box"))
|
||||
.with_prop(Margin::new().with_y(UnitValue::RelRem(1.0)).into())
|
||||
.with_prop(Padding::new().with_all(UnitValue::RelRem(1.5)).into())
|
||||
.with_child(
|
||||
Button::plain(Lc::t("spacing_box_card_button", &LOC))
|
||||
.with_style(button::Style::Solid(Intent::Warning)),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// **< HELPERS >************************************************************************************
|
||||
|
||||
// Caja de muestra sin relleno ni margen propios, para que sólo se vea el efecto de `Margin`/
|
||||
// `Padding` aplicado en cada demostración.
|
||||
fn demo_box_styles() -> AssetsOp {
|
||||
AssetsOp::add_responsive_styles(
|
||||
None,
|
||||
"spacing-demo-box",
|
||||
[
|
||||
("background-color", "#0d6efd"),
|
||||
("color", "#fff"),
|
||||
("min-width", "3rem"),
|
||||
("width", "auto"),
|
||||
("max-width", "none"),
|
||||
("margin", "0"),
|
||||
("padding", "0"),
|
||||
("border-radius", "0.375rem"),
|
||||
("text-align", "center"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
// Aspecto fijo de las filas de muestra.
|
||||
fn demo_row_styles() -> AssetsOp {
|
||||
AssetsOp::add_responsive_styles(
|
||||
None,
|
||||
"spacing-demo-row",
|
||||
[
|
||||
("background-color", "#f1f3f5"),
|
||||
("width", "100%"),
|
||||
("max-width", "none"),
|
||||
("margin", "0 0 1.5rem"),
|
||||
("padding", "0.75rem"),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
// Evita que los fragmentos de código largos desborden su contenedor.
|
||||
fn demo_code_styles() -> AssetsOp {
|
||||
AssetsOp::add_responsive_styles(None, "spacing-demo-code", [("overflow-wrap", "anywhere")])
|
||||
}
|
||||
|
||||
// Caja azul de muestra, sin relleno ni margen propios.
|
||||
fn demo_box(label: Lc) -> Container {
|
||||
Container::new()
|
||||
.with_prop(PropsOp::add_classes("spacing-demo-box"))
|
||||
.with_child(Html::with(move |cx| html! { (label.using(cx)) }))
|
||||
}
|
||||
|
||||
// Etiqueta genérica reutilizada en la mayoría de cajas de muestra.
|
||||
fn box_sample() -> Lc {
|
||||
Lc::t("spacing_box_sample", &LOC)
|
||||
}
|
||||
|
||||
// Fila de demostración con fondo gris para visualizar los límites de cada caja.
|
||||
fn demo_row(flex: Flex) -> Container {
|
||||
Container::new()
|
||||
.with_prop(PropsOp::add_classes("spacing-demo-row"))
|
||||
.with_flex(flex)
|
||||
}
|
||||
|
||||
// Título y fragmento de código que introducen cada demostración.
|
||||
fn caption(title: Lc, code: Lc) -> Html {
|
||||
Html::with(move |cx| {
|
||||
html! {
|
||||
h3 { (title.using(cx)) }
|
||||
p { code class="spacing-demo-code" { (code.using(cx)) } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[pagetop::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
Application::prepare(&IntroSpacing).await.run().await
|
||||
}
|
||||
19
examples/locale/en-US/intro-spacing.ftl
Normal file
19
examples/locale/en-US/intro-spacing.ftl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
spacing_slogan = Native margin and padding with Margin and Padding
|
||||
spacing_note = Margin and Padding have nothing to do with Flexbox: they apply to any component, whether its container uses Flexbox or not. They are combined with Flex here only to lay out the sample boxes in a row.
|
||||
|
||||
spacing_block_title_padding = Padding
|
||||
spacing_block_title_layout = Layout
|
||||
spacing_block_title_responsive = Padding at a breakpoint
|
||||
spacing_block_title_combined = Margin and padding combined
|
||||
|
||||
spacing_title_uniform_small = Small padding on all four sides
|
||||
spacing_title_uniform_medium = Medium padding on all four sides
|
||||
spacing_title_uniform_large = Large padding on all four sides
|
||||
spacing_title_sides = A different value per side, including the logical start/end sides
|
||||
spacing_title_margin_gap = The middle box's margin pushes its neighbours away, without using Gap
|
||||
spacing_title_margin_center = An automatic margin on both sides centers the box in the free space
|
||||
spacing_title_responsive = Padding grows from md onwards, and again from lg
|
||||
spacing_title_combined = Outer margin above/below and inner padding on all four sides, on a component with real content
|
||||
|
||||
spacing_box_sample = Content
|
||||
spacing_box_card_button = Accept
|
||||
19
examples/locale/es-ES/intro-spacing.ftl
Normal file
19
examples/locale/es-ES/intro-spacing.ftl
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
spacing_slogan = Márgenes y relleno nativos con Margin y Padding
|
||||
spacing_note = Margin y Padding no tienen relación con Flexbox: se aplican sobre cualquier componente, use o no Flexbox su contenedor. Aquí se combinan con Flex sólo para colocar las cajas de muestra en fila.
|
||||
|
||||
spacing_block_title_padding = Rellenos
|
||||
spacing_block_title_layout = Composición
|
||||
spacing_block_title_responsive = Relleno por punto de corte
|
||||
spacing_block_title_combined = Margin y padding combinados
|
||||
|
||||
spacing_title_uniform_small = Relleno pequeño en los cuatro lados
|
||||
spacing_title_uniform_medium = Relleno medio en los cuatro lados
|
||||
spacing_title_uniform_large = Relleno grande en los cuatro lados
|
||||
spacing_title_sides = Un valor distinto para cada lado, incluidos los lados lógicos de inicio y fin
|
||||
spacing_title_margin_gap = El margen de la caja central empuja a sus vecinas, sin usar Gap
|
||||
spacing_title_margin_center = Un margen automático en ambos lados centra la caja en el espacio libre
|
||||
spacing_title_responsive = El relleno crece a partir de md, y de nuevo a partir de lg
|
||||
spacing_title_combined = Margen exterior arriba/abajo y relleno interno por los cuatro lados, sobre un componente con contenido real
|
||||
|
||||
spacing_box_sample = Contenido
|
||||
spacing_box_card_button = Aceptar
|
||||
|
|
@ -21,6 +21,3 @@ pub use border::{Border, BorderColor};
|
|||
|
||||
mod rounded;
|
||||
pub use rounded::{Rounded, RoundedRadius};
|
||||
|
||||
mod layout;
|
||||
pub use layout::{Margin, Padding};
|
||||
|
|
|
|||
|
|
@ -1,211 +0,0 @@
|
|||
use pagetop::prelude::*;
|
||||
|
||||
use crate::theme::{BoxSide, BreakPoint, ScaleSize};
|
||||
|
||||
// **< Margin >*************************************************************************************
|
||||
|
||||
/// Clases para establecer **margin** por lado, tamaño y punto de ruptura.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop_bootsier::theme::*;
|
||||
///
|
||||
/// let m = class::Margin::with(BoxSide::Top, ScaleSize::Three);
|
||||
/// assert_eq!(m.to_class(), "mt-3");
|
||||
///
|
||||
/// let m = class::Margin::with(BoxSide::Start, ScaleSize::Auto)
|
||||
/// .with_breakpoint(BreakPoint::LG);
|
||||
/// assert_eq!(m.to_class(), "ms-lg-auto");
|
||||
///
|
||||
/// let m = class::Margin::with(BoxSide::All, ScaleSize::None);
|
||||
/// assert_eq!(m.to_class(), "");
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Margin {
|
||||
side: BoxSide,
|
||||
size: ScaleSize,
|
||||
breakpoint: BreakPoint,
|
||||
}
|
||||
|
||||
impl Margin {
|
||||
/// Crea un **margin** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||
/// ruptura.
|
||||
pub fn with(side: BoxSide, size: ScaleSize) -> Self {
|
||||
Margin {
|
||||
side,
|
||||
size,
|
||||
breakpoint: BreakPoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
// **< Margin BUILDER >*************************************************************************
|
||||
|
||||
/// Establece el punto de ruptura a partir del cual se empieza a aplicar el **margin**.
|
||||
pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self {
|
||||
self.breakpoint = breakpoint;
|
||||
self
|
||||
}
|
||||
|
||||
// **< Margin HELPERS >*************************************************************************
|
||||
|
||||
// Devuelve el prefijo `m*` según el lado.
|
||||
#[rustfmt::skip]
|
||||
#[inline]
|
||||
const fn side_prefix(&self) -> &'static str {
|
||||
match self.side {
|
||||
BoxSide::All => "m",
|
||||
BoxSide::Top => "mt",
|
||||
BoxSide::Bottom => "mb",
|
||||
BoxSide::Start => "ms",
|
||||
BoxSide::End => "me",
|
||||
BoxSide::LeftAndRight => "mx",
|
||||
BoxSide::TopAndBottom => "my",
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve el sufijo del tamaño (`auto`, `0`..`5`), o `None` si no define clase.
|
||||
#[rustfmt::skip]
|
||||
#[inline]
|
||||
const fn size_suffix(&self) -> Option<&'static str> {
|
||||
match self.size {
|
||||
ScaleSize::None => None,
|
||||
ScaleSize::Auto => Some("auto"),
|
||||
ScaleSize::Zero => Some("0"),
|
||||
ScaleSize::One => Some("1"),
|
||||
ScaleSize::Two => Some("2"),
|
||||
ScaleSize::Three => Some("3"),
|
||||
ScaleSize::Four => Some("4"),
|
||||
ScaleSize::Five => Some("5"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Añade la clase de **margin** a la cadena de clases.
|
||||
pub fn push_to(self, classes: &mut String) {
|
||||
if let Some(size) = self.size_suffix() {
|
||||
let side = self.side_prefix();
|
||||
self.breakpoint.push_to(classes, side, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Devuelve la clase de *margin* como cadena (`"mt-3"`, `"ms-lg-auto"`, etc.).
|
||||
///
|
||||
/// Si `size` es `ScaleSize::None`, devuelve `""`.
|
||||
pub fn to_class(self) -> String {
|
||||
let mut class = String::new();
|
||||
self.push_to(&mut class);
|
||||
class
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Margin> for CowStr {
|
||||
/// Permite pasar [`Margin`] directamente a [`PropsOp`].
|
||||
fn from(val: Margin) -> Self {
|
||||
val.to_class().into()
|
||||
}
|
||||
}
|
||||
|
||||
// **< Padding >************************************************************************************
|
||||
|
||||
/// Clases para establecer **padding** por lado, tamaño y punto de ruptura.
|
||||
///
|
||||
/// # Ejemplos
|
||||
///
|
||||
/// ```rust
|
||||
/// use pagetop_bootsier::theme::*;
|
||||
///
|
||||
/// let p = class::Padding::with(BoxSide::LeftAndRight, ScaleSize::Two);
|
||||
/// assert_eq!(p.to_class(), "px-2");
|
||||
///
|
||||
/// let p = class::Padding::with(BoxSide::End, ScaleSize::Four)
|
||||
/// .with_breakpoint(BreakPoint::SM);
|
||||
/// assert_eq!(p.to_class(), "pe-sm-4");
|
||||
///
|
||||
/// let p = class::Padding::with(BoxSide::All, ScaleSize::Auto);
|
||||
/// assert_eq!(p.to_class(), ""); // `Auto` no aplica a padding.
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Padding {
|
||||
side: BoxSide,
|
||||
size: ScaleSize,
|
||||
breakpoint: BreakPoint,
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
/// Crea un **padding** indicando lado(s) y tamaño. Por defecto no se aplica a ningún punto de
|
||||
/// ruptura.
|
||||
pub fn with(side: BoxSide, size: ScaleSize) -> Self {
|
||||
Padding {
|
||||
side,
|
||||
size,
|
||||
breakpoint: BreakPoint::None,
|
||||
}
|
||||
}
|
||||
|
||||
// **< Padding BUILDER >************************************************************************
|
||||
|
||||
/// Establece el punto de ruptura a partir del cual se empieza a aplicar el **padding**.
|
||||
pub fn with_breakpoint(mut self, breakpoint: BreakPoint) -> Self {
|
||||
self.breakpoint = breakpoint;
|
||||
self
|
||||
}
|
||||
|
||||
// **< Padding HELPERS >************************************************************************
|
||||
|
||||
// Devuelve el prefijo `p*` según el lado.
|
||||
#[rustfmt::skip]
|
||||
#[inline]
|
||||
const fn side_prefix(&self) -> &'static str {
|
||||
match self.side {
|
||||
BoxSide::All => "p",
|
||||
BoxSide::Top => "pt",
|
||||
BoxSide::Bottom => "pb",
|
||||
BoxSide::Start => "ps",
|
||||
BoxSide::End => "pe",
|
||||
BoxSide::LeftAndRight => "px",
|
||||
BoxSide::TopAndBottom => "py",
|
||||
}
|
||||
}
|
||||
|
||||
// Devuelve el sufijo del tamaño (`0`..`5`), o None si no define clase.
|
||||
//
|
||||
// Nota: `ScaleSize::Auto` **no aplica** a *padding* => devuelve `None`.
|
||||
#[rustfmt::skip]
|
||||
#[inline]
|
||||
const fn size_suffix(&self) -> Option<&'static str> {
|
||||
match self.size {
|
||||
ScaleSize::None => None,
|
||||
ScaleSize::Auto => None,
|
||||
ScaleSize::Zero => Some("0"),
|
||||
ScaleSize::One => Some("1"),
|
||||
ScaleSize::Two => Some("2"),
|
||||
ScaleSize::Three => Some("3"),
|
||||
ScaleSize::Four => Some("4"),
|
||||
ScaleSize::Five => Some("5"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Añade la clase de **padding** a la cadena de clases.
|
||||
pub fn push_to(self, classes: &mut String) {
|
||||
if let Some(size) = self.size_suffix() {
|
||||
let side = self.side_prefix();
|
||||
self.breakpoint.push_to(classes, side, size);
|
||||
}
|
||||
}
|
||||
|
||||
/// Devuelve la clase de *padding* como cadena (`"px-2"`, `"pe-sm-4"`, etc.).
|
||||
///
|
||||
/// Si `size` es `ScaleSize::None` o `ScaleSize::Auto`, devuelve `""`.
|
||||
pub fn to_class(self) -> String {
|
||||
let mut class = String::new();
|
||||
self.push_to(&mut class);
|
||||
class
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Padding> for CowStr {
|
||||
/// Permite pasar [`Padding`] directamente a [`PropsOp`].
|
||||
fn from(val: Padding) -> Self {
|
||||
val.to_class().into()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,12 +4,9 @@ use pagetop::prelude::*;
|
|||
|
||||
/// Escala discreta de tamaños para clases utilitarias.
|
||||
///
|
||||
/// Se usa como parámetro de tamaño para las clases de [`Border`], [`Margin`] y [`Padding`]. La
|
||||
/// variante `Auto` no aplica en `Padding`.
|
||||
/// Se usa como parámetro de tamaño para las clases de [`Border`].
|
||||
///
|
||||
/// [`Border`]: crate::theme::class::Border
|
||||
/// [`Margin`]: crate::theme::class::Margin
|
||||
/// [`Padding`]: crate::theme::class::Padding
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum ScaleSize {
|
||||
/// Sin tamaño (no define ninguna clase).
|
||||
|
|
@ -68,11 +65,9 @@ impl ScaleSize {
|
|||
|
||||
/// Lados sobre los que aplicar una clase utilitaria (respetando LTR/RTL).
|
||||
///
|
||||
/// Se usa como selector de lado para las clases de [`Border`], [`Margin`] y [`Padding`].
|
||||
/// Se usa como selector de lado para las clases de [`Border`].
|
||||
///
|
||||
/// [`Border`]: crate::theme::class::Border
|
||||
/// [`Margin`]: crate::theme::class::Margin
|
||||
/// [`Padding`]: crate::theme::class::Padding
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)]
|
||||
pub enum BoxSide {
|
||||
/// Todos los lados.
|
||||
|
|
|
|||
|
|
@ -37,6 +37,12 @@ pub use unit::UnitValue;
|
|||
|
||||
// **< HTML LAYOUT >********************************************************************************
|
||||
|
||||
mod responsive;
|
||||
|
||||
pub mod flex;
|
||||
#[doc(inline)]
|
||||
pub use flex::{Flex, FlexItem};
|
||||
|
||||
pub mod spacing;
|
||||
#[doc(inline)]
|
||||
pub use spacing::{Margin, Padding};
|
||||
|
|
|
|||
|
|
@ -26,10 +26,6 @@
|
|||
//! [`Container`]: crate::base::component::Container
|
||||
//! [`Navbar`]: crate::base::component::Navbar
|
||||
|
||||
use crate::CowStr;
|
||||
use crate::core::component::{AssetsOp, Context, Contextual};
|
||||
use crate::core::theme::BreakpointEntry;
|
||||
|
||||
mod props_container;
|
||||
pub use props_container::{Align, AlignContent, Behavior, ContentJustify, Direction, Gap};
|
||||
|
||||
|
|
@ -41,83 +37,3 @@ pub use container::Flex;
|
|||
|
||||
mod item;
|
||||
pub use item::FlexItem;
|
||||
|
||||
// **< Flex / FlexItem PRIVATE >********************************************************************
|
||||
|
||||
// Sustituye, en un valor CSS ya resuelto, los únicos caracteres (`.`, `%`) que no podrían usarse
|
||||
// como fragmento de un nombre de clase. Así, `"1.5rem"` sería `"1_5rem"` y `"33.3333%"` quedaría
|
||||
// como `"33_3333pct"`.
|
||||
fn value_to_token(value: &str) -> String {
|
||||
value.replace('.', "_").replace('%', "pct")
|
||||
}
|
||||
|
||||
// Añade un estilo (`property: value`) al punto de corte indicado, y la clase a `classes`, separada
|
||||
// con un espacio de las que ya hubiera. Recibe un `BreakpointEntry` ya resuelto (ver
|
||||
// `Breakpoint::resolved()`) y extrae aquí el `Breakpoint` que `AddResponsiveStyle` necesita, que
|
||||
// puede ser nulo si aplica siempre.
|
||||
//
|
||||
// La clase se copia al acumulador y se mueve al `AssetsOp`, sin clonarla. Recibirla como `CowStr`
|
||||
// permite además que las clases fijas, las que no dependen de ningún punto de corte, lleguen como
|
||||
// `&'static str` sin asignar memoria.
|
||||
fn styles(
|
||||
cx: &mut Context,
|
||||
classes: &mut String,
|
||||
entry: Option<BreakpointEntry>,
|
||||
class: CowStr,
|
||||
property: &'static str,
|
||||
value: CowStr,
|
||||
) {
|
||||
if !classes.is_empty() {
|
||||
classes.push(' ');
|
||||
}
|
||||
classes.push_str(&class);
|
||||
|
||||
cx.alter_assets(AssetsOp::add_responsive_style(
|
||||
entry.map(|e| e.breakpoint),
|
||||
class,
|
||||
property,
|
||||
value,
|
||||
));
|
||||
}
|
||||
|
||||
// Nombre de clase según el punto de corte: `prefix` ya incluye el guion bajo final antes del valor
|
||||
// (p. ej. `"_flex-direction_"`), y `entry` añade su sufijo si aplica (`"_flex-direction_row_md_"`),
|
||||
// ya resuelto para el tema activo (ver `Breakpoint::resolved()`).
|
||||
macro_rules! responsive_class {
|
||||
($prefix:expr, $token:expr, $entry:expr) => {
|
||||
match $entry {
|
||||
None => util::join!($prefix, $token, "_"),
|
||||
Some(entry) => util::join!($prefix, $token, "_", entry.name, "_"),
|
||||
}
|
||||
};
|
||||
}
|
||||
use responsive_class;
|
||||
|
||||
// Recorre las entradas para una propiedad `Responsive<T>` cuyo valor CSS es un único `T::value()`,
|
||||
// generando y registrando (vía `styles()`) una clase por punto de corte con valor.
|
||||
//
|
||||
// La forma con el marcador final `val` es para propiedades cuyo valor puede contener `.`/`%` (como
|
||||
// `ItemSize` o `ItemOffset` en `FlexItem`) y necesitan pasar por `value_to_token()`.
|
||||
macro_rules! apply {
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value, entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal, val) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value_to_token(&value), entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
use apply;
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ impl Flex {
|
|||
return;
|
||||
};
|
||||
|
||||
use super::{apply, responsive_class, styles, value_to_token};
|
||||
use crate::html::responsive::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
let (prefix, value) = match display {
|
||||
DisplayFlex::Always
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ impl FlexItem {
|
|||
/// cadenas intermedias.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn apply(self, cx: &mut Context, classes: &mut String) {
|
||||
use super::{apply, responsive_class, styles, value_to_token};
|
||||
use crate::html::responsive::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
apply!(cx, classes, self.grow, "_flex-item-grow_", "flex-grow");
|
||||
apply!(cx, classes, self.shrink, "_flex-item-shrink_", "flex-shrink");
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ use crate::core::component::Context;
|
|||
use crate::html::flex::{Flex, FlexItem};
|
||||
use crate::html::maud::{Escaper, RenderAttrs};
|
||||
use crate::html::props::{PropsError, PropsExtra, PropsOp};
|
||||
use crate::html::spacing::{Margin, Padding};
|
||||
use crate::{AutoDefault, CowStr, builder_impl, trace, util};
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -190,6 +191,8 @@ pub struct Props {
|
|||
attrs: Vec<(CowStr, CowStr)>,
|
||||
extras: HashMap<&'static str, PropsExtra>,
|
||||
flex_item: FlexItem,
|
||||
margin: Margin,
|
||||
padding: Padding,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
|
|
@ -213,7 +216,8 @@ impl Props {
|
|||
}
|
||||
|
||||
/// Modifica el identificador, las clases, los atributos o los valores extra según la operación
|
||||
/// indicada. El método recomendado para construir cada operación es usar los constructores de
|
||||
/// indicada, incluido el posicionamiento Flexbox y el espaciado (`FlexItem`, `Margin`,
|
||||
/// `Padding`). El método recomendado para construir cada operación es usar los constructores de
|
||||
/// [`PropsOp`].
|
||||
pub fn with_prop(mut self, op: PropsOp) -> Self {
|
||||
match op {
|
||||
|
|
@ -339,6 +343,12 @@ impl Props {
|
|||
PropsOp::FlexItem(placement) => {
|
||||
self.flex_item = self.flex_item.merge(placement);
|
||||
}
|
||||
PropsOp::Margin(margin) => {
|
||||
self.margin = self.margin.merge(margin);
|
||||
}
|
||||
PropsOp::Padding(padding) => {
|
||||
self.padding = self.padding.merge(padding);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
|
|
@ -560,9 +570,9 @@ impl Props {
|
|||
///
|
||||
/// `Props` no implementa [`RenderAttrs`] directamente. Obliga a pasar siempre el `Context`
|
||||
/// vigente en el punto donde se renderiza, aunque no lo necesite ningún atributo propio. Recibe
|
||||
/// `&mut Context` porque aquí, en el momento de extraer los atributos, es donde se resuelve
|
||||
/// [`FlexItem`]: las clases que devuelve se añaden a las del propio componente al escribir el
|
||||
/// atributo `class`.
|
||||
/// `&mut Context` porque aquí, en el momento de extraer los atributos, es donde se resuelven
|
||||
/// [`FlexItem`], [`Margin`] y [`Padding`]: las clases que devuelven se añaden a las del propio
|
||||
/// componente al escribir el atributo `class`.
|
||||
///
|
||||
/// Si el propio elemento actúa además como contenedor [`Flex`], utiliza [`unpack_with_flex()`]
|
||||
/// en su lugar.
|
||||
|
|
@ -578,10 +588,14 @@ impl Props {
|
|||
/// [`html!`]: crate::html::html
|
||||
/// [`Flex`]: crate::html::flex::Flex
|
||||
/// [`FlexItem`]: crate::html::flex::FlexItem
|
||||
/// [`Margin`]: crate::html::spacing::Margin
|
||||
/// [`Padding`]: crate::html::spacing::Padding
|
||||
/// [`unpack_with_flex()`]: Self::unpack_with_flex
|
||||
pub fn unpack<'a>(&'a self, cx: &mut Context) -> impl RenderAttrs + 'a {
|
||||
let mut classes = String::new();
|
||||
self.flex_item.apply(cx, &mut classes);
|
||||
self.margin.apply(cx, &mut classes);
|
||||
self.padding.apply(cx, &mut classes);
|
||||
PropsUnpack {
|
||||
props: self,
|
||||
classes,
|
||||
|
|
@ -590,20 +604,23 @@ impl Props {
|
|||
|
||||
/// Igual que [`unpack()`], pero además resuelve `flex` con el posicionamiento [`Flex`].
|
||||
///
|
||||
/// A diferencia de [`FlexItem`] (que se acumula con [`PropsOp::FlexItem`] porque cualquier
|
||||
/// componente ajeno puede necesitarlo sin tener un campo propio para ello), `Flex` sólo tiene
|
||||
/// sentido en los contenedores que ya declaran su propio campo `flex: Flex` (`Container`,
|
||||
/// `Navbar`...): se les pasa aquí directamente, ya resuelto (`self.flex()`), sin pasar por
|
||||
/// `PropsOp`.
|
||||
/// A diferencia de [`FlexItem`]/[`Margin`]/[`Padding`] (que se acumulan con sus respectivas
|
||||
/// variantes de `PropsOp` porque cualquier componente ajeno puede necesitarlos sin tener un
|
||||
/// campo propio para ello), `Flex` sólo tiene sentido en los contenedores que ya declaran su
|
||||
/// propio campo `flex: Flex` (como `Container` o `Navbar`). Se les pasa aquí directamente, ya
|
||||
/// resuelto (`self.flex()`), sin pasar por `PropsOp`.
|
||||
///
|
||||
/// [`unpack()`]: Self::unpack
|
||||
/// [`Flex`]: crate::html::flex::Flex
|
||||
/// [`FlexItem`]: crate::html::flex::FlexItem
|
||||
/// [`PropsOp::FlexItem`]: crate::html::props::PropsOp::FlexItem
|
||||
/// [`Margin`]: crate::html::spacing::Margin
|
||||
/// [`Padding`]: crate::html::spacing::Padding
|
||||
pub fn unpack_with_flex<'a>(&'a self, cx: &mut Context, flex: Flex) -> impl RenderAttrs + 'a {
|
||||
let mut classes = String::new();
|
||||
flex.apply(cx, &mut classes);
|
||||
self.flex_item.apply(cx, &mut classes);
|
||||
self.margin.apply(cx, &mut classes);
|
||||
self.padding.apply(cx, &mut classes);
|
||||
PropsUnpack {
|
||||
props: self,
|
||||
classes,
|
||||
|
|
@ -800,8 +817,9 @@ impl Props {
|
|||
// **< PropsUnpack >********************************************************************************
|
||||
|
||||
// Devuelto por `Props::unpack()`/`Props::unpack_with_flex()`. `classes` son las clases resueltas
|
||||
// por `FlexItem::apply()`/`Flex::apply()` (desde el propio `unpack*()` usando el `&mut Context`),
|
||||
// pendientes sólo de añadir a las del componente (ver `Props::write_attrs()`).
|
||||
// por `Flex::apply()`/`FlexItem::apply()`/`Margin::apply()`/`Padding::apply()` (desde el propio
|
||||
// `unpack*()` usando el `&mut Context`), pendientes sólo de añadir a las del componente (ver
|
||||
// `Props::write_attrs()`).
|
||||
struct PropsUnpack<'a> {
|
||||
props: &'a Props,
|
||||
classes: String,
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ use crate::CowStr;
|
|||
use crate::core::TypeInfo;
|
||||
use crate::html::flex::FlexItem;
|
||||
use crate::html::props::extra::PropsExtra;
|
||||
use crate::html::spacing::{Margin, Padding};
|
||||
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
|
|
@ -40,8 +41,9 @@ use std::sync::Arc;
|
|||
/// se interpretan como si
|
||||
/// fueran valores internos del componente para tomar decisiones durante el renderizado.
|
||||
///
|
||||
/// Finalmente, [`FlexItem`](Self::FlexItem) aplica un posicionamiento Flexbox a nivel de ítem sobre
|
||||
/// cualquier componente.
|
||||
/// [`FlexItem`](Self::FlexItem) aplica un posicionamiento Flexbox a nivel de ítem, mientras que
|
||||
/// [`Margin`](Self::Margin) y [`Padding`](Self::Padding) aplican márgenes y relleno interno; los
|
||||
/// tres sobre cualquier componente.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PropsOp {
|
||||
/// Establece el identificador del componente normalizando el valor: recorta espacios, convierte
|
||||
|
|
@ -113,7 +115,7 @@ pub enum PropsOp {
|
|||
/// Elimina el valor extra asociado a la clave indicada, si existe.
|
||||
RemoveExtra(&'static str),
|
||||
/// Aplica un posicionamiento [`FlexItem`] a un componente particular en un contenedor [`Flex`].
|
||||
/// Añade directamente sus estilos Flexbox al propio componente, sin usar clases CSS.
|
||||
/// Añade directamente sus estilos Flexbox al propio componente, generando clases CSS dinámicas.
|
||||
///
|
||||
/// Existe como variante de `PropsOp`, y no como método builder de un componente, porque las
|
||||
/// propiedades de un ítem Flexbox tienen sentido sobre **cualquier** componente que pueda
|
||||
|
|
@ -128,6 +130,15 @@ pub enum PropsOp {
|
|||
/// [`Flex`]: crate::html::flex::Flex
|
||||
/// [`Container::flex()`]: crate::base::component::Container::flex
|
||||
FlexItem(FlexItem),
|
||||
/// Aplica un margen [`Margin`] a un componente particular. Añade directamente sus estilos al
|
||||
/// propio componente, generando clases CSS dinámicas.
|
||||
///
|
||||
/// Como [`FlexItem`](Self::FlexItem), existe como variante de `PropsOp` y no como método
|
||||
/// builder de un componente, porque el margen tiene sentido sobre **cualquier** componente.
|
||||
Margin(Margin),
|
||||
/// Aplica un relleno interno [`Padding`] a un componente particular, siguiendo los mismos
|
||||
/// criterios que [`Margin`](Self::Margin).
|
||||
Padding(Padding),
|
||||
}
|
||||
|
||||
impl PropsOp {
|
||||
|
|
@ -269,4 +280,14 @@ impl PropsOp {
|
|||
pub fn flex_item(placement: FlexItem) -> Self {
|
||||
Self::FlexItem(placement)
|
||||
}
|
||||
|
||||
/// Crea la variante [`Margin`](Self::Margin) con el margen indicado.
|
||||
pub fn margin(margin: Margin) -> Self {
|
||||
Self::Margin(margin)
|
||||
}
|
||||
|
||||
/// Crea la variante [`Padding`](Self::Padding) con el relleno interno indicado.
|
||||
pub fn padding(padding: Padding) -> Self {
|
||||
Self::Padding(padding)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
94
src/html/responsive.rs
Normal file
94
src/html/responsive.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
//! Mecanismo interno compartido para resolver clases CSS nativas *responsive*.
|
||||
//!
|
||||
//! Usado por [`flex`] y [`spacing`]. Ambos módulos resuelven su configuración generando clases CSS
|
||||
//! dinámicamente, de manera independiente a cualquier tema o framework CSS, registradas vía
|
||||
//! [`AssetsOp::add_responsive_style()`] y renderizadas como reglas en el `<head>` del documento.
|
||||
//! El nombre interno de cada clase se deriva de la propiedad y el valor que representa, así que dos
|
||||
//! elementos con la misma configuración comparten la misma regla en vez de duplicarla.
|
||||
//!
|
||||
//! [`flex`]: crate::html::flex
|
||||
//! [`spacing`]: crate::html::spacing
|
||||
//! [`AssetsOp::add_responsive_style()`]: crate::core::component::AssetsOp::add_responsive_style
|
||||
|
||||
use crate::CowStr;
|
||||
use crate::core::component::{AssetsOp, Context, Contextual};
|
||||
use crate::core::theme::BreakpointEntry;
|
||||
|
||||
// Sustituye, en un valor CSS ya resuelto, los únicos caracteres (`.`, `%`) que no podrían usarse
|
||||
// como fragmento de un nombre de clase. Así, `"1.5rem"` sería `"1_5rem"` y `"33.3333%"` quedaría
|
||||
// como `"33_3333pct"`.
|
||||
pub(crate) fn value_to_token(value: &str) -> String {
|
||||
value.replace('.', "_").replace('%', "pct")
|
||||
}
|
||||
|
||||
// Añade un estilo (`property: value`) al punto de corte indicado, y la clase a `classes`, separada
|
||||
// con un espacio de las que ya hubiera. Recibe un `BreakpointEntry` ya resuelto (ver
|
||||
// `Breakpoint::resolved()`) y extrae aquí el `Breakpoint` que `AddResponsiveStyle` necesita, que
|
||||
// puede ser nulo si aplica siempre.
|
||||
//
|
||||
// La clase se copia al acumulador y se mueve al `AssetsOp`, sin clonarla. Recibirla como `CowStr`
|
||||
// permite además que las clases fijas, las que no dependen de ningún punto de corte, lleguen como
|
||||
// `&'static str` sin asignar memoria.
|
||||
pub(crate) fn styles(
|
||||
cx: &mut Context,
|
||||
classes: &mut String,
|
||||
entry: Option<BreakpointEntry>,
|
||||
class: CowStr,
|
||||
property: &'static str,
|
||||
value: CowStr,
|
||||
) {
|
||||
if !classes.is_empty() {
|
||||
classes.push(' ');
|
||||
}
|
||||
classes.push_str(&class);
|
||||
|
||||
cx.alter_assets(AssetsOp::add_responsive_style(
|
||||
entry.map(|e| e.breakpoint),
|
||||
class,
|
||||
property,
|
||||
value,
|
||||
));
|
||||
}
|
||||
|
||||
// Nombre de clase según el punto de corte: `prefix` ya incluye el guion bajo final antes del valor
|
||||
// (p. ej. `"_flex-direction_"`), y `entry` añade su sufijo si aplica (`"_flex-direction_row_md_"`),
|
||||
// ya resuelto para el tema activo (ver `Breakpoint::resolved()`).
|
||||
macro_rules! responsive_class {
|
||||
($prefix:expr, $token:expr, $entry:expr) => {
|
||||
match $entry {
|
||||
None => util::join!($prefix, $token, "_"),
|
||||
Some(entry) => util::join!($prefix, $token, "_", entry.name, "_"),
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use responsive_class;
|
||||
|
||||
// Recorre las entradas para una propiedad `Responsive<T>` cuyo valor CSS es un único `T::value()`,
|
||||
// generando y registrando (vía `styles()`) una clase por punto de corte con valor.
|
||||
//
|
||||
// La forma con el marcador final `val` es para propiedades cuyo valor puede contener `.`/`%` (como
|
||||
// `ItemSize`/`ItemOffset` en `FlexItem`, o `UnitValue` en `Margin`/`Padding`) y necesitan pasar por
|
||||
// `value_to_token()`.
|
||||
macro_rules! apply {
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value, entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
($cx:expr, $classes:expr, $field:expr, $prefix:literal, $property:literal, val) => {
|
||||
for (bp, value) in $field.by_breakpoint() {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved($cx);
|
||||
let class = responsive_class!($prefix, value_to_token(&value), entry);
|
||||
styles($cx, $classes, entry, class.into(), $property, value);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
pub(crate) use apply;
|
||||
30
src/html/spacing.rs
Normal file
30
src/html/spacing.rs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
//! Márgenes y relleno nativos aplicados a componentes.
|
||||
//!
|
||||
//! [`Margin`] y [`Padding`] configuran márgenes externos y relleno interno por lado lógico y punto
|
||||
//! de corte. Aplican sobre cualquier componente. Se usan igual que [`FlexItem`], con
|
||||
//! [`PropsOp::margin()`] y [`PropsOp::padding()`] sobre el `with_prop()` que normalmente ya expone
|
||||
//! cualquier componente.
|
||||
//!
|
||||
//! # Ejemplo
|
||||
//!
|
||||
//! ```rust,no_run
|
||||
//! use pagetop::prelude::*;
|
||||
//!
|
||||
//! // Margen exterior arriba/abajo y relleno interno por los cuatro lados.
|
||||
//! // `Margin` y `Padding` implementan `From` para `PropsOp`, así que `with_prop()`
|
||||
//! // acepta `.into()` en vez de `PropsOp::margin()`/`PropsOp::padding()`.
|
||||
//! let card = Container::new()
|
||||
//! .with_prop(Margin::new().with_y(UnitValue::RelRem(1.0)).into())
|
||||
//! .with_prop(Padding::new().with_all(UnitValue::RelRem(1.5)).into())
|
||||
//! .with_child(Button::plain(Lc::n("Aceptar")));
|
||||
//! ```
|
||||
//!
|
||||
//! [`FlexItem`]: crate::html::flex::FlexItem
|
||||
//! [`PropsOp::margin()`]: crate::html::props::PropsOp::margin
|
||||
//! [`PropsOp::padding()`]: crate::html::props::PropsOp::padding
|
||||
|
||||
mod margin;
|
||||
pub use margin::Margin;
|
||||
|
||||
mod padding;
|
||||
pub use padding::Padding;
|
||||
191
src/html/spacing/margin.rs
Normal file
191
src/html/spacing/margin.rs
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::core::theme::{Breakpoint, Responsive};
|
||||
use crate::html::PropsOp;
|
||||
use crate::html::unit::UnitValue;
|
||||
use crate::{AutoDefault, Getters, builder_impl, util};
|
||||
|
||||
/// Configuración de márgenes externos por lado lógico y punto de corte.
|
||||
///
|
||||
/// No tiene relación con Flexbox. Se aplica sobre cualquier componente, con [`PropsOp::margin()`]
|
||||
/// desde el `with_prop()` que suele exponer cualquier componente.
|
||||
///
|
||||
/// Cada lado admite cualquier [`UnitValue`], incluido [`UnitValue::Auto`] (por ejemplo, para
|
||||
/// centrar un bloque con `margin-inline: auto`). Los lados lógicos `start`/`end` se traducen a
|
||||
/// `margin-inline-start`/`margin-inline-end`, respetando LTR/RTL.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// // Centra el bloque horizontalmente y añade espacio inferior.
|
||||
/// let panel = Container::new().with_prop(PropsOp::margin(
|
||||
/// Margin::new()
|
||||
/// .with_x(UnitValue::Auto)
|
||||
/// .with_bottom(UnitValue::RelRem(1.5)),
|
||||
/// ));
|
||||
/// ```
|
||||
///
|
||||
/// [`Flex`]: crate::html::flex::Flex
|
||||
/// [`FlexItem`]: crate::html::flex::FlexItem
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq, Getters)]
|
||||
pub struct Margin {
|
||||
/// Devuelve el margen superior, por punto de corte.
|
||||
#[getters(copy)]
|
||||
top: Responsive<UnitValue>,
|
||||
/// Devuelve el margen inferior, por punto de corte.
|
||||
#[getters(copy)]
|
||||
bottom: Responsive<UnitValue>,
|
||||
/// Devuelve el margen del lado lógico de inicio, por punto de corte.
|
||||
#[getters(copy)]
|
||||
start: Responsive<UnitValue>,
|
||||
/// Devuelve el margen del lado lógico de fin, por punto de corte.
|
||||
#[getters(copy)]
|
||||
end: Responsive<UnitValue>,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Margin {
|
||||
/// Crea una configuración de margen sin ningún lado establecido.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// **< Margin BUILDER >*************************************************************************
|
||||
|
||||
/// Establece el margen superior.
|
||||
pub fn with_top(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen superior, a partir del punto de corte indicado.
|
||||
pub fn with_top_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen inferior.
|
||||
pub fn with_bottom(mut self, value: UnitValue) -> Self {
|
||||
self.bottom = self.bottom.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen inferior, a partir del punto de corte indicado.
|
||||
pub fn with_bottom_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen del lado lógico de inicio (`margin-inline-start`).
|
||||
pub fn with_start(mut self, value: UnitValue) -> Self {
|
||||
self.start = self.start.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen del lado lógico de inicio (`margin-inline-start`), a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn with_start_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen del lado lógico de fin (`margin-inline-end`).
|
||||
pub fn with_end(mut self, value: UnitValue) -> Self {
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el margen del lado lógico de fin (`margin-inline-end`), a partir del punto de
|
||||
/// corte indicado.
|
||||
pub fn with_end_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen en ambos lados lógicos laterales (inicio y fin).
|
||||
pub fn with_x(mut self, value: UnitValue) -> Self {
|
||||
self.start = self.start.set(value);
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen en ambos lados lógicos laterales (inicio y fin), a partir del
|
||||
/// punto de corte indicado.
|
||||
pub fn with_x_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen arriba y abajo.
|
||||
pub fn with_y(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self.bottom = self.bottom.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen arriba y abajo, a partir del punto de corte indicado.
|
||||
pub fn with_y_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen en los cuatro lados.
|
||||
pub fn with_all(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self.bottom = self.bottom.set(value);
|
||||
self.start = self.start.set(value);
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo margen en los cuatro lados, a partir del punto de corte indicado.
|
||||
pub fn with_all_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Margin {
|
||||
/// Combina esta configuración con otra `Margin`, lado a lado y punto de corte a punto de corte.
|
||||
/// Donde `margin` tenga un valor establecido, sustituye al de `self`, y donde no lo tenga, se
|
||||
/// conserva el de `self`. Es el método que usa [`Props::with_prop()`] para que sucesivas
|
||||
/// [`PropsOp::Margin`] sobre el mismo componente vayan completando lados concretos sin repetir
|
||||
/// los ya establecidos.
|
||||
///
|
||||
/// [`Props::with_prop()`]: crate::html::Props::with_prop
|
||||
/// [`PropsOp::Margin`]: crate::html::PropsOp::Margin
|
||||
pub fn merge(mut self, margin: Margin) -> Self {
|
||||
self.top = self.top.merge(margin.top);
|
||||
self.bottom = self.bottom.merge(margin.bottom);
|
||||
self.start = self.start.merge(margin.start);
|
||||
self.end = self.end.merge(margin.end);
|
||||
self
|
||||
}
|
||||
|
||||
/// Aplica esta configuración como clases de utilidad responsive en el [`Context`], igual que
|
||||
/// [`FlexItem::apply()`](crate::html::flex::FlexItem::apply). Un lado sin ningún valor
|
||||
/// establecido, o con [`UnitValue::None`], no añade nada.
|
||||
///
|
||||
/// Las clases generadas se añaden a `classes`, separadas con un espacio de las que ya hubiera.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn apply(self, cx: &mut Context, classes: &mut String) {
|
||||
use crate::html::responsive::{apply, responsive_class, styles, value_to_token};
|
||||
|
||||
apply!(cx, classes, self.top, "_margin-top_", "margin-top", val);
|
||||
apply!(cx, classes, self.bottom, "_margin-bottom_", "margin-bottom", val);
|
||||
apply!(cx, classes, self.start, "_margin-start_", "margin-inline-start", val);
|
||||
apply!(cx, classes, self.end, "_margin-end_", "margin-inline-end", val);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Margin> for PropsOp {
|
||||
fn from(margin: Margin) -> Self {
|
||||
Self::margin(margin)
|
||||
}
|
||||
}
|
||||
202
src/html/spacing/padding.rs
Normal file
202
src/html/spacing/padding.rs
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
use crate::core::component::Context;
|
||||
use crate::core::theme::{Breakpoint, Responsive};
|
||||
use crate::html::PropsOp;
|
||||
use crate::html::unit::UnitValue;
|
||||
use crate::{AutoDefault, Getters, builder_impl, util};
|
||||
|
||||
/// Configuración de relleno interno por lado lógico y punto de corte.
|
||||
///
|
||||
/// Mismo mecanismo y criterio de uso que [`Margin`](super::Margin): no tiene relación con Flexbox,
|
||||
/// y se aplica sobre cualquier componente vía [`PropsOp::padding()`] desde su `with_prop()`.
|
||||
///
|
||||
/// A diferencia de `Margin`, [`UnitValue::Auto`] no tiene efecto en ningún lado ya que CSS no
|
||||
/// admite `padding: auto`, así que un lado establecido a `Auto` se ignora como si no se hubiera
|
||||
/// establecido.
|
||||
///
|
||||
/// # Ejemplo
|
||||
///
|
||||
/// ```rust,no_run
|
||||
/// use pagetop::prelude::*;
|
||||
///
|
||||
/// let card = Container::new().with_prop(PropsOp::padding(
|
||||
/// Padding::new()
|
||||
/// .with_all(UnitValue::RelRem(1.0))
|
||||
/// .with_bottom_at(Breakpoint::Md, UnitValue::RelRem(2.0)),
|
||||
/// ));
|
||||
/// ```
|
||||
#[derive(AutoDefault, Clone, Copy, Debug, PartialEq, Getters)]
|
||||
pub struct Padding {
|
||||
/// Devuelve el relleno interno superior, por punto de corte.
|
||||
#[getters(copy)]
|
||||
top: Responsive<UnitValue>,
|
||||
/// Devuelve el relleno interno inferior, por punto de corte.
|
||||
#[getters(copy)]
|
||||
bottom: Responsive<UnitValue>,
|
||||
/// Devuelve el relleno interno del lado lógico de inicio, por punto de corte.
|
||||
#[getters(copy)]
|
||||
start: Responsive<UnitValue>,
|
||||
/// Devuelve el relleno interno del lado lógico de fin, por punto de corte.
|
||||
#[getters(copy)]
|
||||
end: Responsive<UnitValue>,
|
||||
}
|
||||
|
||||
#[builder_impl]
|
||||
impl Padding {
|
||||
/// Crea una configuración de relleno sin ningún lado establecido.
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
// **< Padding BUILDER >************************************************************************
|
||||
|
||||
/// Establece el relleno interno superior.
|
||||
pub fn with_top(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno superior, a partir del punto de corte indicado.
|
||||
pub fn with_top_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno inferior.
|
||||
pub fn with_bottom(mut self, value: UnitValue) -> Self {
|
||||
self.bottom = self.bottom.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno inferior, a partir del punto de corte indicado.
|
||||
pub fn with_bottom_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno del lado lógico de inicio (`padding-inline-start`).
|
||||
pub fn with_start(mut self, value: UnitValue) -> Self {
|
||||
self.start = self.start.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno del lado lógico de inicio (`padding-inline-start`), a partir
|
||||
/// del punto de corte indicado.
|
||||
pub fn with_start_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno del lado lógico de fin (`padding-inline-end`).
|
||||
pub fn with_end(mut self, value: UnitValue) -> Self {
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el relleno interno del lado lógico de fin (`padding-inline-end`), a partir del
|
||||
/// punto de corte indicado.
|
||||
pub fn with_end_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno en ambos lados lógicos laterales (inicio y fin).
|
||||
pub fn with_x(mut self, value: UnitValue) -> Self {
|
||||
self.start = self.start.set(value);
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno en ambos lados lógicos laterales (inicio y fin), a partir
|
||||
/// del punto de corte indicado.
|
||||
pub fn with_x_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno arriba y abajo.
|
||||
pub fn with_y(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self.bottom = self.bottom.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno arriba y abajo, a partir del punto de corte indicado.
|
||||
pub fn with_y_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno en los cuatro lados.
|
||||
pub fn with_all(mut self, value: UnitValue) -> Self {
|
||||
self.top = self.top.set(value);
|
||||
self.bottom = self.bottom.set(value);
|
||||
self.start = self.start.set(value);
|
||||
self.end = self.end.set(value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Establece el mismo relleno interno en los cuatro lados, a partir del punto de corte
|
||||
/// indicado.
|
||||
pub fn with_all_at(mut self, bp: Breakpoint, value: UnitValue) -> Self {
|
||||
self.top = self.top.set_at(bp, value);
|
||||
self.bottom = self.bottom.set_at(bp, value);
|
||||
self.start = self.start.set_at(bp, value);
|
||||
self.end = self.end.set_at(bp, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl Padding {
|
||||
/// Combina esta configuración con otra `Padding`, lado a lado y punto de corte a punto de
|
||||
/// corte; mismo criterio que [`Margin::merge()`](super::Margin::merge).
|
||||
pub fn merge(mut self, padding: Padding) -> Self {
|
||||
self.top = self.top.merge(padding.top);
|
||||
self.bottom = self.bottom.merge(padding.bottom);
|
||||
self.start = self.start.merge(padding.start);
|
||||
self.end = self.end.merge(padding.end);
|
||||
self
|
||||
}
|
||||
|
||||
/// Aplica esta configuración como clases de utilidad responsive en el [`Context`], igual que
|
||||
/// [`Margin::apply()`](super::Margin::apply), salvo que aquí un lado con [`UnitValue::Auto`] se
|
||||
/// descarta (ver la documentación de este tipo).
|
||||
///
|
||||
/// Las clases generadas se añaden a `classes`, separadas con un espacio de las que ya hubiera.
|
||||
#[rustfmt::skip]
|
||||
pub(crate) fn apply(self, cx: &mut Context, classes: &mut String) {
|
||||
Self::apply_side(cx, classes, self.top, "_padding-top_", "padding-top");
|
||||
Self::apply_side(cx, classes, self.bottom, "_padding-bottom_", "padding-bottom");
|
||||
Self::apply_side(cx, classes, self.start, "_padding-start_", "padding-inline-start");
|
||||
Self::apply_side(cx, classes, self.end, "_padding-end_", "padding-inline-end");
|
||||
}
|
||||
|
||||
// Aplica un único lado, descartando `UnitValue::Auto` porque CSS no admite `padding: auto`.
|
||||
fn apply_side(
|
||||
cx: &mut Context,
|
||||
classes: &mut String,
|
||||
field: Responsive<UnitValue>,
|
||||
prefix: &'static str,
|
||||
property: &'static str,
|
||||
) {
|
||||
use crate::html::responsive::{responsive_class, styles, value_to_token};
|
||||
|
||||
for (bp, value) in field.by_breakpoint() {
|
||||
if value != UnitValue::Auto {
|
||||
let value = value.value();
|
||||
if !value.is_empty() {
|
||||
let entry = bp.resolved(cx);
|
||||
let class = responsive_class!(prefix, value_to_token(&value), entry);
|
||||
styles(cx, classes, entry, class.into(), property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Padding> for PropsOp {
|
||||
fn from(padding: Padding) -> Self {
|
||||
Self::padding(padding)
|
||||
}
|
||||
}
|
||||
|
|
@ -126,6 +126,13 @@ impl UnitValue {
|
|||
pub const fn is_measurable(&self) -> bool {
|
||||
!matches!(self, UnitValue::None | UnitValue::Auto)
|
||||
}
|
||||
|
||||
// Convierte a un valor CSS ya formateado. Da a `UnitValue` la misma forma de acceso (`value()`)
|
||||
// que ya usan propiedades de `html::flex` como `Direction`, `ItemSize`, `ItemOffset`, etc.,
|
||||
// para poder combinarse con su misma macro (`apply!`).
|
||||
pub(crate) fn value(self) -> CowStr {
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Formatea la unidad como cadena CSS.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue