🚧 Review content layout with flex components

This commit is contained in:
Manuel Cillero 2024-03-17 21:18:36 +01:00
parent e8364a40ee
commit dfbe807a61
11 changed files with 353 additions and 441 deletions

View file

@ -63,14 +63,14 @@ pub(crate) fn add_base_assets(cx: &mut Context) {
#[derive(AutoDefault)]
pub enum BreakPoint {
#[default]
None, /* Does not apply. Rest initially assume 1 pixel = 0.0625rem */
SM, /* PageTop default applies to <= 568px - @media screen and (max-width: 35.5rem) */
MD, /* PageTop default applies to <= 768px - @media screen and (max-width: 48rem) */
LG, /* PageTop default applies to <= 992px - @media screen and (max-width: 62rem) */
XL, /* PageTop default applies to <= 1280px - @media screen and (max-width: 80rem) */
X2L, /* PageTop default applies to <= 1440px - @media screen and (max-width: 90rem) */
X3L, /* PageTop default applies to <= 1920px - @media screen and (max-width: 120rem) */
X2K, /* PageTop default applies to <= 2560px - @media screen and (max-width: 160rem) */
None, // Does not apply. Rest initially assume 1 pixel = 0.0625rem
SM, // @media screen and [ (max-width: 35.5rem) <= 568px < (min-width: 35.5625rem) ]
MD, // @media screen and [ (max-width: 48rem) <= 768px < (min-width: 48.0625rem) ]
LG, // @media screen and [ (max-width: 62rem) <= 992px < (min-width: 62.0625rem) ]
XL, // @media screen and [ (max-width: 80rem) <= 1280px < (min-width: 80.0625rem) ]
X2L, // @media screen and [ (max-width: 90rem) <= 1440px < (min-width: 90.0625rem) ]
X3L, // @media screen and [ (max-width: 120rem) <= 1920px < (min-width: 120.0625rem) ]
X2K, // @media screen and [ (max-width: 160rem) <= 2560px < (min-width: 160.0625rem) ]
}
#[rustfmt::skip]
@ -175,9 +175,6 @@ pub use error403::Error403;
mod error404;
pub use error404::Error404;
mod wrapper;
pub use wrapper::{Wrapper, WrapperType};
pub mod flex;
mod icon;

View file

@ -175,24 +175,6 @@ impl ToString for Gap {
// *************************************************************************************************
#[derive(AutoDefault)]
pub enum ItemWide {
#[default]
Auto,
Full,
}
impl ToString for ItemWide {
fn to_string(&self) -> String {
String::from(match self {
ItemWide::Auto => "",
ItemWide::Full => "flex__full",
})
}
}
// *************************************************************************************************
#[derive(AutoDefault)]
pub enum ItemGrow {
#[default]
@ -276,25 +258,23 @@ pub enum ItemSize {
Percent75,
Percent80,
Percent90,
FullWidth,
}
impl ToString for ItemSize {
fn to_string(&self) -> String {
String::from(match self {
ItemSize::Default => "",
ItemSize::Percent10 => "flex__item-width-10",
ItemSize::Percent20 => "flex__item-width-20",
ItemSize::Percent25 => "flex__item-width-25",
ItemSize::Percent33 => "flex__item-width-33",
ItemSize::Percent40 => "flex__item-width-40",
ItemSize::Percent50 => "flex__item-width-50",
ItemSize::Percent60 => "flex__item-width-60",
ItemSize::Percent66 => "flex__item-width-66",
ItemSize::Percent75 => "flex__item-width-75",
ItemSize::Percent80 => "flex__item-width-80",
ItemSize::Percent90 => "flex__item-width-90",
ItemSize::FullWidth => "flex__item-fullwidth",
ItemSize::Percent10 => "flex__item-size-10",
ItemSize::Percent20 => "flex__item-size-20",
ItemSize::Percent25 => "flex__item-size-25",
ItemSize::Percent33 => "flex__item-size-33",
ItemSize::Percent40 => "flex__item-size-40",
ItemSize::Percent50 => "flex__item-size-50",
ItemSize::Percent60 => "flex__item-size-60",
ItemSize::Percent66 => "flex__item-size-66",
ItemSize::Percent75 => "flex__item-size-75",
ItemSize::Percent80 => "flex__item-size-80",
ItemSize::Percent90 => "flex__item-size-90",
})
}
}

View file

@ -1,5 +1,16 @@
use crate::prelude::*;
#[derive(AutoDefault)]
pub enum ContainerType {
#[default]
Default,
Header,
Main,
Section,
Article,
Footer,
}
#[rustfmt::skip]
#[derive(AutoDefault, ComponentClasses)]
pub struct Container {
@ -7,12 +18,13 @@ pub struct Container {
weight : Weight,
renderable : Renderable,
classes : OptionClasses,
container_type : ContainerType,
direction : flex::Direction,
wrap_align : flex::WrapAlign,
content_justify: flex::ContentJustify,
items_align : flex::ItemAlign,
gap : flex::Gap,
items : TypedComponents<flex::Item>,
items : MixedComponents,
}
impl ComponentTrait for Container {
@ -50,23 +62,85 @@ impl ComponentTrait for Container {
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
let output = self.items().render(cx);
if !output.is_empty() {
let gap = match self.gap() {
flex::Gap::Default => None,
_ => Some(self.gap().to_string()),
};
PrepareMarkup::With(html! {
if output.is_empty() {
return PrepareMarkup::None;
}
let gap = match self.gap() {
flex::Gap::Default => None,
_ => Some(self.gap().to_string()),
};
match self.container_type() {
ContainerType::Default => PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
})
} else {
PrepareMarkup::None
}),
ContainerType::Header => PrepareMarkup::With(html! {
header id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
}),
ContainerType::Main => PrepareMarkup::With(html! {
main id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
}),
ContainerType::Section => PrepareMarkup::With(html! {
section id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
}),
ContainerType::Article => PrepareMarkup::With(html! {
article id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
}),
ContainerType::Footer => PrepareMarkup::With(html! {
footer id=[self.id()] class=[self.classes().get()] style=[gap] {
(output)
}
}),
}
}
}
impl Container {
pub fn header() -> Self {
Container {
container_type: ContainerType::Header,
..Default::default()
}
}
pub fn main() -> Self {
Container {
container_type: ContainerType::Main,
..Default::default()
}
}
pub fn section() -> Self {
Container {
container_type: ContainerType::Section,
..Default::default()
}
}
pub fn article() -> Self {
Container {
container_type: ContainerType::Article,
..Default::default()
}
}
pub fn footer() -> Self {
Container {
container_type: ContainerType::Footer,
..Default::default()
}
}
// Container BUILDER.
#[fn_builder]
@ -119,18 +193,22 @@ impl Container {
#[fn_builder]
pub fn alter_items(&mut self, op: TypedOp<flex::Item>) -> &mut Self {
self.items.alter_value(op);
self.items.alter_typed(op);
self
}
#[rustfmt::skip]
pub fn add_item(mut self, item: flex::Item) -> Self {
self.items.alter_value(TypedOp::Add(OneComponent::with(item)));
self.items.alter_value(AnyOp::Add(AnyComponent::with(item)));
self
}
// Container GETTERS.
pub fn container_type(&self) -> &ContainerType {
&self.container_type
}
pub fn direction(&self) -> &flex::Direction {
&self.direction
}
@ -151,7 +229,7 @@ impl Container {
&self.gap
}
pub fn items(&self) -> &TypedComponents<flex::Item> {
pub fn items(&self) -> &MixedComponents {
&self.items
}
}

View file

@ -7,7 +7,6 @@ pub struct Item {
weight : Weight,
renderable : Renderable,
classes : OptionClasses,
item_wide : flex::ItemWide,
item_grow : flex::ItemGrow,
item_shrink : flex::ItemShrink,
item_size : flex::ItemSize,
@ -38,7 +37,6 @@ impl ComponentTrait for Item {
ClassesOp::Prepend,
[
String::from("flex__item"),
self.wide().to_string(),
self.grow().to_string(),
self.shrink().to_string(),
self.size().to_string(),
@ -74,12 +72,6 @@ impl Item {
Item::default().add_component(component)
}
pub fn full(component: impl ComponentTrait) -> Self {
Item::default()
.with_wide(flex::ItemWide::Full)
.add_component(component)
}
// Item BUILDER.
#[fn_builder]
@ -100,12 +92,6 @@ impl Item {
self
}
#[fn_builder]
pub fn alter_wide(&mut self, wide: flex::ItemWide) -> &mut Self {
self.item_wide = wide;
self
}
#[fn_builder]
pub fn alter_grow(&mut self, grow: flex::ItemGrow) -> &mut Self {
self.item_grow = grow;
@ -152,10 +138,6 @@ impl Item {
// Item GETTERS.
pub fn wide(&self) -> &flex::ItemWide {
&self.item_wide
}
pub fn grow(&self) -> &flex::ItemGrow {
&self.item_grow
}

View file

@ -23,29 +23,31 @@ impl Layout {
.with_id("body__wrapper")
.with_direction(flex::Direction::Column(BreakPoint::None))
.with_items_align(flex::ItemAlign::Center)
.add_item(flex::Item::full(Region::named("header")).with_id("header"))
.add_item(flex::Item::full(Region::named("pagetop")).with_id("pagetop"))
.add_item(flex::Item::full(
flex::Container::new()
.with_id("content__wrapper")
.with_direction(flex::Direction::Row(BreakPoint::None))
.add_item(
flex::Item::with(Region::named("sidebar_left"))
.with_id("sidebar_left")
.with_grow(flex::ItemGrow::Is1),
)
.add_item(
flex::Item::with(Region::named("content"))
.with_id("content")
.with_grow(flex::ItemGrow::Is3),
)
.add_item(
flex::Item::with(Region::named("sidebar_right"))
.with_id("sidebar_right")
.with_grow(flex::ItemGrow::Is1),
),
))
.add_item(flex::Item::full(Region::named("footer")).with_id("footer"))
.add_item(flex::Item::with(Region::named("header")).with_id("header"))
.add_item(flex::Item::with(Region::named("pagetop")).with_id("pagetop"))
.add_item(
flex::Item::with(
flex::Container::new()
.with_direction(flex::Direction::Row(BreakPoint::None))
.add_item(
flex::Item::with(Region::named("sidebar_left"))
.with_id("sidebar_left")
.with_grow(flex::ItemGrow::Is1),
)
.add_item(
flex::Item::with(Region::named("content"))
.with_id("content")
.with_grow(flex::ItemGrow::Is3),
)
.add_item(
flex::Item::with(Region::named("sidebar_right"))
.with_id("sidebar_right")
.with_grow(flex::ItemGrow::Is1),
),
)
.with_id("content__wrapper"),
)
.add_item(flex::Item::with(Region::named("footer")).with_id("footer"))
.render(cx)
}

View file

@ -1,148 +0,0 @@
use crate::prelude::*;
#[derive(AutoDefault)]
pub enum WrapperType {
#[default]
Container,
Header,
Footer,
Main,
Section,
}
#[rustfmt::skip]
#[derive(AutoDefault, ComponentClasses)]
pub struct Wrapper {
id : OptionId,
weight : Weight,
renderable : Renderable,
classes : OptionClasses,
wrapper_type: WrapperType,
mixed : MixedComponents,
}
impl ComponentTrait for Wrapper {
fn new() -> Self {
Wrapper::default().with_classes(ClassesOp::Add, "wrapper__container")
}
fn id(&self) -> Option<String> {
self.id.get()
}
fn weight(&self) -> Weight {
self.weight
}
fn is_renderable(&self, cx: &Context) -> bool {
(self.renderable.check)(cx)
}
fn prepare_component(&self, cx: &mut Context) -> PrepareMarkup {
match self.wrapper_type() {
WrapperType::Container => PrepareMarkup::With(html! {
div id=[self.id()] class=[self.classes().get()] {
div class="wrapper__content" {
(self.components().render(cx))
}
}
}),
WrapperType::Main => PrepareMarkup::With(html! {
main id=[self.id()] class=[self.classes().get()] {
div class="wrapper__content" {
(self.components().render(cx))
}
}
}),
WrapperType::Section => PrepareMarkup::With(html! {
section id=[self.id()] class=[self.classes().get()] {
div class="wrapper__content" {
(self.components().render(cx))
}
}
}),
WrapperType::Header => PrepareMarkup::With(html! {
header id=[self.id()] class=[self.classes().get()] {
div class="wrapper__content" {
(self.components().render(cx))
}
}
}),
WrapperType::Footer => PrepareMarkup::With(html! {
footer id=[self.id()] class=[self.classes().get()] {
div class="wrapper__content" {
(self.components().render(cx))
}
}
}),
}
}
}
impl Wrapper {
pub fn main() -> Self {
let mut c = Wrapper::default().with_classes(ClassesOp::Add, "main__container");
c.wrapper_type = WrapperType::Main;
c
}
pub fn section() -> Self {
let mut c = Wrapper::default().with_classes(ClassesOp::Add, "section__container");
c.wrapper_type = WrapperType::Section;
c
}
pub fn header() -> Self {
let mut c = Wrapper::default().with_classes(ClassesOp::Add, "header__container");
c.wrapper_type = WrapperType::Header;
c
}
pub fn footer() -> Self {
let mut c = Wrapper::default().with_classes(ClassesOp::Add, "footer__container");
c.wrapper_type = WrapperType::Footer;
c
}
// Wrapper BUILDER.
#[fn_builder]
pub fn alter_id(&mut self, id: impl Into<String>) -> &mut Self {
self.id.alter_value(id);
self
}
#[fn_builder]
pub fn alter_weight(&mut self, value: Weight) -> &mut Self {
self.weight = value;
self
}
#[fn_builder]
pub fn alter_renderable(&mut self, check: FnIsRenderable) -> &mut Self {
self.renderable.check = check;
self
}
#[fn_builder]
pub fn alter_components(&mut self, op: MixedOp) -> &mut Self {
self.mixed.alter_value(op);
self
}
#[rustfmt::skip]
pub fn add_component(mut self, component: impl ComponentTrait) -> Self {
self.mixed.alter_value(MixedOp::Add(AnyComponent::with(component)));
self
}
// Wrapper GETTERS.
pub fn wrapper_type(&self) -> &WrapperType {
&self.wrapper_type
}
pub fn components(&self) -> &MixedComponents {
&self.mixed
}
}

View file

@ -47,168 +47,194 @@ fn home(request: HttpRequest, lang: &'static LanguageIdentifier) -> ResultPage<M
.render()
}
fn hello_world() -> Wrapper {
Wrapper::header()
fn hello_world() -> flex::Container {
flex::Container::header()
.with_classes(ClassesOp::Add, "hello-world")
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "hello-col-text")
.with_size(flex::ItemSize::Percent40)
.add_component(
Heading::h1(L10n::l("welcome_title")).with_size(HeadingSize::Medium),
)
.add_component(
Paragraph::fluent(L10n::l("welcome_intro").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name,
.with_content_justify(flex::ContentJustify::Center)
.add_item(
flex::Item::new()
.with_size(flex::ItemSize::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "hello-col-text")
.with_size(flex::ItemSize::Percent40)
.add_component(
Heading::h1(L10n::l("welcome_title"))
.with_size(HeadingSize::Medium),
)
.add_component(
Paragraph::fluent(L10n::l("welcome_intro").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name,
),
))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(
L10n::l("welcome_powered").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://pagetop.cillero.es", "PageTop",
),
),
))
.add_component(
Button::anchor(
"https://github.com/manuelcillero/pagetop",
L10n::l("welcome_code"),
)
.with_target(ButtonTarget::Blank)
.with_left_icon(Some(Icon::with("git")))
.with_classes(ClassesOp::Add, "code-link")
.with_font_size(FontSize::Medium),
)
.add_component(
Button::anchor("#welcome-page", L10n::l("welcome"))
.with_style(StyleBase::Link)
.with_left_icon(Some(Icon::with("arrow-down-circle-fill")))
.with_classes(ClassesOp::Add, "welcome-link")
.with_font_size(FontSize::Medium),
),
))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_powered").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://pagetop.cillero.es", "PageTop",
),
)))
.add_component(
Button::anchor(
"https://github.com/manuelcillero/pagetop",
L10n::l("welcome_code"),
)
.with_target(ButtonTarget::Blank)
.with_left_icon(Some(Icon::with("git")))
.with_classes(ClassesOp::Add, "code-link")
.with_font_size(FontSize::Medium),
)
.add_component(
Button::anchor("#welcome-page", L10n::l("welcome"))
.with_style(StyleBase::Link)
.with_left_icon(Some(Icon::with("arrow-down-circle-fill")))
.with_classes(ClassesOp::Add, "welcome-link")
.with_font_size(FontSize::Medium),
.add_item(
flex::Item::with(Image::with("/base/images/header.svg"))
.with_classes(ClassesOp::Add, "hello-col-image")
.with_size(flex::ItemSize::Percent60),
),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "hello-col-image")
.with_size(flex::ItemSize::Percent60)
.add_component(Image::with("/base/images/header.svg")),
),
)
}
fn welcome() -> Wrapper {
Wrapper::section()
fn welcome() -> flex::Container {
flex::Container::section()
.with_id("welcome-page")
.with_classes(ClassesOp::Add, "welcome")
.add_component(Heading::h2(L10n::l("welcome_page")))
.add_component(
Heading::h3(L10n::l("welcome_subtitle").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name
),
))
.with_size(HeadingSize::Subtitle),
)
.add_component(Paragraph::fluent(L10n::l("welcome_text1")).with_font_size(FontSize::Medium))
.add_component(Paragraph::fluent(L10n::l("welcome_text2")))
}
fn about_pagetop() -> Wrapper {
Wrapper::new()
.with_classes(ClassesOp::Add, "pagetop")
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::SM))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "pagetop-col-image")
.with_size(flex::ItemSize::Percent40)
.add_component(Image::with("/base/images/about.svg")),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "pagetop-col-text")
.add_component(Heading::h2(L10n::l("welcome_pagetop_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_pagetop_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text2")))
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text3"))),
),
)
}
fn promo_pagetop() -> Wrapper {
Wrapper::new()
.with_classes(ClassesOp::Add, "promo")
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "promo-col-text")
.with_size(flex::ItemSize::Percent50)
.add_component(Heading::h2(L10n::l("welcome_promo_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_promo_text1").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "PageTop",
),
))
.with_font_size(FontSize::Medium),
.with_content_justify(flex::ContentJustify::Center)
.add_item(
flex::Item::new()
.with_size(flex::ItemSize::Percent80)
.add_component(Heading::h2(L10n::l("welcome_page")))
.add_component(
Heading::h3(L10n::l("welcome_subtitle").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name
),
))
.with_size(HeadingSize::Subtitle),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "promo-col-image")
.with_size(flex::ItemSize::Percent50)
.add_component(Image::with("/base/images/pagetop.png")),
.add_component(
Paragraph::fluent(L10n::l("welcome_text1")).with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_text2"))),
)
}
fn about_pagetop() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "pagetop")
.with_content_justify(flex::ContentJustify::Center)
.add_item(
flex::Item::new()
.with_size(flex::ItemSize::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::SM))
.add_item(
flex::Item::with(Image::with("/base/images/about.svg"))
.with_classes(ClassesOp::Add, "pagetop-col-image")
.with_size(flex::ItemSize::Percent40),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "pagetop-col-text")
.add_component(Heading::h2(L10n::l("welcome_pagetop_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_pagetop_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text2")))
.add_component(Paragraph::fluent(L10n::l("welcome_pagetop_text3"))),
),
),
)
}
fn reporting_issues() -> Wrapper {
Wrapper::new()
.with_classes(ClassesOp::Add, "issues")
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "issues-col-image")
.add_component(Image::with("/base/images/issues.jpg")),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "issues-col-text")
.with_size(flex::ItemSize::Percent50)
.add_component(Heading::h2(L10n::l("welcome_issues_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_issues_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(
L10n::l("welcome_issues_text2").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name,
fn promo_pagetop() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "promo")
.with_content_justify(flex::ContentJustify::Center)
.add_item(
flex::Item::new()
.with_size(flex::ItemSize::Percent75)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "promo-col-text")
.with_size(flex::ItemSize::Percent50)
.add_component(Heading::h2(L10n::l("welcome_promo_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_promo_text1").with_arg(
"pagetop",
format!(
"<a href=\"{}\" target=\"_blank\">{}</a>",
"https://crates.io/crates/pagetop", "PageTop",
),
))
.with_font_size(FontSize::Medium),
),
),
)),
)
.add_item(
flex::Item::with(Image::with("/base/images/pagetop.png"))
.with_classes(ClassesOp::Add, "promo-col-image")
.with_size(flex::ItemSize::Percent50),
),
),
)
}
fn reporting_issues() -> flex::Container {
flex::Container::new()
.with_classes(ClassesOp::Add, "issues")
.with_content_justify(flex::ContentJustify::Center)
.add_item(
flex::Item::new()
.with_size(flex::ItemSize::Percent90)
.add_component(
flex::Container::new()
.with_direction(flex::Direction::Column(BreakPoint::MD))
.add_item(
flex::Item::with(Image::with("/base/images/issues.jpg"))
.with_classes(ClassesOp::Add, "issues-col-image"),
)
.add_item(
flex::Item::new()
.with_classes(ClassesOp::Add, "issues-col-text")
.with_size(flex::ItemSize::Percent50)
.add_component(Heading::h2(L10n::l("welcome_issues_title")))
.add_component(
Paragraph::fluent(L10n::l("welcome_issues_text1"))
.with_font_size(FontSize::Medium),
)
.add_component(Paragraph::fluent(
L10n::l("welcome_issues_text2").with_arg(
"app",
format!(
"<span class=\"app-name\">{}</span>",
&config::SETTINGS.app.name,
),
),
)),
),
),
)
}