Move Images and Tiles into the toolkit

Add a Profile toolkit struct
This commit is contained in:
asonix 2021-01-21 23:42:19 -06:00
parent f6f603324a
commit c86f4b3ff2
24 changed files with 595 additions and 180 deletions

View file

@ -43,7 +43,7 @@ img {
display: block;
width: 100%;
height: 100%;
object-fit: scale-down;
object-fit: contain;
}
.toolkit-select {
@ -339,6 +339,64 @@ img {
}
}
.toolkit-tiles {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
.toolkit-tile {
position: relative;
}
picture {
display: block;
height: 100%;
}
img {
object-fit: cover;
}
.toolkit-link {
display: block;
height: 100%;
width: 100%;
font-style: none;
font-weight: 500;
color: $border-light;
&:hover,
&:focus,
&:active {
color: $border-light;
}
}
.toolkit-tile--overlay {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
display: flex;
justify-content: space-around;
align-items: center;
text-align: center;
opacity: 0;
transition: opacity 0.2s ease-in-out;
background-color: rgba(0, 0, 0, 0.4);
text-shadow: 1px 1px 1px $dark;
&:hover {
opacity: 1;
}
}
.toolkit-title--meta p {
margin: 0;
margin-top: 16px;
}
}
.toolkit-centered {
width: 100%;
max-width: 900px;

50
toolkit/src/banner.rs Normal file
View file

@ -0,0 +1,50 @@
use crate::image::Image;
use std::rc::Rc;
#[derive(Clone, Default)]
pub struct Banner {
image: Option<Rc<dyn Image>>,
dark: bool,
}
impl Banner {
pub fn new() -> Self {
Banner {
image: None,
dark: false,
}
}
pub fn image(mut self, image: impl Image + 'static) -> Self {
self.image = Some(Rc::new(image));
self
}
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}
pub(crate) fn image_opt(&self) -> Option<&dyn Image> {
self.image.as_deref()
}
pub(crate) fn class_string(&self) -> String {
let mut classes = vec!["toolkit-banner".to_owned()];
if self.dark {
classes.push("toolkit-dark".to_owned());
}
classes.join(" ")
}
}
impl std::fmt::Debug for Banner {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Banner")
.field("dark", &self.dark)
.field("image", &"Rc<dyn Image>")
.finish()
}
}

View file

@ -1,5 +1,3 @@
use std::{cell::RefCell, fmt};
#[derive(Clone, Copy, Debug)]
pub enum ButtonKind {
Primary,
@ -14,19 +12,8 @@ pub enum LinkKind {
NewTab,
}
#[derive(Default)]
#[derive(Clone, Debug, Default)]
pub struct Button {
pub(crate) inner: RefCell<Inner>,
}
impl fmt::Debug for Button {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Button").field("inner", &"Inner").finish()
}
}
#[derive(Default)]
pub(crate) struct Inner {
pub(crate) label: String,
pub(crate) kind: ButtonKind,
pub(crate) classes: Vec<String>,
@ -39,79 +26,71 @@ pub(crate) struct Inner {
impl Button {
pub fn primary(label: &str) -> Self {
Button {
inner: RefCell::new(Inner {
label: label.to_owned(),
..Inner::default()
}),
label: label.to_owned(),
..Default::default()
}
}
pub fn primary_outline(label: &str) -> Self {
Button {
inner: RefCell::new(Inner {
label: label.to_owned(),
kind: ButtonKind::PrimaryOutline,
..Inner::default()
}),
label: label.to_owned(),
kind: ButtonKind::PrimaryOutline,
..Default::default()
}
}
pub fn outline(label: &str) -> Self {
Button {
inner: RefCell::new(Inner {
label: label.to_owned(),
kind: ButtonKind::Outline,
..Inner::default()
}),
label: label.to_owned(),
kind: ButtonKind::Outline,
..Default::default()
}
}
pub fn secondary(label: &str) -> Self {
Button {
inner: RefCell::new(Inner {
label: label.to_owned(),
kind: ButtonKind::Secondary,
..Inner::default()
}),
label: label.to_owned(),
kind: ButtonKind::Secondary,
..Default::default()
}
}
pub fn kind(&self, kind: ButtonKind) -> &Self {
self.inner.borrow_mut().kind = kind;
pub fn kind(mut self, kind: ButtonKind) -> Self {
self.kind = kind;
self
}
pub fn classes(&self, classes: &[&str]) -> &Self {
self.inner.borrow_mut().classes = classes.into_iter().map(|s| s.to_string()).collect();
pub fn classes(mut self, classes: &[&str]) -> Self {
self.classes = classes.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn href(&self, href: &str) -> &Self {
self.inner.borrow_mut().href = Some(href.to_owned());
pub fn href(mut self, href: &str) -> Self {
self.href = Some(href.to_owned());
self
}
pub fn new_tab(&self) -> &Self {
self.inner.borrow_mut().link_kind = LinkKind::NewTab;
pub fn new_tab(mut self) -> Self {
self.link_kind = LinkKind::NewTab;
self
}
pub fn form(&self, action: &str) -> &Self {
self.inner.borrow_mut().action = Some(action.to_owned());
pub fn form(mut self, action: &str) -> Self {
self.action = Some(action.to_owned());
self
}
pub fn dark(&self, dark: bool) -> &Self {
self.inner.borrow_mut().dark = dark;
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}
pub(crate) fn class_string(&self) -> String {
use ButtonKind::*;
let mut classes = self.inner.borrow().classes.clone();
let mut classes = self.classes.clone();
let static_classes = match self.inner.borrow().kind {
let static_classes = match self.kind {
Primary => "toolkit-button toolkit-button__primary",
PrimaryOutline => "toolkit-button toolkit-button__primary-outline",
Outline => "toolkit-button toolkit-button__outline",
@ -120,7 +99,7 @@ impl Button {
classes.push(static_classes.to_owned());
if self.inner.borrow().dark {
if self.dark {
classes.push("toolkit-dark".to_owned());
}

View file

@ -17,17 +17,17 @@ impl Card {
}
}
pub fn centered(&mut self) -> &mut Self {
pub fn centered(mut self) -> Self {
self.classes.push("toolkit-centered".to_owned());
self
}
pub fn classes(&mut self, classes: &[&str]) -> &mut Self {
pub fn classes(mut self, classes: &[&str]) -> Self {
self.classes = classes.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}

View file

@ -52,27 +52,27 @@ impl FileInput {
}
}
pub fn multiple(&mut self) -> &mut Self {
pub fn multiple(mut self) -> Self {
self.multiple = true;
self
}
pub fn classes(&mut self, classes: &[&str]) -> &mut Self {
pub fn classes(mut self, classes: &[&str]) -> Self {
self.classes = classes.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn accept(&mut self, accept: &str) -> &mut Self {
pub fn accept(mut self, accept: &str) -> Self {
self.accept = accept.to_string();
self
}
pub fn no_group(&mut self) -> &mut Self {
pub fn no_group(mut self) -> Self {
self.group = false;
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}

65
toolkit/src/icon.rs Normal file
View file

@ -0,0 +1,65 @@
use crate::image::Image;
use std::rc::Rc;
#[derive(Clone)]
pub struct Icon {
pub(crate) href: String,
pub(crate) small: bool,
pub(crate) dark: bool,
image: Option<Rc<dyn Image>>,
}
impl Icon {
pub fn new(href: String) -> Self {
Icon {
href,
small: false,
dark: false,
image: None,
}
}
pub fn image(mut self, image: impl Image + 'static) -> Self {
self.image = Some(Rc::new(image));
self
}
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}
pub fn small(mut self, small: bool) -> Self {
self.small = small;
self
}
pub(crate) fn image_opt(&self) -> Option<&dyn Image> {
self.image.as_deref()
}
pub(crate) fn class_string(&self) -> String {
let mut classes = vec!["toolkit-icon--link".to_owned()];
if self.small {
classes.push("toolkit-icon--link__small".to_owned());
}
if self.dark {
classes.push("toolkit-dark".to_owned());
}
classes.join(" ")
}
}
impl std::fmt::Debug for Icon {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Icon")
.field("href", &self.href)
.field("small", &self.dark)
.field("dark", &self.dark)
.field("image", &"Rc<dyn Image>")
.finish()
}
}

86
toolkit/src/image.rs Normal file
View file

@ -0,0 +1,86 @@
use std::rc::Rc;
pub trait Image {
fn src(&self) -> String;
fn title(&self) -> String;
fn png_srcset(&self) -> Option<String>;
fn jpeg_srcset(&self) -> Option<String>;
fn webp_srcset(&self) -> Option<String>;
}
impl<T> Image for &T
where
T: Image,
{
fn src(&self) -> String {
T::src(self)
}
fn title(&self) -> String {
T::title(self)
}
fn png_srcset(&self) -> Option<String> {
T::png_srcset(self)
}
fn jpeg_srcset(&self) -> Option<String> {
T::jpeg_srcset(self)
}
fn webp_srcset(&self) -> Option<String> {
T::webp_srcset(self)
}
}
impl<T> Image for Rc<T>
where
T: Image,
{
fn src(&self) -> String {
T::src(self)
}
fn title(&self) -> String {
T::title(self)
}
fn png_srcset(&self) -> Option<String> {
T::png_srcset(self)
}
fn jpeg_srcset(&self) -> Option<String> {
T::jpeg_srcset(self)
}
fn webp_srcset(&self) -> Option<String> {
T::webp_srcset(self)
}
}
impl<T> Image for Box<T>
where
T: Image,
{
fn src(&self) -> String {
T::src(self)
}
fn title(&self) -> String {
T::title(self)
}
fn png_srcset(&self) -> Option<String> {
T::png_srcset(self)
}
fn jpeg_srcset(&self) -> Option<String> {
T::jpeg_srcset(self)
}
fn webp_srcset(&self) -> Option<String> {
T::webp_srcset(self)
}
}

View file

@ -25,32 +25,32 @@ impl Input {
}
}
pub fn kind(&mut self, kind: InputKind) -> &mut Self {
pub fn kind(mut self, kind: InputKind) -> Self {
self.kind = kind;
self
}
pub fn value(&mut self, value: &str) -> &mut Self {
pub fn value(mut self, value: &str) -> Self {
self.value = Some(value.to_owned());
self
}
pub fn placeholder(&mut self, placeholder: &str) -> &mut Self {
pub fn placeholder(mut self, placeholder: &str) -> Self {
self.placeholder = Some(placeholder.to_owned());
self
}
pub fn value_opt(&mut self, value: Option<String>) -> &mut Self {
pub fn value_opt(mut self, value: Option<String>) -> Self {
self.value = value;
self
}
pub fn classes(&mut self, classes: &[&str]) -> &mut Self {
pub fn classes(mut self, classes: &[&str]) -> Self {
self.classes = classes.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}

View file

@ -1,21 +1,31 @@
include!(concat!(env!("OUT_DIR"), "/templates.rs"));
mod banner;
mod button;
mod card;
mod file_input;
mod icon;
mod image;
mod input;
mod link;
mod profile;
mod select;
mod text_input;
mod tile;
pub use self::{
banner::Banner,
button::{Button, ButtonKind, LinkKind},
card::Card,
file_input::FileInput,
icon::Icon,
image::Image,
input::{Input, InputKind},
link::Link,
profile::Profile,
select::Select,
text_input::TextInput,
tile::Tile,
};
use chrono::{DateTime, Utc};

View file

@ -27,17 +27,17 @@ impl Link {
}
}
pub fn title(&mut self, title: &str) -> &mut Self {
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}
pub fn plain(&mut self, plain: bool) -> &mut Self {
pub fn plain(mut self, plain: bool) -> Self {
self.plain = plain;
self
}

67
toolkit/src/profile.rs Normal file
View file

@ -0,0 +1,67 @@
use crate::{banner::Banner, icon::Icon, image::Image};
#[derive(Clone, Debug)]
pub struct Profile {
pub(crate) handle: String,
pub(crate) display_name: Option<String>,
pub(crate) description: Option<String>,
pub(crate) icon: Icon,
pub(crate) banner: Banner,
dark: bool,
}
impl Profile {
pub fn new(handle: String, href: String) -> Self {
Profile {
handle,
display_name: None,
description: None,
icon: Icon::new(href),
banner: Banner::new(),
dark: false,
}
}
pub fn display_name(mut self, display_name: &str) -> Self {
self.display_name = Some(display_name.to_owned());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
pub fn icon_image(self, image: impl Image + 'static) -> Self {
Profile {
icon: self.icon.image(image),
..self
}
}
pub fn banner_image(self, image: impl Image + 'static) -> Self {
Profile {
banner: self.banner.image(image),
..self
}
}
pub fn dark(self, dark: bool) -> Self {
Profile {
icon: self.icon.dark(dark),
banner: self.banner.dark(dark),
dark,
..self
}
}
pub(crate) fn class_string(&self) -> String {
let mut classes = vec!["toolkit-profile".to_owned()];
if self.dark {
classes.push("toolkit-dark".to_owned());
}
classes.join(" ")
}
}

View file

@ -24,22 +24,22 @@ impl Select {
}
}
pub fn title(&mut self, title: &str) -> &mut Self {
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
pub fn default_option(&mut self, value: &str) -> &mut Self {
pub fn default_option(mut self, value: &str) -> Self {
self.default = Some(value.to_owned());
self
}
pub fn add_option(&mut self, text: String, value: String) -> &mut Self {
pub fn add_option(mut self, text: String, value: String) -> Self {
self.options.push(SelectOption { value, text });
self
}
pub fn options(&mut self, options: &[(&str, &str)]) -> &mut Self {
pub fn options(mut self, options: &[(&str, &str)]) -> Self {
self.options = options
.iter()
.map(|(text, value)| SelectOption {
@ -50,7 +50,7 @@ impl Select {
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}

View file

@ -17,47 +17,57 @@ impl TextInput {
}
}
pub fn password(&mut self) -> &mut Self {
self.input.kind(InputKind::Password);
self
pub fn password(self) -> Self {
TextInput {
input: self.input.kind(InputKind::Password),
..self
}
}
pub fn textarea(&mut self) -> &mut Self {
self.input.kind(InputKind::TextArea);
self
pub fn textarea(self) -> Self {
TextInput {
input: self.input.kind(InputKind::TextArea),
..self
}
}
pub fn title(&mut self, title: &str) -> &mut Self {
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
pub fn value(&mut self, value: &str) -> &mut Self {
self.input.value(value);
self
pub fn value(self, value: &str) -> Self {
TextInput {
input: self.input.value(value),
..self
}
}
pub fn placeholder(&mut self, placeholder: &str) -> &mut Self {
self.input.placeholder(placeholder);
self
pub fn placeholder(self, placeholder: &str) -> Self {
TextInput {
input: self.input.placeholder(placeholder),
..self
}
}
pub fn classes(&mut self, classes: &[&str]) -> &mut Self {
pub fn classes(mut self, classes: &[&str]) -> Self {
self.classes = classes.into_iter().map(|s| s.to_string()).collect();
self
}
pub fn value_opt(&mut self, value: Option<String>) -> &mut Self {
self.input.value_opt(value);
self
pub fn value_opt(self, value: Option<String>) -> Self {
TextInput {
input: self.input.value_opt(value),
..self
}
}
pub fn error_opt(&mut self, error: Option<String>) -> &mut Self {
pub fn error_opt(mut self, error: Option<String>) -> Self {
self.error = error;
self
}
pub fn dark(&mut self, dark: bool) -> &mut Self {
pub fn dark(mut self, dark: bool) -> Self {
self.dark = dark;
self
}

78
toolkit/src/tile.rs Normal file
View file

@ -0,0 +1,78 @@
use crate::image::Image;
use std::rc::Rc;
#[derive(Clone, Copy, Debug)]
pub enum TileFlag {
Normal,
Primary,
}
#[derive(Clone)]
pub struct Tile {
pub(crate) title: Option<String>,
pub(crate) description: Option<String>,
pub(crate) link: Option<String>,
pub(crate) flag: TileFlag,
image: Rc<dyn Image>,
}
impl Tile {
pub fn new(image: impl Image + 'static) -> Self {
Tile {
title: None,
description: None,
link: None,
flag: TileFlag::Normal,
image: Rc::new(image),
}
}
pub fn title(mut self, title: &str) -> Self {
self.title = Some(title.to_owned());
self
}
pub fn description(mut self, description: &str) -> Self {
self.description = Some(description.to_owned());
self
}
pub fn link(mut self, link: &str) -> Self {
self.link = Some(link.to_owned());
self
}
pub fn flag(mut self, flag: TileFlag) -> Self {
self.flag = flag;
self
}
pub(crate) fn image(&self) -> &dyn Image {
&*self.image
}
pub(crate) fn class_string(&self) -> String {
let mut classes = vec!["toolkit-tile".to_owned()];
match self.flag {
TileFlag::Normal => (),
TileFlag::Primary => {
classes.push("toolkit-tile__primary".to_owned());
}
}
classes.join(" ")
}
}
impl std::fmt::Debug for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
f.debug_struct("Tile")
.field("title", &self.title)
.field("description", &self.description)
.field("link", &self.link)
.field("flag", &self.flag)
.field("image", &"Box<dyn Image>")
.finish()
}
}

View file

@ -1,11 +1,10 @@
@(dark: bool, img: Content)
@use crate::banner::Banner;
@use crate::templates::image;
@if dark {
<div class="toolkit-banner toolkit-dark">
@:img()
</div>
} else {
<div class="toolkit-banner">
@:img()
</div>
}
@(banner: &Banner)
<div class="@banner.class_string()">
@if let Some(img) = banner.image_opt() {
@:image(img)
}
</div>

View file

@ -3,7 +3,7 @@
@(button: &Button)
@if let Some(action) = button.inner.borrow().action.as_ref() {
@if let Some(action) = button.action.as_ref() {
<form class="@button.class_string()" method="POST" action="@action">
@:button_inner(button)
</form>

View file

@ -1,7 +1,7 @@
@use crate::Button;
@use super::button;
@(buttons: &[&Button])
@(buttons: &[Button])
<div class="toolkit-button-group">
@for btn in buttons {

View file

@ -2,18 +2,18 @@
@(button: &Button)
<span>@button.inner.borrow().label</span>
@if let Some(href) = button.inner.borrow().href.as_ref() {
@match button.inner.borrow().link_kind {
<span>@button.label</span>
@if let Some(href) = button.href.as_ref() {
@match button.link_kind {
LinkKind::CurrentTab => {
<a href="@href" class="toolkit-button--action">@button.inner.borrow().label</a>
<a href="@href" class="toolkit-button--action">@button.label</a>
}
LinkKind::NewTab => {
<a href="@href" target="_blank" rel="noopener noreferer" class="toolkit-button--action">
@button.inner.borrow().label
@button.label
</a>
}
}
} else {
<button class="toolkit-button--action">@button.inner.borrow().label</button>
<button class="toolkit-button--action">@button.label</button>
}

View file

@ -1,31 +1,12 @@
@(href: &str, small: bool, dark: bool, picture: Content)
@use crate::icon::Icon;
@use crate::templates::image;
@if small {
@if dark {
<a href="@href" class="toolkit-icon--link toolkit-icon--link__small toolkit-dark">
<div class="toolkit-icon">
@:picture()
</div>
</a>
} else {
<a href="@href" class="toolkit-icon--link toolkit-icon--link__small">
<div class="toolkit-icon">
@:picture()
</div>
</a>
}
} else {
@if dark {
<a href="@href" class="toolkit-icon--link toolkit-dark">
<div class="toolkit-icon">
@:picture()
</div>
</a>
} else {
<a href="@href" class="toolkit-icon--link">
<div class="toolkit-icon">
@:picture()
</div>
</a>
}
}
@(icon: &Icon)
<a href="@icon.href" class="@icon.class_string()">
<div class="toolkit-icon">
@if let Some(img) = icon.image_opt() {
@:image(img)
}
</div>
</a>

View file

@ -0,0 +1,17 @@
@use crate::image::Image;
@(image: &dyn Image)
<picture>
@if let Some(srcset) = image.webp_srcset() {
<source type="image/webp" srcset="@srcset" />
}
@if let Some(srcset) = image.png_srcset() {
<source type="image/png" srcset="@srcset" />
}
@if let Some(srcset) = image.jpeg_srcset() {
<source type="image/jpeg" srcset="@srcset" />
}
<img src="@image.src()" title="@image.title()" alt="@image.title()" />
</picture>

View file

@ -1,49 +1,26 @@
@use crate::templates::{icon, banner};
@use crate::profile::Profile;
@(view_path: &str, display_name: Option<&str>, handle: &str, description: Option<&str>, dark: bool, icon_img: Content, banner_img: Content)
@(profile: &Profile)
@if dark {
<div class="toolkit-profile toolkit-dark">
@:banner(dark, { @:banner_img() })
<div class="toolkit-profile--content">
<div class="toolkit-profile--content--top">
@:icon(view_path, false, dark, { @:icon_img() })
<div class="toolkit-profile--meta">
<div class="toolkit-profile--meta--display">
@if let Some(display_name) = display_name {
@display_name
} else {
&nbsp;
}
</div>
<div class="toolkit-profile--meta--handle">@handle</div>
<div class="@profile.class_string()">
@:banner(&profile.banner)
<div class="toolkit-profile--content">
<div class="toolkit-profile--content--top">
@:icon(&profile.icon)
<div class="toolkit-profile--meta">
<div class="toolkit-profile--meta--display">
@if let Some(display_name) = &profile.display_name {
@display_name
} else {
&nbsp;
}
</div>
<div class="toolkit-profile--meta--handle">@profile.handle</div>
</div>
@if let Some(description) = description {
<div class="toolkit-profile--description">@description</div>
}
</div>
@if let Some(description) = &profile.description {
<div class="toolkit-profile--description">@description</div>
}
</div>
} else {
<div class="toolkit-profile">
@:banner(dark, { @:banner_img() })
<div class="toolkit-profile--content">
<div class="toolkit-profile--content--top">
@:icon(view_path, false, dark, { @:icon_img() })
<div class="toolkit-profile--meta">
<div class="toolkit-profile--meta--display">
@if let Some(display_name) = display_name {
@display_name
} else {
&nbsp;
}
</div>
<div class="toolkit-profile--meta--handle">@handle</div>
</div>
</div>
@if let Some(description) = description {
<div class="toolkit-profile--description">@description</div>
}
</div>
</div>
}
</div>

View file

@ -0,0 +1,13 @@
@use crate::link::Link;
@use crate::tile::Tile;
@use crate::templates::{link, tile_inner};
@(tile: &Tile)
@if let Some(href) = &tile.link {
@:link(&Link::current_tab(href).plain(true).dark(true), {
@:tile_inner(tile)
})
} else {
@:tile_inner(tile)
}

View file

@ -0,0 +1,20 @@
@use crate::tile::Tile;
@use crate::templates::image;
@(tile: &Tile)
<div class="@tile.class_string()">
<div class="toolkit-tile--overlay">
@if tile.title.is_some() || tile.description.is_some() {
<div class="toolkit-tile--meta">
@if let Some(title) = &tile.title {
<h2>@title</h2>
}
@if let Some(description) = &tile.description {
<p>@description</p>
}
</div>
}
</div>
@:image(tile.image())
</div>

View file

@ -0,0 +1,5 @@
@(body: Content)
<div class="toolkit-tiles">
@:body()
</div>