From 2f5c7f461fbe434d8caea52772f1c396370ddfbe Mon Sep 17 00:00:00 2001 From: Manuel Cillero Date: Thu, 2 Jan 2025 11:53:59 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=84=20[bootsier]=20A=C3=B1ade=20soport?= =?UTF-8?q?e=20a=20"Container"=20y=20"Grid"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/pagetop-bootsier/src/bs.rs | 46 +++++ packages/pagetop-bootsier/src/bs/container.rs | 167 ++++++++++++++++++ packages/pagetop-bootsier/src/bs/grid.rs | 110 ++++++++++++ .../pagetop-bootsier/src/bs/grid/cssgrid.rs | 103 +++++++++++ packages/pagetop-bootsier/src/bs/grid/item.rs | 119 +++++++++++++ .../static/scss/_variables.scss | 4 +- 6 files changed, 547 insertions(+), 2 deletions(-) create mode 100644 packages/pagetop-bootsier/src/bs/container.rs create mode 100644 packages/pagetop-bootsier/src/bs/grid.rs create mode 100644 packages/pagetop-bootsier/src/bs/grid/cssgrid.rs create mode 100644 packages/pagetop-bootsier/src/bs/grid/item.rs diff --git a/packages/pagetop-bootsier/src/bs.rs b/packages/pagetop-bootsier/src/bs.rs index 8b137891..3933b8ff 100644 --- a/packages/pagetop-bootsier/src/bs.rs +++ b/packages/pagetop-bootsier/src/bs.rs @@ -1 +1,47 @@ +use pagetop::prelude::*; +use std::fmt; + +mod container; +pub use container::{Container, ContainerType}; + +pub mod grid; + +/// Define los puntos de interrupción (*breakpoints*) usados por Bootstrap para diseño responsivo. +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum BreakPoint { + #[default] + None, // < 576px (Dispositivos muy pequeños: teléfonos en modo vertical, menos de 576px) + SM, // >= 576px (Dispositivos pequeños: teléfonos en modo horizontal, 576px o más) + MD, // >= 768px (Dispositivos medianos: tabletas, 768px o más) + LG, // >= 992px (Dispositivos grandes: puestos de escritorio, 992px o más) + XL, // >= 1200px (Dispositivos muy grandes: puestos de escritorio grandes, 1200px o más) + XXL, // >= 1400px (Dispositivos extragrandes: puestos de escritorio más grandes, 1400px o más) + Fluid, // Siempre aplica el 100% del dispositivo +} + +impl BreakPoint { + /// Indica si se trata de un punto de interrupción de Bootstrap. + /// Devuelve `true` si el valor es SM, MD, LG, XL o XXL. + /// Devuelve `false` si es None o Fluid. + pub fn is_breakpoint(&self) -> bool { + !matches!(self, BreakPoint::None | BreakPoint::Fluid) + } +} + +/// Devuelve el texto asociado al punto de interrupción usado por Bootstrap. +#[rustfmt::skip] +impl fmt::Display for BreakPoint { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + BreakPoint::None => write!(f, ""), + BreakPoint::SM => write!(f, "sm"), + BreakPoint::MD => write!(f, "md"), + BreakPoint::LG => write!(f, "lg"), + BreakPoint::XL => write!(f, "xl"), + BreakPoint::XXL => write!(f, "xxl"), + BreakPoint::Fluid => write!(f, "fluid"), + } + } +} diff --git a/packages/pagetop-bootsier/src/bs/container.rs b/packages/pagetop-bootsier/src/bs/container.rs new file mode 100644 index 00000000..28bf14d6 --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/container.rs @@ -0,0 +1,167 @@ +use pagetop::prelude::*; + +use crate::bs::BreakPoint; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub enum ContainerType { + #[default] + Default, // Contenedor genérico + Main, // Contenido principal + Header, // Encabezado + Footer, // Pie + Section, // Sección específica de contenido + Article, // Artículo dentro de una sección +} + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Container { + id : OptionId, + classes : OptionClasses, + container_type: ContainerType, + breakpoint : BreakPoint, + children : Children, +} + +impl ComponentTrait for Container { + fn new() -> Self { + Container::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + trio_string!("container", "-", self.breakpoint().to_string()), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let output = self.children().render(cx); + if output.is_empty() { + return PrepareMarkup::None; + } + match self.container_type() { + ContainerType::Default => PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + ContainerType::Main => PrepareMarkup::With(html! { + main id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + ContainerType::Header => PrepareMarkup::With(html! { + header id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + ContainerType::Footer => PrepareMarkup::With(html! { + footer id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + ContainerType::Section => PrepareMarkup::With(html! { + section id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + ContainerType::Article => PrepareMarkup::With(html! { + article id=[self.id()] class=[self.classes().get()] { + (output) + } + }), + } + } +} + +impl Container { + pub fn main() -> Self { + Container { + container_type: ContainerType::Main, + ..Default::default() + } + } + + pub fn header() -> Self { + Container { + container_type: ContainerType::Header, + ..Default::default() + } + } + + pub fn footer() -> Self { + Container { + container_type: ContainerType::Footer, + ..Default::default() + } + } + + pub fn section() -> Self { + Container { + container_type: ContainerType::Section, + ..Default::default() + } + } + + pub fn article() -> Self { + Container { + container_type: ContainerType::Article, + ..Default::default() + } + } + + // Container BUILDER. + + #[fn_builder] + pub fn with_id(mut self, id: impl Into) -> Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn with_classes(mut self, op: ClassesOp, classes: impl Into) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { + self.breakpoint = bp; + self + } + + pub fn add_child(mut self, child: impl ComponentTrait) -> Self { + self.children.add(ChildComponent::with(child)); + self + } + + #[fn_builder] + pub fn with_children(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // Container GETTERS. + + fn classes(&self) -> &OptionClasses { + &self.classes + } + + pub fn container_type(&self) -> &ContainerType { + &self.container_type + } + + pub fn breakpoint(&self) -> &BreakPoint { + &self.breakpoint + } + + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/packages/pagetop-bootsier/src/bs/grid.rs b/packages/pagetop-bootsier/src/bs/grid.rs new file mode 100644 index 00000000..58b99965 --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/grid.rs @@ -0,0 +1,110 @@ +use pagetop::prelude::*; + +use crate::bs::BreakPoint; + +use std::fmt; + +mod cssgrid; +pub use cssgrid::Grid; + +mod item; +pub use item::Item; + +#[derive(AutoDefault)] +pub enum Layout { + #[default] + Default, + Rows(u8), + Cols(u8), + Grid(u8, u8), +} + +#[rustfmt::skip] +impl fmt::Display for Layout { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Layout::Rows(r) if *r > 1 => write!(f, "--bs-rows: {r};"), + Layout::Cols(c) if *c > 0 => write!(f, "--bs-columns: {c};"), + Layout::Grid(r, c) => write!(f, "{}", trio_string!( + Layout::Rows(*r).to_string(), " ", Layout::Cols(*c).to_string() + )), + _ => write!(f, ""), + } + } +} + +#[derive(AutoDefault)] +pub enum Gap { + #[default] + Default, + Row(unit::Value), + Col(unit::Value), + Grid(unit::Value, unit::Value), + Both(unit::Value), +} + +#[rustfmt::skip] +impl fmt::Display for Gap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Gap::Default => write!(f, ""), + Gap::Row(r) => write!(f, "row-gap: {r};"), + Gap::Col(c) => write!(f, "column-gap: {c};"), + Gap::Grid(r, c) => write!(f, "--bs-gap: {r} {c};"), + Gap::Both(v) => write!(f, "--bs-gap: {v};"), + } + } +} + +#[derive(AutoDefault)] +pub enum ItemColumns { + #[default] + Default, + Cols(u8), +} + +#[rustfmt::skip] +impl fmt::Display for ItemColumns { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ItemColumns::Cols(c) if *c > 1 => write!(f, "g-col-{c}"), + _ => write!(f, ""), + } + } +} + +#[derive(AutoDefault)] +pub enum ItemResponsive { + #[default] + Default, + Cols(BreakPoint, u8), +} + +#[rustfmt::skip] +impl fmt::Display for ItemResponsive { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ItemResponsive::Cols(bp, c) if bp.is_breakpoint() && *c > 0 => { + write!(f, "g-col-{bp}-{c}") + } + _ => write!(f, ""), + } + } +} + +#[derive(AutoDefault)] +pub enum ItemStart { + #[default] + Default, + Col(u8), +} + +#[rustfmt::skip] +impl fmt::Display for ItemStart { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + ItemStart::Col(c) if *c > 1 => write!(f, "g-start-{c}"), + _ => write!(f, ""), + } + } +} diff --git a/packages/pagetop-bootsier/src/bs/grid/cssgrid.rs b/packages/pagetop-bootsier/src/bs/grid/cssgrid.rs new file mode 100644 index 00000000..cfb18e7b --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/grid/cssgrid.rs @@ -0,0 +1,103 @@ +use pagetop::prelude::*; + +use crate::bs::grid; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Grid { + id : OptionId, + classes : OptionClasses, + grid_layout: grid::Layout, + grid_gap : grid::Gap, + items : Children, +} + +impl ComponentTrait for Grid { + fn new() -> Self { + Grid::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes(ClassesOp::Prepend, "grid"); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let output = self.items().render(cx); + if output.is_empty() { + return PrepareMarkup::None; + } + + let style = option_string!([self.layout().to_string(), self.gap().to_string()]; " "); + + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] style=[style] { + (output) + } + }) + } +} + +impl Grid { + pub fn with(item: grid::Item) -> Self { + Grid::default().add_item(item) + } + + // Grid BUILDER. + + #[fn_builder] + pub fn with_id(mut self, id: impl Into) -> Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn with_classes(mut self, op: ClassesOp, classes: impl Into) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn with_layout(mut self, layout: grid::Layout) -> Self { + self.grid_layout = layout; + self + } + + #[fn_builder] + pub fn with_gap(mut self, gap: grid::Gap) -> Self { + self.grid_gap = gap; + self + } + + pub fn add_item(mut self, item: grid::Item) -> Self { + self.items.add(ChildComponent::with(item)); + self + } + + #[fn_builder] + pub fn with_items(mut self, op: TypedOp) -> Self { + self.items.alter_typed(op); + self + } + + // Grid GETTERS. + + fn classes(&self) -> &OptionClasses { + &self.classes + } + + pub fn layout(&self) -> &grid::Layout { + &self.grid_layout + } + + pub fn gap(&self) -> &grid::Gap { + &self.grid_gap + } + + pub fn items(&self) -> &Children { + &self.items + } +} diff --git a/packages/pagetop-bootsier/src/bs/grid/item.rs b/packages/pagetop-bootsier/src/bs/grid/item.rs new file mode 100644 index 00000000..e08f01bb --- /dev/null +++ b/packages/pagetop-bootsier/src/bs/grid/item.rs @@ -0,0 +1,119 @@ +use pagetop::prelude::*; + +use crate::bs::grid; + +#[rustfmt::skip] +#[derive(AutoDefault)] +pub struct Item { + id : OptionId, + classes : OptionClasses, + columns : grid::ItemColumns, + responsive: grid::ItemResponsive, + start : grid::ItemStart, + children : Children, +} + +impl ComponentTrait for Item { + fn new() -> Self { + Item::default() + } + + fn id(&self) -> Option { + self.id.get() + } + + fn setup_before_prepare(&mut self, _cx: &mut Context) { + self.alter_classes( + ClassesOp::Prepend, + [ + self.columns().to_string(), + self.responsive().to_string(), + self.start().to_string(), + ] + .join(" "), + ); + } + + fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup { + let output = self.children().render(cx); + if output.is_empty() { + return PrepareMarkup::None; + } + PrepareMarkup::With(html! { + div id=[self.id()] class=[self.classes().get()] { + (output) + } + }) + } +} + +impl Item { + pub fn with(child: impl ComponentTrait) -> Self { + Item::default().add_child(child) + } + + // Item BUILDER. + + #[fn_builder] + pub fn with_id(mut self, id: impl Into) -> Self { + self.id.alter_value(id); + self + } + + #[fn_builder] + pub fn with_classes(mut self, op: ClassesOp, classes: impl Into) -> Self { + self.classes.alter_value(op, classes); + self + } + + #[fn_builder] + pub fn with_columns(mut self, columns: grid::ItemColumns) -> Self { + self.columns = columns; + self + } + + #[fn_builder] + pub fn with_responsive(mut self, responsive: grid::ItemResponsive) -> Self { + self.responsive = responsive; + self + } + + #[fn_builder] + pub fn with_start(mut self, start: grid::ItemStart) -> Self { + self.start = start; + self + } + + pub fn add_child(mut self, child: impl ComponentTrait) -> Self { + self.children.add(ChildComponent::with(child)); + self + } + + #[fn_builder] + pub fn with_children(mut self, op: ChildOp) -> Self { + self.children.alter_child(op); + self + } + + // Item GETTERS. + + fn classes(&self) -> &OptionClasses { + &self.classes + } + + pub fn columns(&self) -> &grid::ItemColumns { + &self.columns + } + + pub fn responsive(&self) -> &grid::ItemResponsive { + &self.responsive + } + + pub fn start(&self) -> &grid::ItemStart { + &self.start + } + + pub fn children(&self) -> &Children { + &self.children + } +} diff --git a/packages/pagetop-bootsier/static/scss/_variables.scss b/packages/pagetop-bootsier/static/scss/_variables.scss index 06531395..35976fa2 100644 --- a/packages/pagetop-bootsier/static/scss/_variables.scss +++ b/packages/pagetop-bootsier/static/scss/_variables.scss @@ -374,9 +374,9 @@ $enable-gradients: false !default; $enable-transitions: true !default; $enable-reduced-motion: true !default; $enable-smooth-scroll: true !default; -$enable-grid-classes: true !default; +$enable-grid-classes: false !default; $enable-container-classes: true !default; -$enable-cssgrid: false !default; +$enable-cssgrid: true !default; $enable-button-pointers: true !default; $enable-rfs: true !default; $enable-validation-icons: true !default;