diff --git a/assets/css/basic.css b/assets/css/basic.css index 1f92da1f..325a34d5 100644 --- a/assets/css/basic.css +++ b/assets/css/basic.css @@ -353,13 +353,11 @@ body { display: block; } -/* Options and headers stay on one line, so the menu grows to fit the longest one. */ .dropdown-item { display: block; width: 100%; padding: 0.375rem 1rem; text-align: left; - white-space: nowrap; color: var(--val-color--text); text-decoration: none; background-color: transparent; @@ -389,7 +387,6 @@ body { display: block; padding: 0.5rem 1rem 0.25rem; margin: 0; - white-space: nowrap; font-size: 0.8125rem; font-weight: 600; color: var(--val-color--text--muted); @@ -675,182 +672,28 @@ input:disabled + label { color: var(--val-color--text--muted); } -/* - * A navbar with a toggle button collapses below the breakpoint set by its `navbar-expand-{name}` - * class, added by the `Navbar` component. `navbar-expand` alone (a breakpoint with no minimum - * width) never collapses and needs no rules, since the base rules above are the expanded layout. - * The widths below match the default `Theme::breakpoint_entry()` table, so a child theme that - * renames or resizes the breakpoints must also redefine these classes. - * - * While collapsed, the `.nav` items are stacked in a column, so a dropdown menu inside it is laid - * out in the flow (`position: static`) to push down the following items instead of covering them. - */ -@media (max-width: 575.98px) { - .navbar-expand-sm { - flex-wrap: wrap; - } - .navbar-expand-sm:has(.navbar-toggle) .navbar-toggle { - display: inline-block; - order: -1; - } - .navbar-expand-sm:has(.navbar-toggle) .navbar-content { - display: none; - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-sm:has(.navbar-toggle) .navbar-content.show { - display: flex; - } - .navbar-expand-sm .nav { - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-sm .nav .dropdown-menu { - position: static; - box-shadow: none; - } -} - @media (max-width: 767.98px) { - .navbar-expand-md { + .navbar { flex-wrap: wrap; } - .navbar-expand-md:has(.navbar-toggle) .navbar-toggle { + .navbar:has(.navbar-toggle) .navbar-toggle { display: inline-block; order: -1; } - .navbar-expand-md:has(.navbar-toggle) .navbar-content { + .navbar:has(.navbar-toggle) .navbar-content { display: none; flex-direction: column; align-items: stretch; width: 100%; } - .navbar-expand-md:has(.navbar-toggle) .navbar-content.show { + .navbar:has(.navbar-toggle) .navbar-content.show { display: flex; } - .navbar-expand-md .nav { + .navbar .nav { flex-direction: column; align-items: stretch; width: 100%; } - .navbar-expand-md .nav .dropdown-menu { - position: static; - box-shadow: none; - } -} - -@media (max-width: 991.98px) { - .navbar-expand-lg { - flex-wrap: wrap; - } - .navbar-expand-lg:has(.navbar-toggle) .navbar-toggle { - display: inline-block; - order: -1; - } - .navbar-expand-lg:has(.navbar-toggle) .navbar-content { - display: none; - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-lg:has(.navbar-toggle) .navbar-content.show { - display: flex; - } - .navbar-expand-lg .nav { - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-lg .nav .dropdown-menu { - position: static; - box-shadow: none; - } -} - -@media (max-width: 1199.98px) { - .navbar-expand-xl { - flex-wrap: wrap; - } - .navbar-expand-xl:has(.navbar-toggle) .navbar-toggle { - display: inline-block; - order: -1; - } - .navbar-expand-xl:has(.navbar-toggle) .navbar-content { - display: none; - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xl:has(.navbar-toggle) .navbar-content.show { - display: flex; - } - .navbar-expand-xl .nav { - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xl .nav .dropdown-menu { - position: static; - box-shadow: none; - } -} - -@media (max-width: 1399.98px) { - .navbar-expand-xxl { - flex-wrap: wrap; - } - .navbar-expand-xxl:has(.navbar-toggle) .navbar-toggle { - display: inline-block; - order: -1; - } - .navbar-expand-xxl:has(.navbar-toggle) .navbar-content { - display: none; - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xxl:has(.navbar-toggle) .navbar-content.show { - display: flex; - } - .navbar-expand-xxl .nav { - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xxl .nav .dropdown-menu { - position: static; - box-shadow: none; - } -} - -@media (max-width: 1919.98px) { - .navbar-expand-xxxl { - flex-wrap: wrap; - } - .navbar-expand-xxxl:has(.navbar-toggle) .navbar-toggle { - display: inline-block; - order: -1; - } - .navbar-expand-xxxl:has(.navbar-toggle) .navbar-content { - display: none; - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xxxl:has(.navbar-toggle) .navbar-content.show { - display: flex; - } - .navbar-expand-xxxl .nav { - flex-direction: column; - align-items: stretch; - width: 100%; - } - .navbar-expand-xxxl .nav .dropdown-menu { - position: static; - box-shadow: none; - } } /* diff --git a/examples/form-controls.rs b/examples/form-controls.rs index e7311b21..8bb3112d 100644 --- a/examples/form-controls.rs +++ b/examples/form-controls.rs @@ -140,9 +140,12 @@ async fn form_controls(request: HttpRequest) -> Result { .with_child(form::Hidden::field("origin", "form-selections")) // Botonera de acciones. .with_child( - Flex::new() - .with_wrap(flex::Behavior::Wrap) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) + Container::new() + .with_flex( + Flex::new() + .with_wrap(flex::Behavior::Wrap) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) .with_style(button::Style::Solid(Intent::Primary)), @@ -257,9 +260,12 @@ async fn form_controls(request: HttpRequest) -> Result { .with_child(form::Hidden::field("origin", "form-text")) // Botonera de acciones. .with_child( - Flex::new() - .with_wrap(flex::Behavior::Wrap) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) + Container::new() + .with_flex( + Flex::new() + .with_wrap(flex::Behavior::Wrap) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) .with_style(button::Style::Solid(Intent::Primary)), @@ -434,9 +440,12 @@ fn form_lists() -> Form { .with_child(form::Hidden::field("origin", "form-lists")) // Botonera de acciones. .with_child( - Flex::new() - .with_wrap(flex::Behavior::Wrap) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) + Container::new() + .with_flex( + Flex::new() + .with_wrap(flex::Behavior::Wrap) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + ) .with_child( Button::submit(Lc::t("btn_submit", &LOC)) .with_style(button::Style::Solid(Intent::Primary)), diff --git a/examples/intro-flex.rs b/examples/intro-flex.rs index 4e07a0bb..b13e362a 100644 --- a/examples/intro-flex.rs +++ b/examples/intro-flex.rs @@ -113,7 +113,7 @@ fn justify_block() -> Block { demo_row( Flex::new() .with_justify(justify) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(flex_item("1"))) .with_child(demo_box(flex_item("2"))) @@ -126,26 +126,26 @@ fn justify_block() -> Block { fn align_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align", &LOC)); - let align_variants: [(&str, align::Items, &str); 4] = [ + let align_variants: [(&str, flex::Align, &str); 4] = [ ( "flex_title_align_start", - align::Items::Start, - "Flex::new().with_align(align::Items::Start)", + flex::Align::Start, + "Flex::new().with_align(Align::Start)", ), ( "flex_title_align_center", - align::Items::Center, - "Flex::new().with_align(align::Items::Center)", + flex::Align::Center, + "Flex::new().with_align(Align::Center)", ), ( "flex_title_align_end", - align::Items::End, - "Flex::new().with_align(align::Items::End)", + flex::Align::End, + "Flex::new().with_align(Align::End)", ), ( "flex_title_align_stretch", - align::Items::Stretch, - "Flex::new().with_align(align::Items::Stretch)", + flex::Align::Stretch, + "Flex::new().with_align(Align::Stretch)", ), ]; for (title_key, align, code) in align_variants { @@ -155,7 +155,7 @@ fn align_block() -> Block { demo_row( Flex::new() .with_align(align) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")) .with_child(demo_box(Lc::t("flex_box_medium", &LOC))) @@ -165,13 +165,13 @@ fn align_block() -> Block { block .with_child(caption( Lc::t("flex_title_align_baseline", &LOC), - Lc::n("Flex::new().with_align(align::Items::Baseline)"), + Lc::n("Flex::new().with_align(Align::Baseline)"), )) .with_child( demo_row( Flex::new() - .with_align(align::Items::Baseline) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(flex::Align::Baseline) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem") @@ -185,26 +185,26 @@ fn align_block() -> Block { fn align_self_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align_self", &LOC)); - let align_self_variants: [(&str, align::ItemSelf, &str); 4] = [ + let align_self_variants: [(&str, flex::ItemAlign, &str); 4] = [ ( "flex_title_align_self_start", - align::ItemSelf::Start, - "FlexItem::new().with_align_self(align::ItemSelf::Start)", + flex::ItemAlign::Start, + "FlexItem::new().with_align_self(flex::ItemAlign::Start)", ), ( "flex_title_align_self_end", - align::ItemSelf::End, - "FlexItem::new().with_align_self(align::ItemSelf::End)", + flex::ItemAlign::End, + "FlexItem::new().with_align_self(flex::ItemAlign::End)", ), ( "flex_title_align_self_center", - align::ItemSelf::Center, - "FlexItem::new().with_align_self(align::ItemSelf::Center)", + flex::ItemAlign::Center, + "FlexItem::new().with_align_self(flex::ItemAlign::Center)", ), ( "flex_title_align_self_stretch", - align::ItemSelf::Stretch, - "FlexItem::new().with_align_self(align::ItemSelf::Stretch)", + flex::ItemAlign::Stretch, + "FlexItem::new().with_align_self(flex::ItemAlign::Stretch)", ), ]; for (title_key, align_self, code) in align_self_variants { @@ -213,8 +213,8 @@ fn align_self_block() -> Block { .with_child( demo_row( Flex::new() - .with_align(align::Items::Start) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(flex::Align::Start) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")) .with_child( @@ -226,13 +226,13 @@ fn align_self_block() -> Block { block .with_child(caption( Lc::t("flex_title_align_self_baseline", &LOC), - Lc::n("FlexItem::new().with_align_self(align::ItemSelf::Baseline)"), + Lc::n("FlexItem::new().with_align_self(flex::ItemAlign::Baseline)"), )) .with_child( demo_row( Flex::new() - .with_align(align::Items::Start) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(flex::Align::Start) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child( sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem") @@ -240,7 +240,7 @@ fn align_self_block() -> Block { ) .with_child( demo_box(flex_item("1")) - .with_prop(FlexItem::new().with_align_self(align::ItemSelf::Baseline)), + .with_prop(FlexItem::new().with_align_self(flex::ItemAlign::Baseline)), ) .with_child(sized_box(Lc::t("flex_box_tall", &LOC), "2.5rem 1rem")), ) @@ -249,41 +249,41 @@ fn align_self_block() -> Block { fn align_content_block() -> Block { let mut block = Block::new().with_title(Lc::t("flex_block_title_align_content", &LOC)); - let align_content_variants: [(&str, align::Content, &str); 7] = [ + let align_content_variants: [(&str, flex::AlignContent, &str); 7] = [ ( "flex_title_align_content_start", - align::Content::Start, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Start)", + flex::AlignContent::Start, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Start)", ), ( "flex_title_align_content_end", - align::Content::End, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::End)", + flex::AlignContent::End, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::End)", ), ( "flex_title_align_content_center", - align::Content::Center, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Center)", + flex::AlignContent::Center, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Center)", ), ( "flex_title_align_content_between", - align::Content::SpaceBetween, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceBetween)", + flex::AlignContent::SpaceBetween, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceBetween)", ), ( "flex_title_align_content_around", - align::Content::SpaceAround, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceAround)", + flex::AlignContent::SpaceAround, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceAround)", ), ( "flex_title_align_content_evenly", - align::Content::SpaceEvenly, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::SpaceEvenly)", + flex::AlignContent::SpaceEvenly, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::SpaceEvenly)", ), ( "flex_title_align_content_stretch", - align::Content::Stretch, - "Flex::new().with_wrap(Behavior::Wrap).with_align_content(align::Content::Stretch)", + flex::AlignContent::Stretch, + "Flex::new().with_wrap(Behavior::Wrap).with_align_content(AlignContent::Stretch)", ), ]; for (title_key, align_content, code) in align_content_variants { @@ -291,7 +291,7 @@ fn align_content_block() -> Block { Flex::new() .with_wrap(flex::Behavior::Wrap) .with_align_content(align_content) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_prop(PropsOp::add_style("max-width", "21rem")) .with_prop(PropsOp::add_style("min-height", "11rem")); @@ -316,7 +316,7 @@ fn grow_shrink_block() -> Block { Lc::n("FlexItem::new().with_grow(flex::ItemGrow::Is1)"), )) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("flex_box_fixed", &LOC))) .with_child( demo_box(Lc::t("flex_box_grows", &LOC)) @@ -329,7 +329,7 @@ fn grow_shrink_block() -> Block { Lc::n("FlexItem::new().with_shrink(flex::ItemShrink::Is0)"), )) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_prop(PropsOp::add_style("max-width", "31rem")) .with_child( demo_box(flex_item("1")).with_prop(PropsOp::add_style("width", "10.5rem")), @@ -354,7 +354,7 @@ fn other_block() -> Block { Lc::n("FlexItem::push_end()"), )) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("flex_box_start_1", &LOC))) .with_child(demo_box(Lc::t("flex_box_start_2", &LOC))) .with_child(demo_box(Lc::t("flex_box_end", &LOC)).with_prop(FlexItem::push_end())), @@ -363,8 +363,8 @@ fn other_block() -> Block { let mut wrap_row = demo_row( Flex::new() .with_wrap(flex::Behavior::Wrap) - .with_align_content(align::Content::SpaceBetween) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align_content(flex::AlignContent::SpaceBetween) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_prop(PropsOp::add_style("max-width", "20rem")) .with_prop(PropsOp::add_style("min-height", "11rem")); @@ -380,7 +380,7 @@ fn other_block() -> Block { Lc::n(concat!( "Flex::new()", ".with_wrap(Behavior::Wrap)", - ".with_align_content(align::Content::SpaceBetween)", + ".with_align_content(AlignContent::SpaceBetween)", )), )) .with_child(wrap_row) @@ -389,7 +389,7 @@ fn other_block() -> Block { Lc::n("FlexItem::new().with_order(ItemOrder::First) / .with_order(ItemOrder::Last)"), )) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_child( demo_box(Lc::n("A")) .with_prop(FlexItem::new().with_order(flex::ItemOrder::Last)), @@ -404,7 +404,7 @@ fn other_block() -> Block { ) .with_child(caption( Lc::t("flex_title_gap_none", &LOC), - Lc::n("Flex::new() (align::Gap::None por defecto)"), + Lc::n("Flex::new() (Gap::None por defecto)"), )) .with_child( demo_row(Flex::new()) @@ -414,10 +414,10 @@ fn other_block() -> Block { ) .with_child(caption( Lc::t("flex_title_gap_some", &LOC), - Lc::n("Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(1.5)))"), + Lc::n("Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(1.5)))"), )) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(1.5)))) + demo_row(Flex::new().with_gap(flex::Gap::Both(UnitValue::RelRem(1.5)))) .with_child(demo_box(flex_item("1"))) .with_child(demo_box(flex_item("2"))) .with_child(demo_box(flex_item("3"))), @@ -465,8 +465,8 @@ fn other_block() -> Block { .with_child( demo_row( Flex::new() - .with_align(align::Items::Center) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(flex::Align::Center) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(Lc::t("flex_box_file", &LOC))) .with_child(demo_box(Lc::t("flex_box_edit", &LOC))) @@ -530,8 +530,10 @@ fn flex_item(n: impl Into) -> Lc { } // Fila de demostracion con fondo gris para visualizar los limites del propio contenedor flex. -fn demo_row(flex: Flex) -> Flex { - flex.with_prop(PropsOp::add_classes("flex-demo-row")) +fn demo_row(flex: Flex) -> Container { + Container::new() + .with_prop(PropsOp::add_classes("flex-demo-row")) + .with_flex(flex) } // Titulo y fragmento de codigo que introducen cada demostracion. diff --git a/examples/intro-grid.rs b/examples/intro-grid.rs deleted file mode 100644 index c22c3912..00000000 --- a/examples/intro-grid.rs +++ /dev/null @@ -1,544 +0,0 @@ -use pagetop::prelude::*; - -include_locales!(LOC from "examples/locale"); - -struct IntroGrid; - -#[async_trait] -impl Extension for IntroGrid { - fn dependencies(&self) -> Vec { - vec![&pagetop_bootsier::Bootsier] - } - - fn configure_router(&self, router: Router) -> Router { - router.route("/", web::get(intro_grid)) - } -} - -async fn intro_grid(request: HttpRequest) -> Result { - Page::new(request) - .with_assets(demo_box_styles()) - .with_assets(demo_row_styles()) - .with_child( - Intro::default() - .with_opening(IntroOpening::Custom) - .with_title(Lc::n("PageTop")) - .with_slogan(Lc::t("grid_slogan", &LOC)) - .with_button(None::<(Lc, Route)>) - .with_child(columns_block()) - .with_child(rows_gap_block()) - .with_child(placement_block()) - .with_child(alignment_block()) - .with_child(content_alignment_block()) - .with_child(item_alignment_block()) - .with_child(auto_flow_block()) - .with_child(auto_tracks_block()) - .with_child(responsive_block()) - .with_child(layout_block()), - ) - .render() - .await -} - -fn columns_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_columns", &LOC)) - .with_child(caption( - Lc::t("grid_title_columns_thirds", &LOC), - Lc::n("Grid::new().with_columns(Tracks::repeat(3, AxisTrack::Fraction(1.0)))"), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(3, grid::AxisTrack::Fraction(1.0))) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))), - ) - .with_child(caption( - Lc::t("grid_title_columns_sidebar", &LOC), - Lc::n(concat!( - "Grid::new().with_columns(Tracks::new()", - ".with_track(AxisTrack::Fixed(UnitValue::Px(140)))", - ".with_track(AxisTrack::Fraction(1.0)))", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns( - grid::Tracks::new() - .with_track(grid::AxisTrack::Fixed(UnitValue::Px(140))) - .with_track(grid::AxisTrack::Fraction(1.0)), - ) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(Lc::t("grid_box_sidebar", &LOC))) - .with_child(demo_box(Lc::t("grid_box_content", &LOC))), - ) - .with_child(caption( - Lc::t("grid_title_columns_min_max", &LOC), - Lc::n(concat!( - "Grid::new().with_columns(Tracks::new()", - ".with_track(AxisTrack::MinContent)", - ".with_track(AxisTrack::Fraction(1.0))", - ".with_track(AxisTrack::MaxContent))", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns( - grid::Tracks::new() - .with_track(grid::AxisTrack::MinContent) - .with_track(grid::AxisTrack::Fraction(1.0)) - .with_track(grid::AxisTrack::MaxContent), - ) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(Lc::t("grid_box_min_content", &LOC))) - .with_child(demo_box(Lc::t("grid_box_fraction", &LOC))) - .with_child(demo_box(Lc::t("grid_box_max_content", &LOC))), - ) -} - -fn rows_gap_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_rows_gap", &LOC)) - .with_child(caption( - Lc::t("grid_title_rows", &LOC), - Lc::n(concat!( - "Grid::new()", - ".with_columns(Tracks::repeat(2, AxisTrack::Fraction(1.0)))", - ".with_rows(Tracks::repeat(2, AxisTrack::Fixed(UnitValue::RelRem(3.0))))", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(2, grid::AxisTrack::Fraction(1.0))) - .with_rows(grid::Tracks::repeat( - 2, - grid::AxisTrack::Fixed(UnitValue::RelRem(3.0)), - )) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))) - .with_child(demo_box(grid_box("4"))), - ) - .with_child(caption( - Lc::t("grid_title_gap_distinct", &LOC), - Lc::n(concat!( - "Grid::new().with_gap(align::Gap::Distinct {", - " row: UnitValue::RelRem(1.5), column: UnitValue::RelRem(0.25) })", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(3, grid::AxisTrack::Fraction(1.0))) - .with_gap(align::Gap::Distinct { - row: UnitValue::RelRem(1.5), - column: UnitValue::RelRem(0.25), - }), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))) - .with_child(demo_box(grid_box("4"))) - .with_child(demo_box(grid_box("5"))) - .with_child(demo_box(grid_box("6"))), - ) -} - -fn placement_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_placement", &LOC)) - .with_child(caption( - Lc::t("grid_title_placement", &LOC), - Lc::n(concat!( - "GridItem::new().with_column(ItemPlacement::Span(2))", - " / .with_column(ItemPlacement::Range(2, 4))", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(4, grid::AxisTrack::Fraction(1.0))) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(Lc::t("grid_box_plain", &LOC))) - .with_child( - demo_box(Lc::t("grid_box_span_2", &LOC)) - .with_prop(GridItem::new().with_column(grid::ItemPlacement::Span(2))), - ) - .with_child(demo_box(Lc::t("grid_box_plain", &LOC))) - .with_child( - demo_box(Lc::t("grid_box_range", &LOC)).with_prop( - GridItem::new() - .with_column(grid::ItemPlacement::Range(2, 4)) - .with_row(grid::ItemPlacement::Line(2)), - ), - ), - ) -} - -fn alignment_block() -> Block { - let mut block = Block::new().with_title(Lc::t("grid_block_title_alignment", &LOC)); - - let variants: [(&str, grid::DefaultJustify, align::Items, &str); 4] = [ - ( - "grid_title_alignment_start", - grid::DefaultJustify::Start, - align::Items::Start, - concat!( - "Grid::new().with_justify_items(DefaultJustify::Start)", - ".with_align_items(align::Items::Start)", - ), - ), - ( - "grid_title_alignment_center", - grid::DefaultJustify::Center, - align::Items::Center, - concat!( - "Grid::new().with_justify_items(DefaultJustify::Center)", - ".with_align_items(align::Items::Center)", - ), - ), - ( - "grid_title_alignment_end", - grid::DefaultJustify::End, - align::Items::End, - concat!( - "Grid::new().with_justify_items(DefaultJustify::End)", - ".with_align_items(align::Items::End)", - ), - ), - ( - "grid_title_alignment_stretch", - grid::DefaultJustify::Stretch, - align::Items::Stretch, - concat!( - "Grid::new().with_justify_items(DefaultJustify::Stretch)", - ".with_align_items(align::Items::Stretch)", - ), - ), - ]; - for (title_key, justify_items, align_items, code) in variants { - block = block - .with_child(caption(Lc::t(title_key, &LOC), Lc::n(code))) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat( - 3, - grid::AxisTrack::Fixed(UnitValue::Px(96)), - )) - .with_rows(grid::Tracks::repeat( - 1, - grid::AxisTrack::Fixed(UnitValue::RelRem(4.5)), - )) - .with_justify_items(justify_items) - .with_align_items(align_items) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))), - ); - } - block -} - -fn content_alignment_block() -> Block { - let mut block = Block::new().with_title(Lc::t("grid_block_title_content_alignment", &LOC)); - - let variants: [(&str, grid::ContentJustify, &str); 4] = [ - ( - "grid_title_content_alignment_start", - grid::ContentJustify::Start, - "Grid::new().with_justify_content(ContentJustify::Start)", - ), - ( - "grid_title_content_alignment_center", - grid::ContentJustify::Center, - "Grid::new().with_justify_content(ContentJustify::Center)", - ), - ( - "grid_title_content_alignment_between", - grid::ContentJustify::SpaceBetween, - "Grid::new().with_justify_content(ContentJustify::SpaceBetween)", - ), - ( - "grid_title_content_alignment_evenly", - grid::ContentJustify::SpaceEvenly, - "Grid::new().with_justify_content(ContentJustify::SpaceEvenly)", - ), - ]; - for (title_key, justify_content, code) in variants { - block = block - .with_child(caption(Lc::t(title_key, &LOC), Lc::n(code))) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat( - 3, - grid::AxisTrack::Fixed(UnitValue::Px(72)), - )) - .with_justify_content(justify_content) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_prop(PropsOp::add_style("max-width", "26rem")) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))), - ); - } - block -} - -fn item_alignment_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_item_alignment", &LOC)) - .with_child(caption( - Lc::t("grid_title_item_alignment", &LOC), - Lc::n(concat!( - "GridItem::new()", - ".with_justify_self(ItemJustify::End)", - ".with_align_self(align::ItemSelf::End)", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat( - 3, - grid::AxisTrack::Fixed(UnitValue::Px(96)), - )) - .with_rows(grid::Tracks::repeat( - 1, - grid::AxisTrack::Fixed(UnitValue::RelRem(4.5)), - )) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child( - demo_box(Lc::t("grid_box_overridden", &LOC)).with_prop( - GridItem::new() - .with_justify_self(grid::ItemJustify::End) - .with_align_self(align::ItemSelf::End), - ), - ) - .with_child(demo_box(grid_box("3"))), - ) -} - -fn auto_flow_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_auto_flow", &LOC)) - .with_child(caption( - Lc::t("grid_title_auto_flow_row", &LOC), - Lc::n("Grid::new().with_auto_flow(AutoFlow::Row) (por defecto)"), - )) - .with_child(dense_demo(grid::AutoFlow::Row)) - .with_child(caption( - Lc::t("grid_title_auto_flow_dense", &LOC), - Lc::n("Grid::new().with_auto_flow(AutoFlow::RowDense)"), - )) - .with_child(dense_demo(grid::AutoFlow::RowDense)) -} - -fn dense_demo(auto_flow: grid::AutoFlow) -> Grid { - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(4, grid::AxisTrack::Fraction(1.0))) - .with_auto_flow(auto_flow) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child( - demo_box(Lc::t("grid_box_span_2", &LOC)) - .with_prop(GridItem::new().with_column(grid::ItemPlacement::Span(2))), - ) - .with_child( - demo_box(Lc::t("grid_box_span_2", &LOC)) - .with_prop(GridItem::new().with_column(grid::ItemPlacement::Span(2))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))) -} - -fn auto_tracks_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_auto_tracks", &LOC)) - .with_child(caption( - Lc::t("grid_title_auto_tracks", &LOC), - Lc::n(concat!( - "Grid::new()", - ".with_columns(Tracks::repeat(3, AxisTrack::Fraction(1.0)))", - ".with_auto_rows(AxisTrack::Fixed(UnitValue::RelRem(3.0)))", - )), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(3, grid::AxisTrack::Fraction(1.0))) - .with_auto_rows(grid::AxisTrack::Fixed(UnitValue::RelRem(3.0))) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_child(demo_box(grid_box("1"))) - .with_child(demo_box(grid_box("2"))) - .with_child(demo_box(grid_box("3"))) - .with_child(demo_box(grid_box("4"))) - .with_child(demo_box(grid_box("5"))) - .with_child(demo_box(grid_box("6"))), - ) -} - -fn responsive_block() -> Block { - let mut row = demo_grid( - Grid::new() - .with_columns(grid::Tracks::repeat(1, grid::AxisTrack::Fraction(1.0))) - .with_columns_at( - Breakpoint::Sm, - grid::Tracks::repeat(2, grid::AxisTrack::Fraction(1.0)), - ) - .with_columns_at( - Breakpoint::Md, - grid::Tracks::repeat(3, grid::AxisTrack::Fraction(1.0)), - ) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ); - for n in 1..=6 { - row = row.with_child(demo_box(card_label(n))); - } - Block::new() - .with_title(Lc::t("grid_block_title_responsive", &LOC)) - .with_child(caption( - Lc::t("grid_title_responsive", &LOC), - Lc::n(concat!( - "Grid::new()", - ".with_columns(Tracks::repeat(1, AxisTrack::Fraction(1.0)))", - ".with_columns_at(Breakpoint::Sm, Tracks::repeat(2, AxisTrack::Fraction(1.0)))", - ".with_columns_at(Breakpoint::Md, Tracks::repeat(3, AxisTrack::Fraction(1.0)))", - )), - )) - .with_child(row) -} - -fn layout_block() -> Block { - Block::new() - .with_title(Lc::t("grid_block_title_layout", &LOC)) - .with_child(caption( - Lc::t("grid_title_layout", &LOC), - Lc::t("grid_desc_layout", &LOC), - )) - .with_child( - demo_grid( - Grid::new() - .with_columns( - grid::Tracks::new() - .with_track(grid::AxisTrack::Fixed(UnitValue::Px(120))) - .with_track(grid::AxisTrack::Fraction(1.0)) - .with_track(grid::AxisTrack::Fixed(UnitValue::Px(120))), - ) - .with_rows( - grid::Tracks::new() - .with_track(grid::AxisTrack::Auto) - .with_track(grid::AxisTrack::Fraction(1.0)) - .with_track(grid::AxisTrack::Auto), - ) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), - ) - .with_prop(PropsOp::add_style("min-height", "18rem")) - .with_child( - demo_box(Lc::t("grid_box_header", &LOC)) - .with_prop(GridItem::new().with_column(grid::ItemPlacement::Span(3))), - ) - .with_child(demo_box(Lc::t("grid_box_nav", &LOC))) - .with_child(demo_box(Lc::t("grid_box_content", &LOC))) - .with_child(demo_box(Lc::t("grid_box_aside", &LOC))) - .with_child( - demo_box(Lc::t("grid_box_footer", &LOC)) - .with_prop(GridItem::new().with_column(grid::ItemPlacement::Span(3))), - ), - ) -} - -// **< HELPERS >************************************************************************************ - -// Aspecto fijo de las cajas de muestra: esquinas rectas y borde claro, a diferencia de las cajas -// redondeadas de `intro-flex.rs`, para distinguir a simple vista los ejemplos de Grid de los de Flex. -fn demo_box_styles() -> AssetsOp { - AssetsOp::add_responsive_styles( - None, - "grid-demo-box", - [ - ("background-color", "#0d6efd"), - ("color", "#fff"), - ("border", "2px solid #6ea8fe"), - ("min-width", "3rem"), - ("width", "auto"), - ("max-width", "none"), - ("margin", "0"), - ("text-align", "center"), - ], - ) -} - -// Aspecto fijo de las filas de muestra. -fn demo_row_styles() -> AssetsOp { - AssetsOp::add_responsive_styles( - None, - "grid-demo-row", - [ - ("background-color", "#f1f3f5"), - ("width", "100%"), - ("max-width", "none"), - ("margin", "0 0 1.5rem"), - ("padding", "0.75rem"), - ], - ) -} - -// Caja con el relleno vertical estandar del resto de ejemplos. -fn demo_box(label: Lc) -> Container { - Container::new() - .with_prop(PropsOp::add_classes("grid-demo-box")) - .with_prop(PropsOp::add_style("padding", "0.5rem 1rem")) - .with_child(Html::with(move |cx| html! { (label.using(cx)) })) -} - -// Etiqueta "Box N" para las cajas que sólo se distinguen por su posición. -fn grid_box(n: impl Into) -> Lc { - Lc::t("grid_box_label", &LOC).with_arg("n", n) -} - -// Etiqueta "Tarjeta N" para las cajas de la rejilla responsive. -fn card_label(n: usize) -> Lc { - Lc::t("grid_box_card", &LOC).with_arg("n", n.to_string()) -} - -// Rejilla de demostracion con fondo gris para visualizar los limites del propio contenedor grid. -fn demo_grid(grid: Grid) -> Grid { - grid.with_prop(PropsOp::add_classes("grid-demo-row")) -} - -// Titulo y fragmento de codigo que introducen cada demostracion. -fn caption(title: Lc, code: Lc) -> Html { - Html::with(move |cx| { - html! { - h3 { (title.using(cx)) } - p { code { (code.using(cx)) } } - } - }) -} - -#[pagetop::main] -async fn main() -> std::io::Result<()> { - Application::prepare(&IntroGrid).await.run().await -} diff --git a/examples/intro-responsive.rs b/examples/intro-responsive.rs index 6306a678..fd53d4fe 100644 --- a/examples/intro-responsive.rs +++ b/examples/intro-responsive.rs @@ -52,7 +52,7 @@ fn activation_block() -> Block { Lc::n("Flex::at(Breakpoint::Md)"), )) .with_child( - demo_row(Flex::at(Breakpoint::Md).with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::at(Breakpoint::Md).with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("responsive_box_nav_home", &LOC))) .with_child(demo_box(Lc::t("responsive_box_nav_products", &LOC))) .with_child(demo_box(Lc::t("responsive_box_nav_about", &LOC))) @@ -76,7 +76,7 @@ fn direction_block() -> Block { Flex::new() .with_direction(flex::Direction::Column) .with_direction_at(Breakpoint::Md, flex::Direction::RowReverse) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(sized_box( Lc::t("responsive_box_image", &LOC), @@ -124,7 +124,7 @@ fn justify_align_block() -> Block { "Flex::new()", ".with_justify(ContentJustify::Center)", ".with_justify_at(Breakpoint::Md, ContentJustify::SpaceBetween)", - ".with_align(align::Items::Center)", + ".with_align(Align::Center)", )), )) .with_child( @@ -132,8 +132,8 @@ fn justify_align_block() -> Block { Flex::new() .with_justify(flex::ContentJustify::Center) .with_justify_at(Breakpoint::Md, flex::ContentJustify::SpaceBetween) - .with_align(align::Items::Center) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))), + .with_align(flex::Align::Center) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), ) .with_child(demo_box(Lc::t("responsive_box_logo", &LOC))) .with_child(demo_box(Lc::t("responsive_box_menu", &LOC))), @@ -151,7 +151,7 @@ fn order_block() -> Block { )), )) .with_child( - demo_row(Flex::at(Breakpoint::Lg).with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + demo_row(Flex::at(Breakpoint::Lg).with_gap(flex::Gap::Both(UnitValue::RelRem(0.5)))) .with_child(demo_box(Lc::t("responsive_box_content", &LOC))) .with_child(demo_box(Lc::t("responsive_box_sidebar", &LOC)).with_prop( FlexItem::new().with_order_at(Breakpoint::Lg, flex::ItemOrder::First), @@ -166,7 +166,7 @@ fn gap_grow_block() -> Block { Lc::t("responsive_title_gap_grow", &LOC), Lc::n(concat!( "Flex::new()", - ".with_gap(align::Gap::Both(RelRem(0.5)))", + ".with_gap(Gap::Both(RelRem(0.5)))", ".with_gap_at(Breakpoint::Md, Gap::Both(RelRem(1.5)))", " + FlexItem::new().with_grow_at(Breakpoint::Md, ItemGrow::Is1)", )), @@ -174,8 +174,8 @@ fn gap_grow_block() -> Block { .with_child( demo_row( Flex::new() - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) - .with_gap_at(Breakpoint::Md, align::Gap::Both(UnitValue::RelRem(1.5))), + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))) + .with_gap_at(Breakpoint::Md, flex::Gap::Both(UnitValue::RelRem(1.5))), ) .with_child(demo_box(Lc::t("responsive_box_file", &LOC))) .with_child(demo_box(Lc::t("responsive_box_edit", &LOC))) @@ -245,8 +245,10 @@ fn card_label(n: usize) -> Lc { } // Fila de demostracion con fondo gris para visualizar los limites del propio contenedor flex. -fn demo_row(flex: Flex) -> Flex { - flex.with_prop(PropsOp::add_classes("flex-demo-row")) +fn demo_row(flex: Flex) -> Container { + Container::new() + .with_prop(PropsOp::add_classes("flex-demo-row")) + .with_flex(flex) } // Titulo y fragmento de codigo que introducen cada demostracion. diff --git a/examples/intro-spacing.rs b/examples/intro-spacing.rs index eed4c38f..75b9e24b 100644 --- a/examples/intro-spacing.rs +++ b/examples/intro-spacing.rs @@ -66,7 +66,7 @@ fn padding_block() -> Block { block = block .with_child(caption(Lc::t(title_key, &LOC), Lc::n(code))) .with_child( - demo_row(Flex::new().with_gap(align::Gap::Both(UnitValue::RelRem(0.5)))) + 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))), ); } @@ -229,8 +229,10 @@ fn box_sample() -> Lc { } // Fila de demostración con fondo gris para visualizar los límites de cada caja. -fn demo_row(flex: Flex) -> Flex { - flex.with_prop(PropsOp::add_classes("spacing-demo-row")) +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. diff --git a/examples/locale/en-US/intro-grid.ftl b/examples/locale/en-US/intro-grid.ftl deleted file mode 100644 index c0c3c241..00000000 --- a/examples/locale/en-US/intro-grid.ftl +++ /dev/null @@ -1,49 +0,0 @@ -grid_slogan = CSS Grid positioning -grid_block_title_columns = Columns -grid_block_title_rows_gap = Rows and gap -grid_block_title_placement = Item placement -grid_block_title_alignment = Align items -grid_block_title_content_alignment = Justify content -grid_block_title_item_alignment = Individual alignment -grid_block_title_auto_flow = Auto-placement algorithm -grid_block_title_auto_tracks = Implicit tracks -grid_block_title_responsive = Responsive grid -grid_block_title_layout = Composite structure: page layout - -grid_title_columns_thirds = Three equal columns -grid_title_columns_sidebar = Fixed sidebar + flexible content -grid_title_columns_min_max = Min-content, fraction, max-content -grid_title_rows = Fixed-height rows -grid_title_gap_distinct = Different row and column spacing -grid_title_placement = Column spanning and explicit ranges -grid_title_alignment_start = Justify/align items: start -grid_title_alignment_center = Justify/align items: center -grid_title_alignment_end = Justify/align items: end -grid_title_alignment_stretch = Justify/align items: stretch -grid_title_content_alignment_start = Justify content: start -grid_title_content_alignment_center = Justify content: center -grid_title_content_alignment_between = Justify content: space between -grid_title_content_alignment_evenly = Justify content: space evenly -grid_title_item_alignment = Overriding the container's alignment on a single item -grid_title_auto_flow_row = Default flow, leaves a gap -grid_title_auto_flow_dense = Dense flow, fills the gap -grid_title_auto_tracks = Equal-height implicit rows -grid_title_responsive = One column on mobile, two on tablet, three on desktop -grid_title_layout = Header and footer spanning three columns, flexible content area -grid_desc_layout = Grid::new() with fixed side columns, a flexible center column and explicit spans - -grid_box_label = Box { $n } -grid_box_card = Card { $n } -grid_box_sidebar = Sidebar -grid_box_content = Content -grid_box_min_content = Min-content -grid_box_fraction = 1fr -grid_box_max_content = Max-content -grid_box_plain = Plain -grid_box_span_2 = Spans 2 columns -grid_box_range = Columns 2 to 3 -grid_box_overridden = Overridden -grid_box_header = Header -grid_box_nav = Navigation -grid_box_aside = Aside -grid_box_footer = Footer diff --git a/examples/locale/es-ES/intro-grid.ftl b/examples/locale/es-ES/intro-grid.ftl deleted file mode 100644 index 5066795a..00000000 --- a/examples/locale/es-ES/intro-grid.ftl +++ /dev/null @@ -1,49 +0,0 @@ -grid_slogan = Posicionamiento con CSS Grid -grid_block_title_columns = Columnas -grid_block_title_rows_gap = Filas y espaciado -grid_block_title_placement = Colocación de elementos -grid_block_title_alignment = Alineación de elementos -grid_block_title_content_alignment = Justificación de contenido -grid_block_title_item_alignment = Alineación individual -grid_block_title_auto_flow = Algoritmo de colocación automática -grid_block_title_auto_tracks = Pistas implícitas -grid_block_title_responsive = Rejilla responsive -grid_block_title_layout = Estructura compuesta: maquetación de página - -grid_title_columns_thirds = Tres columnas iguales -grid_title_columns_sidebar = Barra lateral fija + contenido flexible -grid_title_columns_min_max = Contenido mínimo, fracción, contenido máximo -grid_title_rows = Filas de altura fija -grid_title_gap_distinct = Espaciado distinto entre filas y columnas -grid_title_placement = Columnas abarcadas y rangos explícitos -grid_title_alignment_start = Justificación/alineación de elementos: inicio -grid_title_alignment_center = Justificación/alineación de elementos: centro -grid_title_alignment_end = Justificación/alineación de elementos: final -grid_title_alignment_stretch = Justificación/alineación de elementos: estirar -grid_title_content_alignment_start = Justificación de contenido: inicio -grid_title_content_alignment_center = Justificación de contenido: centro -grid_title_content_alignment_between = Justificación de contenido: espacio entre elementos -grid_title_content_alignment_evenly = Justificación de contenido: espacio repartido a partes iguales -grid_title_item_alignment = Sobrescribir la alineación del contenedor en un único elemento -grid_title_auto_flow_row = Flujo por defecto, deja un hueco -grid_title_auto_flow_dense = Flujo denso, rellena el hueco -grid_title_auto_tracks = Filas implícitas de igual altura -grid_title_responsive = Una columna en móvil, dos en tableta, tres en escritorio -grid_title_layout = Cabecera y pie abarcando tres columnas, área de contenido flexible -grid_desc_layout = Grid::new() con columnas laterales fijas, una columna central flexible y abarcados explícitos - -grid_box_label = Caja { $n } -grid_box_card = Tarjeta { $n } -grid_box_sidebar = Barra lateral -grid_box_content = Contenido -grid_box_min_content = Contenido mínimo -grid_box_fraction = 1fr -grid_box_max_content = Contenido máximo -grid_box_plain = Normal -grid_box_span_2 = Abarca 2 columnas -grid_box_range = Columnas 2 a 3 -grid_box_overridden = Sobrescrita -grid_box_header = Cabecera -grid_box_nav = Navegación -grid_box_aside = Aparte -grid_box_footer = Pie diff --git a/extensions/pagetop-admin/src/component/admin_frame.rs b/extensions/pagetop-admin/src/component/admin_frame.rs index 380beb47..cbdfd371 100644 --- a/extensions/pagetop-admin/src/component/admin_frame.rs +++ b/extensions/pagetop-admin/src/component/admin_frame.rs @@ -9,8 +9,8 @@ use crate::registry; /// Renderiza breadcrumb, encabezado (título + acciones locales), tareas locales (pestañas) y el /// contenido de la página. No incluye ningún menú de navegación: mostrar las secciones del panel /// (el "sidebar" o el "menú superior", según el tema) es responsabilidad del tema que intercepte -/// [`CoreTemplates::Admin`](pagetop::core::theme::CoreTemplates::Admin) (por ejemplo -/// `pagetop-bootsier`) leyendo directamente [`crate::registry::global()`]. El tema básico de +/// [`CoreTemplates::Admin`](pagetop::core::theme::CoreTemplates::Admin) -- por ejemplo +/// `pagetop-bootsier` -- leyendo directamente [`crate::registry::global()`]. El tema básico de /// PageTop no lo hace, y una página de administración sigue siendo completamente navegable sin él, /// a través del *dashboard* (`/admin`), las páginas de sección y este mismo breadcrumb. /// diff --git a/extensions/pagetop-admin/src/lib.rs b/extensions/pagetop-admin/src/lib.rs index 7e3d3e35..dfa7eab5 100644 --- a/extensions/pagetop-admin/src/lib.rs +++ b/extensions/pagetop-admin/src/lib.rs @@ -162,7 +162,7 @@ impl Extension for Admin { registry::build(); // Región neutra del core: cualquier tema puede decidir renderizarla (o no) sin que - // `pagetop-admin` dependa de ninguno en concreto (ver `CoreRegions::Aside`). + // `pagetop-admin` dependa de ninguno en concreto -- ver `CoreRegions::Aside`. InRegion::Global(&CoreRegions::Aside).add(component::AdminMenu::new()); } diff --git a/extensions/pagetop-bootsier/src/lib.rs b/extensions/pagetop-bootsier/src/lib.rs index 54528768..7991078d 100644 --- a/extensions/pagetop-bootsier/src/lib.rs +++ b/extensions/pagetop-bootsier/src/lib.rs @@ -125,33 +125,16 @@ impl Extension for Bootsier { #[async_trait] impl Theme for Bootsier { - #[rustfmt::skip] - fn breakpoint_entry(&self, bp: Breakpoint) -> BreakpointEntry { - use Breakpoint::*; - - // Anchos mínimos de la rejilla de Bootstrap (`$grid-breakpoints`). Bootstrap no tiene un - // punto de corte por encima de `xxl`, así que `Xxxl` se resuelve como `xxl`. - match bp { - Xs => BreakpointEntry { breakpoint: Xs, name: "xs", min_width: "" }, - Sm => BreakpointEntry { breakpoint: Sm, name: "sm", min_width: "576px" }, - Md => BreakpointEntry { breakpoint: Md, name: "md", min_width: "768px" }, - Lg => BreakpointEntry { breakpoint: Lg, name: "lg", min_width: "992px" }, - Xl => BreakpointEntry { breakpoint: Xl, name: "xl", min_width: "1200px" }, - Xxl => BreakpointEntry { breakpoint: Xxl, name: "xxl", min_width: "1400px" }, - Xxxl => BreakpointEntry { breakpoint: Xxxl, name: "xxl", min_width: "1400px" }, - } - } - fn intent_color(&self, intent: Intent) -> &'static str { theme::BootsierColors::from(intent).as_str() } - fn setup_component(&self, component: &mut dyn Component, cx: &mut Context) { + fn setup_component(&self, component: &mut dyn Component, _cx: &mut Context) { setup_component!(component, { Badge => |c| theme::bs::badge::setup(c), Brand => |c| theme::bs::brand::setup(c), Button => |c| theme::bs::button::setup(c), - Container => |c| theme::bs::container::setup(c, cx), + Container => |c| theme::bs::container::setup(c), Dialog => |c| theme::bs::dialog::setup(c), Dropdown => |c| theme::bs::dropdown::setup(c), Image => |c| theme::bs::image::setup(c), diff --git a/extensions/pagetop-bootsier/src/theme.rs b/extensions/pagetop-bootsier/src/theme.rs index f093ac42..18b15f8a 100644 --- a/extensions/pagetop-bootsier/src/theme.rs +++ b/extensions/pagetop-bootsier/src/theme.rs @@ -11,10 +11,13 @@ //! //! # Plantillas //! -//! Bootsier maqueta las dos plantillas de PageTop ([`CoreTemplates`]): `Standard`, con cabecera, -//! contenido y pie, que es la plantilla por defecto de cualquier página; y `Admin`, con la shell -//! completa de administración (barra superior + barra lateral + área de contenido), que se activa -//! creando la página con [`Page::admin()`] en lugar de [`Page::new()`]. +//! Bootsier maqueta las dos plantillas de PageTop +//! ([`CoreTemplates`](pagetop::prelude::CoreTemplates)): `Standard`, con +//! cabecera, contenido y pie, que es la plantilla por defecto de cualquier página; y `Admin`, con +//! la shell completa de AdminLTE 4 (barra superior + barra lateral + área de contenido), que se +//! activa creando la página con [`Page::admin()`](pagetop::response::Page::admin) en lugar de +//! [`Page::new()`](pagetop::response::Page::new). No define sus propias variantes de plantilla: +//! intercepta el componente `Template` en `render_component()` (ver `bs::layout`). //! //! ```rust,no_run //! use pagetop::prelude::*; @@ -29,10 +32,6 @@ //! } //! ``` //! -//! [`CoreTemplates`]: pagetop::prelude::CoreTemplates -//! [`Page::admin()`]: pagetop::response::Page::admin -//! [`Page::new()`]: pagetop::response::Page::new -//! //! # Barra lateral //! //! Registra elementos en [`BootsierRegions::Sidebar`](bs::BootsierRegions::Sidebar) para poblar la @@ -89,8 +88,6 @@ //! })); //! ``` -use pagetop::prelude::*; - pub mod bs; pub mod class; @@ -116,35 +113,3 @@ pub use bs::form::textarea::TextareaBootsier; pub use bs::nav::NavBootsier; #[doc(hidden)] pub use bs::navbar::NavbarBootsier; - -// Añade la clase de un punto de corte con un prefijo y un sufijo (opcional) a la cadena de clases, -// separada con un espacio de las que ya hubiera: `prefix-name-suffix`, sin `-suffix` si no hay -// sufijo, y sin `-name` si el punto de corte aplica siempre. -// -// `name` es el nombre del punto de corte según `Theme::breakpoint_entry()` en el tema activo de -// `cx`. Si el tema no le asocia un ancho mínimo (`Breakpoint::resolved()` devuelve `None`), el -// punto de corte aplica siempre y se omite `name`, igual que Bootstrap con `container` frente a -// `container-lg`, o `container-fluid` frente a `container-lg-fluid`. -pub(crate) fn push_breakpoint_class( - cx: &Context, - bp: Breakpoint, - classes: &mut String, - prefix: &str, - suffix: &str, -) { - if prefix.is_empty() { - return; - } - if !classes.is_empty() { - classes.push(' '); - } - classes.push_str(prefix); - if let Some(entry) = bp.resolved(cx) { - classes.push('-'); - classes.push_str(entry.name); - } - if !suffix.is_empty() { - classes.push('-'); - classes.push_str(suffix); - } -} diff --git a/extensions/pagetop-bootsier/src/theme/bs/badge.rs b/extensions/pagetop-bootsier/src/theme/bs/badge.rs index 913ad0d3..ab058de7 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/badge.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/badge.rs @@ -9,8 +9,8 @@ const EXTRA_COLOR: &str = "bootsier.badge.color"; /// Extensión de Bootsier para [`Badge`]. /// /// Permite forzar un color de la paleta de Bootsier ([`BootsierColors`]) en vez del que le -/// correspondería por defecto a la [`Intent`] del badge (por ejemplo, para usar `Light`/`Dark`, -/// que `Intent` no tiene). +/// correspondería por defecto a la [`Intent`] del badge -- por ejemplo, para usar `Light`/`Dark`, +/// que `Intent` no tiene. /// /// ```rust,no_run /// use pagetop::prelude::*; @@ -39,7 +39,7 @@ impl BadgeBootsier for Badge { // **< Badge SETUP >******************************************************************************** pub(crate) fn setup(badge: &mut Badge) { - // `Badge::setup()` (core) ya ha traducido la intención con `Theme::intent_color()`. La clase + // `Badge::setup()` (core) ya ha traducido la intención con `Theme::intent_color()` -- la clase // `badge-*` que hay que localizar es siempre la derivada de la `Intent`, con independencia de // que `BadgeBootsier::with_color()` fuerce un color distinto para el destino `text-bg-*`. let intent_color = BootsierColors::from(badge.intent()).as_str(); diff --git a/extensions/pagetop-bootsier/src/theme/bs/button.rs b/extensions/pagetop-bootsier/src/theme/bs/button.rs index 9ecd8f05..18b80175 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/button.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/button.rs @@ -75,9 +75,10 @@ impl ButtonBootsier for Button { pub(crate) fn setup(button: &mut Button) { button.alter_prop(PropsOp::replace_classes("button", "btn")); - // `Button::setup()` (core) ya ha traducido la intención con `Theme::intent_color()`. Aquí sólo - // queda cambiar el prefijo `button-`/`button-outline-` por el equivalente `btn-`/`btn-outline-` - // de Bootstrap, conservando el mismo nombre de color salvo que `with_color()` lo sobrescriba. + // `Button::setup()` (core) ya ha traducido la intención con `Theme::intent_color()` -- aquí + // sólo queda cambiar el prefijo `button-`/`button-outline-` por el equivalente + // `btn-`/`btn-outline-` de Bootstrap, conservando el mismo nombre de color salvo que + // `with_color()` lo sobrescriba. let override_color = button .props() .extra::(EXTRA_COLOR) diff --git a/extensions/pagetop-bootsier/src/theme/bs/container.rs b/extensions/pagetop-bootsier/src/theme/bs/container.rs index 2bb08e79..67526ab8 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/container.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/container.rs @@ -26,7 +26,7 @@ const EXTRA_WIDTH: &str = "bootsier.container.width"; /// /// let main = bs::Container::main() /// .with_id("main-page") -/// .with_width(bs::container::Width::From(Breakpoint::Lg)) +/// .with_width(bs::container::Width::From(BreakPoint::LG)) /// .with_prop(PropsOp::add_classes(class::Bg::with(BootsierColors::Light))) /// .with_prop(PropsOp::add_classes(class::Text::with(BootsierColors::Dark))) /// .with_prop(PropsOp::add_classes(class::Border::with(ScaleSize::One))) @@ -36,9 +36,9 @@ const EXTRA_WIDTH: &str = "bootsier.container.width"; pub trait ContainerBootsier { /// Establece el comportamiento del ancho para el contenedor. /// - /// Determina si el contenedor aplica los anchos máximos predefinidos para cada punto de corte, - /// o si ocupa siempre el 100% del ancho disponible, o lo hace hasta un ancho máximo explícito. - /// Ver [`Width`] para las variantes disponibles. + /// Determina si el contenedor aplica los anchos máximos predefinidos para cada punto de + /// ruptura, o si ocupa siempre el 100% del ancho disponible, o lo hace hasta un ancho máximo + /// explícito. Ver [`Width`] para las variantes disponibles. fn with_width(self, width: Width) -> Self; } @@ -55,13 +55,13 @@ impl ContainerBootsier for Container { /// Define cómo se comporta el ancho de un contenedor ([`Container`]). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum Width { - /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de corte. - /// Por debajo del menor punto de corte ocupa el 100% del ancho disponible. + /// Comportamiento por defecto, aplica los anchos máximos predefinidos para cada punto de + /// ruptura. Por debajo del menor punto de ruptura ocupa el 100% del ancho disponible. #[default] Default, - /// Aplica los anchos máximos predefinidos a partir del punto de corte indicado. Por debajo de - /// ese punto de corte ocupa el 100% del ancho disponible. - From(Breakpoint), + /// Aplica los anchos máximos predefinidos a partir del punto de ruptura indicado. Por debajo de + /// ese punto de ruptura ocupa el 100% del ancho disponible. + From(BreakPoint), /// Ocupa el 100% del ancho disponible siempre. Fluid, /// Ocupa el 100% del ancho disponible hasta un ancho máximo explícito. @@ -72,34 +72,30 @@ impl Width { const CONTAINER: &str = "container"; /// Añade la clase asociada al ancho del contenedor a la cadena de clases. - /// - /// El nombre del punto de corte se resuelve en el tema activo de `cx`. #[inline] - pub fn push_to(self, cx: &Context, classes: &mut String) { + pub fn push_to(self, classes: &mut String) { match self { - Self::Default => { - push_breakpoint_class(cx, Breakpoint::Xs, classes, Self::CONTAINER, "") - } - Self::From(bp) => push_breakpoint_class(cx, bp, classes, Self::CONTAINER, ""), + Self::Default => BreakPoint::None.push_to(classes, Self::CONTAINER, ""), + Self::From(bp) => bp.push_to(classes, Self::CONTAINER, ""), Self::Fluid | Self::FluidMax(_) => { - push_breakpoint_class(cx, Breakpoint::Xs, classes, Self::CONTAINER, "fluid") + BreakPoint::None.push_to(classes, Self::CONTAINER, "fluid") } } } /// Devuelve la clase asociada al ancho del contenedor. - pub fn to_class(self, cx: &Context) -> String { + pub fn to_class(self) -> String { let mut class = String::new(); - self.push_to(cx, &mut class); + self.push_to(&mut class); class } } // **< Container SETUP >**************************************************************************** -pub(crate) fn setup(container: &mut Container, cx: &Context) { +pub(crate) fn setup(container: &mut Container) { let width = container.props().extra_or(EXTRA_WIDTH, Width::default()); - container.alter_prop(PropsOp::prepend_classes(width.to_class(cx))); + container.alter_prop(PropsOp::prepend_classes(width.to_class())); if let Width::FluidMax(w) = width && w.is_measurable() { diff --git a/extensions/pagetop-bootsier/src/theme/bs/dropdown.rs b/extensions/pagetop-bootsier/src/theme/bs/dropdown.rs index 829f0f24..c071f3fb 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/dropdown.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/dropdown.rs @@ -168,7 +168,7 @@ pub(crate) async fn render( let auto_close = auto_close.opt_str(); let menu_classes = { let mut classes = "dropdown-menu".to_string(); - menu_align.push_to(cx, &mut classes); + menu_align.push_to(&mut classes); classes }; diff --git a/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs b/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs index c07fcceb..d6f76709 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/dropdown/props.rs @@ -112,69 +112,65 @@ impl Direction { /// Alineación horizontal del menú desplegable [`Dropdown`](crate::theme::bs::Dropdown). /// /// Permite alinear el menú al inicio o al final del botón (respetando LTR/RTL) y añadirle una -/// alineación diferente a partir de un punto de corte ([`Breakpoint`]). +/// alineación diferente a partir de un punto de ruptura ([`BreakPoint`]). #[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] pub enum MenuAlign { /// Alineación al inicio (comportamiento por defecto). #[default] Start, - /// Alineación al inicio a partir del punto de corte indicado. - StartAt(Breakpoint), - /// Alineación al inicio por defecto, y al final a partir de un punto de corte válido. - StartAndEnd(Breakpoint), + /// Alineación al inicio a partir del punto de ruptura indicado. + StartAt(BreakPoint), + /// Alineación al inicio por defecto, y al final a partir de un punto de ruptura válido. + StartAndEnd(BreakPoint), /// Alineación al final. End, - /// Alineación al final a partir del punto de corte indicado. - EndAt(Breakpoint), - /// Alineación al final por defecto, y al inicio a partir de un punto de corte válido. - EndAndStart(Breakpoint), + /// Alineación al final a partir del punto de ruptura indicado. + EndAt(BreakPoint), + /// Alineación al final por defecto, y al inicio a partir de un punto de ruptura válido. + EndAndStart(BreakPoint), } impl MenuAlign { /// Añade las clases de alineación a la cadena de clases (sin incluir la base `dropdown-menu`). - /// - /// El nombre del punto de corte se resuelve en el tema activo de `cx`. #[inline] - pub fn push_to(self, cx: &Context, classes: &mut String) { - const MENU: &str = "dropdown-menu"; - + pub fn push_to(self, classes: &mut String) { match self { // Alineación por defecto (start), no añade clases extra. Self::Start => {} // `dropdown-menu-{bp}-start` Self::StartAt(bp) => { - push_breakpoint_class(cx, bp, classes, MENU, "start"); + bp.push_to(classes, "dropdown-menu", "start"); } // `dropdown-menu-start` + `dropdown-menu-{bp}-end` Self::StartAndEnd(bp) => { - push_breakpoint_class(cx, Breakpoint::Xs, classes, MENU, "start"); - push_breakpoint_class(cx, bp, classes, MENU, "end"); + BreakPoint::None.push_to(classes, "dropdown-menu", "start"); + bp.push_to(classes, "dropdown-menu", "end"); } // `dropdown-menu-end` Self::End => { - push_breakpoint_class(cx, Breakpoint::Xs, classes, MENU, "end"); + BreakPoint::None.push_to(classes, "dropdown-menu", "end"); } // `dropdown-menu-{bp}-end` Self::EndAt(bp) => { - push_breakpoint_class(cx, bp, classes, MENU, "end"); + bp.push_to(classes, "dropdown-menu", "end"); } // `dropdown-menu-end` + `dropdown-menu-{bp}-start` Self::EndAndStart(bp) => { - push_breakpoint_class(cx, Breakpoint::Xs, classes, MENU, "end"); - push_breakpoint_class(cx, bp, classes, MENU, "start"); + BreakPoint::None.push_to(classes, "dropdown-menu", "end"); + bp.push_to(classes, "dropdown-menu", "start"); } } } /// Devuelve las clases de alineación del menú (sin incluir la base `dropdown-menu`). - pub fn to_class(self, cx: &Context) -> String { + pub fn to_class(self) -> String { let mut classes = String::new(); - self.push_to(cx, &mut classes); + self.push_to(&mut classes); classes } } diff --git a/extensions/pagetop-bootsier/src/theme/bs/layout/region.rs b/extensions/pagetop-bootsier/src/theme/bs/layout/region.rs index 3c3f296a..968d7373 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/layout/region.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/layout/region.rs @@ -97,8 +97,8 @@ impl RegionName for BootsierRegions { // **< Region RENDER >****************************************************************************** // Regiones de Bootsier: se renderizan sin el `
` envolvente que aplica -// `layout::Region::prepare()` por defecto. Sus elementos van directamente dentro del contenedor que -// los gestiona (sidebar-menu o navbar-nav). Devuelve `None` si `component` no envuelve una +// `layout::Region::prepare()` por defecto -- sus elementos van directamente dentro del contenedor +// que los gestiona (sidebar-menu o navbar-nav). Devuelve `None` si `component` no envuelve una // `BootsierRegions`, dejando que el resto de la cadena de temas (o el propio componente) resuelva // el renderizado por defecto. pub(crate) async fn render( diff --git a/extensions/pagetop-bootsier/src/theme/bs/layout/template.rs b/extensions/pagetop-bootsier/src/theme/bs/layout/template.rs index 8b80c84b..99a33065 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/layout/template.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/layout/template.rs @@ -5,10 +5,10 @@ use crate::theme::{ContainerBootsier, bs}; use crate::{ADMINLTE_VERSION, LOCALES_BOOTSIER}; // Regiones de Bootsier: se renderiza sin el `
` envolvente que aplica -// `layout::Template::prepare()` por defecto; delega en `render_standard()`/`render_admin()` según -// la variante de `CoreTemplates` que envuelva el componente. Devuelve `None` si `component` no -// envuelve una `CoreTemplates`, dejando que el resto de la cadena de temas (o el propio componente) -// resuelva el renderizado por defecto. +// `layout::Template::prepare()` por defecto -- delega en `render_standard()`/`render_admin()` +// según la variante de `CoreTemplates` que envuelva el componente. Devuelve `None` si +// `component` no envuelve una `CoreTemplates`, dejando que el resto de la cadena de temas (o +// el propio componente) resuelva el renderizado por defecto. pub(crate) async fn render( component: &layout::Template, cx: &mut Context, diff --git a/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs b/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs index 48059ec7..ab1d8a33 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/navbar/component.rs @@ -8,18 +8,16 @@ const TOGGLE_OFFCANVAS: &str = "offcanvas"; const EXTRA_LAYOUT: &str = "bootsier.navbar.layout"; const EXTRA_POSITION: &str = "bootsier.navbar.position"; +const EXTRA_EXPAND: &str = "bootsier.navbar.expand"; -/// Extensión de Bootsier para [`Navbar`]. +/// Extensión de Bootsier para [`Navbar`](crate::theme::bs::Navbar). /// /// Permite mostrar enlaces, menús y una marca de identidad en distintas disposiciones (simples, con -/// botón de despliegue o dentro de un [`Offcanvas`]), controladas por [`navbar::Layout`]. También -/// puede fijarse en la parte superior o inferior del documento mediante [`navbar::Position`]. El -/// punto de corte a partir del cual deja de colapsar se define con [`Navbar::with_expand()`]. -/// -/// [`Navbar`]: crate::theme::bs::Navbar -/// [`Offcanvas`]: crate::theme::bs::Offcanvas -/// [`navbar::Layout`]: crate::theme::bs::navbar::Layout -/// [`navbar::Position`]: crate::theme::bs::navbar::Position +/// botón de despliegue o dentro de un [`Offcanvas`](crate::theme::bs::Offcanvas)), controladas por +/// [`navbar::Layout`](crate::theme::bs::navbar::Layout). También puede fijarse en la parte superior +/// o inferior del documento mediante [`navbar::Position`](crate::theme::bs::navbar::Position), y +/// definir a partir de qué punto de ruptura deja de colapsar con +/// [`with_expand()`](Self::with_expand). /// /// # Ejemplos /// @@ -44,6 +42,7 @@ const EXTRA_POSITION: &str = "bootsier.navbar.position"; /// # use pagetop::prelude::*; /// # use pagetop_bootsier::theme::*; /// let navbar = bs::Navbar::simple_toggle() +/// .with_expand(BreakPoint::MD) /// .with_item(bs::navbar::Item::nav( /// bs::Nav::new() /// .with_item(bs::nav::Item::link(Lc::n("Home"), "/")) @@ -89,7 +88,7 @@ const EXTRA_POSITION: &str = "bootsier.navbar.position"; /// .with_route(Route::from("/")); /// /// let navbar = bs::Navbar::brand_right(brand) -/// .with_expand(Breakpoint::Lg) +/// .with_expand(BreakPoint::LG) /// .with_item(bs::navbar::Item::nav( /// bs::Nav::new() /// .with_kind(bs::nav::Kind::Pills) @@ -152,6 +151,9 @@ pub trait NavbarBootsier { /// Crea una barra de navegación con **marca de identidad** y contenido en **offcanvas**. fn offcanvas_brand_right(brand: Brand, oc: bs::Offcanvas) -> Self; + /// Define a partir de qué punto de ruptura la barra de navegación deja de colapsar. + fn with_expand(self, bp: BreakPoint) -> Self; + /// Define dónde se mostrará la barra de navegación dentro del documento. fn with_position(self, position: bs::navbar::Position) -> Self; } @@ -185,6 +187,11 @@ impl NavbarBootsier for Navbar { navbar } + fn with_expand(mut self, bp: BreakPoint) -> Self { + self.alter_prop(PropsOp::set_extra(EXTRA_EXPAND, bp)); + self + } + fn with_position(mut self, position: bs::navbar::Position) -> Self { self.alter_prop(PropsOp::set_extra(EXTRA_POSITION, position)); self @@ -194,10 +201,14 @@ impl NavbarBootsier for Navbar { // **< Navbar SETUP >******************************************************************************* pub(crate) fn setup(navbar: &mut Navbar) { + // Sin `with_expand()`, colapsa por debajo de 768px, igual que el tema Basic (que no tiene + // punto de ruptura configurable y siempre usa ese umbral, ver `static/css/basic.css`). + let expand = navbar.props().extra_or(EXTRA_EXPAND, BreakPoint::MD); let position = navbar .props() .extra_or(EXTRA_POSITION, bs::navbar::Position::default()); let mut classes = String::new(); + expand.push_to(&mut classes, "navbar-expand", ""); position.push_to(&mut classes); if !classes.is_empty() { navbar.alter_prop(PropsOp::add_classes(classes)); diff --git a/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs b/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs index 68709e80..e9eed78e 100644 --- a/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs +++ b/extensions/pagetop-bootsier/src/theme/bs/offcanvas/component.rs @@ -14,7 +14,7 @@ use crate::theme::*; /// - Se puede configurar el borde de la ventana desde el que se desliza el panel /// ([`with_placement()`](Self::with_placement)). /// - Encabezado con título ([`with_title()`](Self::with_title)) y **botón de cierre** integrado. -/// - Puede cambiar su comportamiento a partir de un punto de corte +/// - Puede cambiar su comportamiento a partir de un punto de ruptura /// ([`with_breakpoint()`](Self::with_breakpoint)). /// - Asocia título y controles de accesibilidad a un identificador único y expone atributos /// adecuados para lectores de pantalla y navegación por teclado. @@ -47,8 +47,8 @@ pub struct Offcanvas { props: Props, /// Devuelve el título del panel. title: Lc, - /// Devuelve el punto de corte configurado para cambiar el comportamiento del panel. - breakpoint: Breakpoint, + /// Devuelve el punto de ruptura configurado para cambiar el comportamiento del panel. + breakpoint: BreakPoint, /// Devuelve el comportamiento configurado para la capa de fondo. backdrop: bs::offcanvas::Backdrop, /// Indica si la página principal puede desplazarse mientras el panel está abierto. @@ -77,8 +77,8 @@ impl Component for Offcanvas { // Clases CSS por defecto para el panel. self.alter_prop(PropsOp::prepend_classes({ - let mut classes = String::new(); - push_breakpoint_class(cx, *self.breakpoint(), &mut classes, "offcanvas", ""); + let mut classes = "offcanvas".to_string(); + self.breakpoint().push_to(&mut classes, "offcanvas", ""); self.placement().push_to(&mut classes); self.visibility().push_to(&mut classes); classes @@ -112,16 +112,16 @@ impl Offcanvas { self } - /// Establece el punto de corte a partir del cual cambia el comportamiento del panel. + /// Establece el punto de ruptura a partir del cual cambia el comportamiento del panel. /// /// - **Por debajo** de ese tamaño de pantalla, el componente actúa como panel deslizante /// ([`Offcanvas`]). /// - **Por encima**, el contenido del panel se muestra tal cual, integrado en la página. /// - /// Por ejemplo, con `Breakpoint::Lg`, será *offcanvas* en móviles y tabletas, y visible - /// directamente en pantallas grandes. Por defecto usa `Breakpoint::Xs` para que sea + /// Por ejemplo, con `BreakPoint::Lg`, será *offcanvas* en móviles y tabletas, y visible + /// directamente en pantallas grandes. Por defecto usa `BreakPoint::None` para que sea /// *offcanvas* siempre. - pub fn with_breakpoint(mut self, bp: Breakpoint) -> Self { + pub fn with_breakpoint(mut self, bp: BreakPoint) -> Self { self.breakpoint = bp; self } diff --git a/extensions/pagetop-bootsier/src/theme/class.rs b/extensions/pagetop-bootsier/src/theme/class.rs index 8ce0a0c8..ee51497b 100644 --- a/extensions/pagetop-bootsier/src/theme/class.rs +++ b/extensions/pagetop-bootsier/src/theme/class.rs @@ -1,7 +1,7 @@ //! Define clases para aplicar en componentes del tema. //! -//! Incluyen colores y niveles de opacidad, escalas de tamaño y lados, necesarios para crear -//! determinadas clases del tema: +//! Incluyen puntos de ruptura, colores y niveles de opacidad, escalas de tamaño y lados, necesarios +//! para crear determinadas clases del tema: //! //! ```rust,no_run //! use pagetop_bootsier::theme::*; diff --git a/extensions/pagetop-bootsier/src/theme/token.rs b/extensions/pagetop-bootsier/src/theme/token.rs index cf763304..edd0da0a 100644 --- a/extensions/pagetop-bootsier/src/theme/token.rs +++ b/extensions/pagetop-bootsier/src/theme/token.rs @@ -1,5 +1,8 @@ //! Tipos enumerados para construir clases del tema. +mod breakpoint; +pub use breakpoint::BreakPoint; + mod color; pub use color::{BootsierColors, OpacityLevel}; diff --git a/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs b/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs new file mode 100644 index 00000000..31310760 --- /dev/null +++ b/extensions/pagetop-bootsier/src/theme/token/breakpoint.rs @@ -0,0 +1,61 @@ +use pagetop::prelude::*; + +/// Puntos de ruptura (*breakpoints*) para aplicar diseño *responsive*. +#[derive(AutoDefault, Clone, Copy, Debug, PartialEq)] +pub enum BreakPoint { + /// **Menos de 576px**. Dispositivos muy pequeños: teléfonos en modo vertical. + #[default] + None, + /// **576px o más**. Dispositivos pequeños: teléfonos en modo horizontal. + SM, + /// **768px o más**. Dispositivos medianos: tabletas. + MD, + /// **992px o más**. Dispositivos grandes: puestos de escritorio. + LG, + /// **1200px o más**. Dispositivos muy grandes: puestos de escritorio grandes. + XL, + /// **1400px o más**. Dispositivos extragrandes: puestos de escritorio más grandes. + XXL, +} + +impl BreakPoint { + /// Devuelve la identificación del punto de ruptura (`"sm"`, `"md"`, etc.), o `""` para `None`. + #[rustfmt::skip] + #[inline] + pub const fn as_str(self) -> &'static str { + match self { + Self::None => "", + Self::SM => "sm", + Self::MD => "md", + Self::LG => "lg", + Self::XL => "xl", + Self::XXL => "xxl", + } + } + + // Añade el punto de ruptura con un prefijo y un sufijo (opcional) a la cadena de clases. + // + // - Para `None`: `prefix` o `prefix-suffix` (si `suffix` no está vacío). + // - Para `SM..XXL`: `prefix-{breakpoint}` o `prefix-{breakpoint}-{suffix}`. + #[inline] + pub(crate) fn push_to(self, classes: &mut String, prefix: &str, suffix: &str) { + if prefix.is_empty() { + return; + } + if !classes.is_empty() { + classes.push(' '); + } + match self { + Self::None => classes.push_str(prefix), + _ => { + classes.push_str(prefix); + classes.push('-'); + classes.push_str(self.as_str()); + } + } + if !suffix.is_empty() { + classes.push('-'); + classes.push_str(suffix); + } + } +} diff --git a/extensions/pagetop-bootsier/tests/breakpoint_class.rs b/extensions/pagetop-bootsier/tests/breakpoint_class.rs deleted file mode 100644 index 659bc926..00000000 --- a/extensions/pagetop-bootsier/tests/breakpoint_class.rs +++ /dev/null @@ -1,86 +0,0 @@ -// Verifies that Bootsier builds its breakpoint-based Bootstrap classes from the breakpoint names -// of `Bootsier::breakpoint_entry()`, and that `Xxxl` (absent in Bootstrap) resolves as `xxl`. - -use pagetop::prelude::*; -use pagetop_bootsier::theme::*; - -fn cx() -> Context { - Context::default().with_theme(&pagetop_bootsier::Bootsier) -} - -#[pagetop::test] -async fn container_width_uses_breakpoint_name() { - let cx = cx(); - assert_eq!(bs::container::Width::Default.to_class(&cx), "container"); - assert_eq!(bs::container::Width::Fluid.to_class(&cx), "container-fluid"); - assert_eq!( - bs::container::Width::From(Breakpoint::Lg).to_class(&cx), - "container-lg" - ); -} - -#[pagetop::test] -async fn container_width_xxxl_resolves_as_xxl() { - let cx = cx(); - assert_eq!( - bs::container::Width::From(Breakpoint::Xxxl).to_class(&cx), - "container-xxl" - ); -} - -#[pagetop::test] -async fn dropdown_menu_align_combines_breakpoint_classes() { - let cx = cx(); - assert_eq!(bs::dropdown::MenuAlign::Start.to_class(&cx), ""); - assert_eq!( - bs::dropdown::MenuAlign::EndAt(Breakpoint::Md).to_class(&cx), - "dropdown-menu-md-end" - ); - assert_eq!( - bs::dropdown::MenuAlign::StartAndEnd(Breakpoint::Xl).to_class(&cx), - "dropdown-menu-start dropdown-menu-xl-end" - ); -} - -#[pagetop::test] -async fn navbar_expands_at_md_by_default_and_at_the_chosen_breakpoint() { - let item = - || bs::navbar::Item::nav(bs::Nav::new().with_item(bs::nav::Item::link(Lc::n("Home"), "/"))); - - let mut navbar = bs::Navbar::simple().with_item(item()); - let html = navbar.render(&mut cx()).await.into_string(); - assert!(html.contains("navbar-expand-md")); - // The expansion class comes from the `Navbar` component only, so it is not duplicated. - assert_eq!(html.matches("navbar-expand").count(), 1); - - let mut navbar = bs::Navbar::simple() - .with_expand(Breakpoint::Xxxl) - .with_item(item()); - let html = navbar.render(&mut cx()).await.into_string(); - assert!(html.contains("navbar-expand-xxl")); - - // Bootstrap needs a plain `navbar-expand` to keep the bar always expanded. - let mut navbar = bs::Navbar::simple() - .with_expand(Breakpoint::Xs) - .with_item(item()); - let html = navbar.render(&mut cx()).await.into_string(); - assert!(html.contains("navbar-expand")); - assert!(!html.contains("navbar-expand-")); -} - -#[pagetop::test] -async fn offcanvas_uses_either_the_plain_or_the_breakpoint_class() { - // Bootstrap's `.offcanvas` is always fixed, so it must not accompany `.offcanvas-{bp}`. - let panel = - |offcanvas: bs::Offcanvas| offcanvas.with_child(Html::with(|_| html! { p { "Content" } })); - - let mut plain = panel(bs::Offcanvas::new()); - let html = plain.render(&mut cx()).await.into_string(); - assert!(html.contains(r#"class="offcanvas "#)); - assert!(!html.contains("offcanvas-lg")); - - let mut responsive = panel(bs::Offcanvas::new().with_breakpoint(Breakpoint::Lg)); - let html = responsive.render(&mut cx()).await.into_string(); - assert!(html.contains(r#"class="offcanvas-lg "#)); - assert!(!html.contains(r#"class="offcanvas "#)); -} diff --git a/extensions/pagetop-menu/src/component/menu_block.rs b/extensions/pagetop-menu/src/component/menu_block.rs index aa6e6555..589d8f13 100644 --- a/extensions/pagetop-menu/src/component/menu_block.rs +++ b/extensions/pagetop-menu/src/component/menu_block.rs @@ -4,8 +4,8 @@ use crate::tree::{MenuKey, MenuNode, TreeOptions, build_tree, try_resolve_menu_u /// Renderiza un menú completo como bloque de navegación HTML. /// -/// Se construye componiendo [`Nav`] y [`nav::Item`] (con [`Dropdown`]/[`dropdown::Item`] para -/// los nodos con hijos) a partir del árbol del menú, así que produce el mismo marcado accesible +/// Se construye componiendo [`Nav`] y [`nav::Item`] -- con [`Dropdown`]/[`dropdown::Item`] para +/// los nodos con hijos -- a partir del árbol del menú, así que produce el mismo marcado accesible /// que cualquier `Nav`/`Dropdown` y se beneficia igual del CSS/JavaScript que aporta el tema /// activo: /// @@ -25,17 +25,17 @@ use crate::tree::{MenuKey, MenuNode, TreeOptions, build_tree, try_resolve_menu_u /// /// # Limitaciones conocidas /// -/// - **Profundidad máxima de 2 niveles.** [`Dropdown`] no admite submenús anidados (como Bootstrap, -/// del que toma su marcado): un nodo de tercer nivel o más profundo nunca se construye. -/// [`TreeOptions::max_depth`] se acota internamente a `2` con independencia de lo que indique -/// [`with_max_depth()`](Self::with_max_depth), así que no hay pérdida silenciosa de datos, -/// sencillamente no se piden a la base de datos. +/// - **Profundidad máxima de 2 niveles.** [`Dropdown`] no admite submenús anidados (como +/// Bootstrap, del que toma su marcado): un nodo de tercer nivel o más profundo nunca se +/// construye -- [`TreeOptions::max_depth`] se acota internamente a `2` con independencia de lo +/// que indique [`with_max_depth()`](Self::with_max_depth), así que no hay pérdida silenciosa de +/// datos, sencillamente no se piden a la base de datos. /// - **Sin colapso responsive propio.** A diferencia del antiguo `Menu::collapsible`, `Nav` es una -/// lista plana sin botón ni JavaScript de colapso; una aplicación que necesite ese comportamiento -/// debe envolver `MenuBlock` en su propia chrome hasta que exista un componente `Navbar` en el -/// core. +/// lista plana sin botón ni JavaScript de colapso; una aplicación que necesite ese +/// comportamiento debe envolver `MenuBlock` en su propia chrome hasta que exista un componente +/// `Navbar` en el core. /// - `MenuNode::in_active_trail` y `MenuNode::expanded` (ver [`crate::tree::MenuNode`]) todavía no -/// se reflejan en el marcado. Ni [`nav::Item`] ni [`dropdown::Item`] tienen hoy una forma +/// se reflejan en el marcado -- ni [`nav::Item`] ni [`dropdown::Item`] tienen hoy una forma /// verificada de pre-abrirse en el servidor sin que la mejora progresiva del tema /// (`accessible-menu` en `Basic`) lo sobrescriba al inicializarse. Sólo se traduce el estado /// `is_active`/`enabled` de cada nodo. @@ -149,7 +149,7 @@ impl MenuBlock { // Convierte un `MenuNode` de nivel 1 (raíz) en un `nav::Item`: sin hijos, enlace (o etiqueta sin // ruta propia); con hijos, activador de un `Dropdown` con sus hijos como `dropdown::Item`. Los -// hijos de un `MenuNode` de nivel 1 nunca tienen a su vez hijos propios. `TreeOptions::max_depth` +// hijos de un `MenuNode` de nivel 1 nunca tienen a su vez hijos propios -- `TreeOptions::max_depth` // se acota a 2 en `MenuBlock::prepare()`, así que no hay un tercer nivel que representar. fn node_to_item(node: &MenuNode, cx: &Context) -> nav::Item { if !node.children.is_empty() { diff --git a/extensions/pagetop-user/src/component/password_confirm.rs b/extensions/pagetop-user/src/component/password_confirm.rs index 70e3a773..480b2ca1 100644 --- a/extensions/pagetop-user/src/component/password_confirm.rs +++ b/extensions/pagetop-user/src/component/password_confirm.rs @@ -7,7 +7,7 @@ use crate::LOCALES_USER; /// Campos de contraseña y confirmación, compuestos a partir de /// [`form::input::Field::password()`] del core. No valida en ningún momento que ambos valores -/// coincidan (eso ocurre en el servidor, tras el envío del formulario), sólo renderiza los dos +/// coincidan -- eso ocurre en el servidor, tras el envío del formulario -- sólo renderiza los dos /// campos, uno junto al otro, sin ningún contenedor propio. #[derive(AutoDefault, Clone, Debug, Getters)] pub(crate) struct PasswordConfirm { diff --git a/extensions/pagetop-user/src/handlers/admin/users.rs b/extensions/pagetop-user/src/handlers/admin/users.rs index ed045684..b77023c3 100644 --- a/extensions/pagetop-user/src/handlers/admin/users.rs +++ b/extensions/pagetop-user/src/handlers/admin/users.rs @@ -330,7 +330,7 @@ fn edit_actions( can_toggle_admin: bool, waypoint: &Waypoint, cx: &mut Context, -) -> Flex { +) -> Container { let (next_status, label_key) = match status { UserStatus::Blocked => ("active", "btn-activate"), _ => ("blocked", "btn-block"), @@ -361,10 +361,13 @@ fn edit_actions( status_form = status_form.with_prop(PropsOp::set(hx::CONFIRM, confirm)); } - let mut container = Flex::new() - .with_wrap(flex::Behavior::Wrap) - .with_align(align::Items::Center) - .with_gap(align::Gap::Both(UnitValue::RelRem(0.5))) + let mut container = Container::new() + .with_flex( + Flex::new() + .with_wrap(flex::Behavior::Wrap) + .with_align(flex::Align::Center) + .with_gap(flex::Gap::Both(UnitValue::RelRem(0.5))), + ) .with_child( Button::submit(Lc::t("btn-save", &LOCALES_USER)) .with_style(button::Style::Solid(Intent::Primary)) diff --git a/src/base/component.rs b/src/base/component.rs index 807c57e2..86409bda 100644 --- a/src/base/component.rs +++ b/src/base/component.rs @@ -30,16 +30,10 @@ 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; diff --git a/src/base/component/container.rs b/src/base/component/container.rs index a33ad79f..9fcf344c 100644 --- a/src/base/component/container.rs +++ b/src/base/component/container.rs @@ -47,6 +47,9 @@ 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, } @@ -67,7 +70,7 @@ impl Component for Container { if output.is_empty() { return Ok(html! {}); } - let container_props = self.props().unpack(cx); + let container_props = self.props().unpack_with_flex(cx, self.flex()); Ok(match self.kind() { Kind::Default => html! { div (container_props) { (output) } }, Kind::Main => html! { main (container_props) { (output) } }, @@ -135,6 +138,12 @@ impl Container { self } + /// Establece el posicionamiento Flexbox como contenedor (usa `None` para quitarlo). + pub fn with_flex(mut self, flex: impl Into>) -> 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) -> Self { diff --git a/src/base/component/grid.rs b/src/base/component/grid.rs deleted file mode 100644 index 502c7362..00000000 --- a/src/base/component/grid.rs +++ /dev/null @@ -1,376 +0,0 @@ -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 `` 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, - /// Devuelve las pistas de columna (`grid-template-columns`), por punto de corte. - #[getters(copy)] - columns: Responsive, - /// Devuelve las pistas de fila (`grid-template-rows`), por punto de corte. - #[getters(copy)] - rows: Responsive, - /// Devuelve el tamaño de las columnas generadas implícitamente (`grid-auto-columns`), por - /// punto de corte. - #[getters(copy)] - auto_columns: Responsive, - /// Devuelve el tamaño de las filas generadas implícitamente (`grid-auto-rows`), por punto de - /// corte. - #[getters(copy)] - auto_rows: Responsive, - /// Devuelve el algoritmo de colocación automática, por punto de corte. - #[getters(copy)] - auto_flow: Responsive, - /// Devuelve la alineación de los elementos en el eje de columnas, por punto de corte. - #[getters(copy)] - justify_items: Responsive, - /// Devuelve la alineación de los elementos en el eje de filas, por punto de corte. - #[getters(copy)] - align_items: Responsive, - /// Devuelve la alineación de las pistas en el eje de columnas, por punto de corte. - #[getters(copy)] - justify_content: Responsive, - /// Devuelve la alineación de las pistas en el eje de filas, por punto de corte. - #[getters(copy)] - align_content: Responsive, - /// Devuelve el espaciado entre pistas, por punto de corte. - #[getters(copy)] - gap: Responsive, - /// 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 { - self.props.get_id() - } - - async fn prepare(&self, cx: &mut Context) -> Result { - 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) -> 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) -> 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) -> 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); - } - } - } -} diff --git a/src/base/component/navbar/component.rs b/src/base/component/navbar/component.rs index 7731f8bb..3b00b6a2 100644 --- a/src/base/component/navbar/component.rs +++ b/src/base/component/navbar/component.rs @@ -3,8 +3,7 @@ use crate::prelude::*; /// Componente para crear una **barra de navegación**. /// /// Permite mostrar enlaces, menús desplegables ([`nav::Item::dropdown()`]) y una marca de -/// identidad, en distintas disposiciones controladas por [`navbar::Layout`]. El punto de corte a -/// partir del cual deja de colapsar se define con [`with_expand()`](Self::with_expand). +/// identidad, en distintas disposiciones controladas por [`navbar::Layout`]. /// /// Si no contiene elementos, el componente **no se renderiza**. /// @@ -24,13 +23,11 @@ use crate::prelude::*; /// )); /// ``` /// -/// Barra **colapsable**, con botón de despliegue, que muestra su contenido en línea a partir de un -/// punto de corte (por defecto, [`Breakpoint::Md`]): +/// Barra **colapsable**, con botón de despliegue: /// /// ```rust,no_run /// # use pagetop::prelude::*; /// let navbar = Navbar::simple_toggle() -/// .with_expand(Breakpoint::Lg) /// .with_item(navbar::Item::nav( /// Nav::new() /// .with_item(nav::Item::link(Lc::n("Home"), "/")) @@ -61,7 +58,7 @@ use crate::prelude::*; /// )); /// ``` /// -/// Barra con **botón de despliegue**, contenido y **marca de identidad**: +/// Barra con **botón de despliegue** y **marca de identidad**, en ese orden: /// /// ```rust,no_run /// # use pagetop::prelude::*; @@ -84,10 +81,9 @@ pub struct Navbar { props: Props, /// Devuelve la disposición configurada para la barra de navegación. layout: navbar::Layout, - /// Devuelve el punto de corte a partir del cual la barra deja de colapsar. - #[default(Breakpoint::Md)] + /// Devuelve el posicionamiento Flexbox como contenedor, si tiene alguno. #[getters(copy)] - expand: Breakpoint, + flex: Flex, /// Devuelve la lista de contenidos. items: Children, } @@ -103,17 +99,9 @@ impl Component for Navbar { } fn setup(&mut self, cx: &mut Context) { - // 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. + // 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`. self.alter_prop(PropsOp::ensure_id(cx.build_id::(1))); - - let class: CowStr = match self.expand().resolved(cx) { - Some(entry) => util::join!("navbar-expand-", entry.name).into(), - None => "navbar-expand".into(), - }; - self.alter_prop(PropsOp::prepend_classes(class)); self.alter_prop(PropsOp::prepend_classes("navbar")); } @@ -136,9 +124,12 @@ 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 `