💄 [bootsier] Añade soporte a "Container" y "Grid"

This commit is contained in:
Manuel Cillero 2025-01-02 11:53:59 +01:00
parent cfb96135df
commit 2f5c7f461f
6 changed files with 547 additions and 2 deletions

View file

@ -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"),
}
}
}

View file

@ -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<String> {
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<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> 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
}
}

View file

@ -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, ""),
}
}
}

View file

@ -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<String> {
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<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> 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<grid::Item>) -> 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
}
}

View file

@ -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<String> {
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<String>) -> Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn with_classes(mut self, op: ClassesOp, classes: impl Into<String>) -> 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
}
}

View file

@ -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;