Add JDD: Dynamic grid with tabs

Summary: Ref T2770

Reviewers: #testers, kuba-orlik

Reviewed By: #testers, kuba-orlik

Subscribers: kuba-orlik, jenkins-user

Maniphest Tasks: T2770

Differential Revision: https://hub.sealcode.org/D1476
This commit is contained in:
Mateusz Jarczyński 2024-05-21 18:28:48 +02:00
parent acef3b1e46
commit 07b115e2b7
2 changed files with 430 additions and 0 deletions

View File

@ -0,0 +1,248 @@
.dynamic-grid-component {
.dynamic-grid-title {
text-align: center;
}
.tabs-menu {
display: flex;
align-items: stretch;
list-style: none;
padding: 0;
border-bottom: 1px solid #ccc;
margin-bottom: 36px;
.tabs-menu-button {
display: flex;
margin-bottom: -1px;
border: 1px solid #ccc;
background: #eee;
color: #666;
font-size: 12px;
cursor: pointer;
transition: border 200ms, color 200ms;
label {
padding: 12px 15px;
}
}
.tabs-menu-button:hover {
border-top-color: #333;
color: #333;
}
}
.tabs-menu-radio:checked ~ .tabs-menu-button label {
border-bottom-color: #fff;
border-top-color: #b721ff;
background: #fff;
color: #222;
}
.tabs-menu-radio {
display: none;
}
.tab-container {
display: none;
.tiles-container {
display: grid;
grid-gap: 24px;
grid-template-columns: repeat(3, 1fr);
grid-auto-rows: 1fr;
grid-auto-flow: row dense;
.tile {
position: relative;
overflow: hidden;
.tile-content {
position: absolute;
background-color: #c3c3c3;
width: 100%;
opacity: 0.9;
padding: 8px;
z-index: 1;
overflow: hidden;
bottom: 0;
box-sizing: border-box;
.tile-content-wrapper {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 4;
line-height: 20px;
height: 103px;
overflow: hidden;
.tile-title {
font-weight: bold;
line-height: 20px;
margin: 10px 0;
}
.tile-subtitle {
line-height: 20px;
}
}
.spacer {
height: 5px;
}
}
.tile-image {
z-index: 0;
width: 100%;
height: 100%;
picture {
height: 100%;
width: 100% !important;
}
}
}
.tile.square {
grid-column: span 1;
grid-row: span 1;
.tile-image--horizontal {
display: none;
}
.tile-image--vertical {
display: none;
}
.tile-image--square {
picture img {
display: block !important;
}
}
}
.tile.horizontal {
grid-column: span 2;
grid-row: span 1;
.tile-image--vertical {
display: none;
}
.tile-image--square {
display: none;
}
.tile-image--horizontal {
picture img {
display: block !important;
}
}
}
.tile.vertical {
grid-row: span 2;
grid-column: span 1;
.tile-image--horizontal {
display: none;
}
.tile-image--square {
display: none;
}
.tile-image--vertical {
picture img {
display: block !important;
}
}
}
}
.buttons-container {
margin-top: 36px;
display: flex;
justify-content: center;
flex-flow: row wrap;
column-gap: 18px;
row-gap: 16px;
.button {
display: inline-block;
text-align: center;
padding: 8px 24px;
border: 1px solid #000000;
color: var(--text-color);
min-width: 240px;
line-height: 23px;
font-size: 16px;
&.dark {
background-color: #000000;
--text-color: #ffffff;
}
&.bright {
background-color: #ffffff;
--text-color: #000000;
}
}
}
}
@container (max-width: 950px) {
.tab-container {
.tiles-container {
grid-template-columns: repeat(2, 1fr);
.tile.vertical {
grid-row: span 1;
grid-column: span 1;
.tile-image--vertical {
display: none;
}
.tile-image--horizontal {
display: none;
}
.tile-image--square {
display: block;
picture img {
display: block !important;
}
}
}
}
}
}
@container (max-width: 700px) {
.tab-container {
.tiles-container {
grid-template-columns: 1fr;
max-width: 450px;
margin: 0 auto;
.tile.horizontal {
grid-column: span 1;
grid-row: span 1;
.tile-image--vertical {
display: none;
}
.tile-image--horizontal {
display: none;
}
.tile-image--square {
display: block;
picture img {
display: block !important;
}
}
}
}
}
}
}

View File

@ -0,0 +1,182 @@
import type { FlatTemplatable } from "tempstream";
import { TempstreamJSX } from "tempstream";
import type {
ExtractParsed,
ExtractStructuredComponentArgumentsParsed,
JDDContext,
} from "@sealcode/jdd";
import { Component, ComponentArguments } from "@sealcode/jdd";
type ExtractArray<T> = T extends Array<infer X> ? X : never;
const generate_id = (function* () {
while (true) {
for (let i = 1; i <= 10000; i++) {
yield i;
}
}
})();
const component_arguments = {
heading: new ComponentArguments.ShortText(),
tabs: new ComponentArguments.List(
new ComponentArguments.Structured({
name: new ComponentArguments.ShortText().setExampleValues([
"Tab 1",
"Tab 2",
"Tab 3",
]),
tiles: new ComponentArguments.List(
new ComponentArguments.Structured({
title: new ComponentArguments.ShortText(),
subtitle: new ComponentArguments.ShortText(),
url: new ComponentArguments.ShortText(),
photo: new ComponentArguments.Structured({
image: new ComponentArguments.Image(),
alt: new ComponentArguments.ShortText(),
}),
shape: new ComponentArguments.Enum([
"square",
"horizontal",
"vertical",
]),
})
),
buttons: new ComponentArguments.List(
new ComponentArguments.Structured({
text: new ComponentArguments.ShortText().setExampleValues([
"Button 1",
"Button 2",
"Button 3",
]),
color: new ComponentArguments.Enum(["dark", "bright"] as const),
link: new ComponentArguments.ShortText(),
})
),
})
),
} as const;
export class DynamicGrid extends Component<typeof component_arguments> {
getArguments() {
return component_arguments;
}
public image_sizes = {
square: { width: 400, height: 400 },
horizontal: { width: 824, height: 400 },
vertical: { width: 400, height: 824 },
};
renderTile(
jdd_context: JDDContext,
tile: ExtractArray<
ExtractArray<ExtractParsed<typeof component_arguments.tabs>>["tiles"]
>
) {
return (
<div class={["tile", tile.shape]}>
<div class="tile-content">
<div class="tile-content-wrapper">
<h3 class="tile-title">{tile.title}</h3>
<p class="tile-subtitle">{tile.subtitle}</p>
</div>
<div class="spacer"></div>
</div>
{(["square", "horizontal", "vertical"] as const).map((shape) => (
<div class={["tile-image", `tile-image--${shape}`]}>
{jdd_context.render_image(tile.photo.image, {
sizesAttr: `${this.image_sizes[shape].width}px`,
alt: tile.photo.alt,
container: {
...this.image_sizes[shape],
objectFit: "cover",
},
crop: this.image_sizes[shape],
imgStyle: "display: none;",
})}
</div>
))}
</div>
);
}
composeTab(
jdd_context: JDDContext,
tab: ExtractArray<ExtractParsed<typeof component_arguments.tabs>>,
tab_id: string
) {
return (
<div class="tab-container" id={tab_id}>
<div class="tiles-container">
{tab.tiles.map((tile) => this.renderTile(jdd_context, tile))}
</div>
<div class="buttons-container">
{tab.buttons.map((button) => (
<a class={["button", button.color]} href={button.link}>
{button.text}
</a>
))}
</div>
</div>
);
}
toHTML(
{
heading,
tabs,
}: ExtractStructuredComponentArgumentsParsed<typeof component_arguments>,
jdd_context: JDDContext
): FlatTemplatable {
const { value: id } = generate_id.next();
return (
<div class="dynamic-grid-component">
<h2 class="dynamic-grid-title">{heading}</h2>
<ul class="tabs-menu">
{tabs.map((tab, index) => {
return (
<li class="tabs-menu-button">
<label for={`input-${id}-${index}`}>{tab.name}</label>
{
/* HTML */ `<style>
body:has(#input-${id}-${index}:checked)
li:has([for="input-${id}-${index}"]) {
border-bottom-color: #fff;
border-top-color: #b721ff;
background: #fff;
color: #222;
}
</style>`
}
</li>
);
})}
</ul>
{tabs.map((tab, index) => {
return (
<section>
{this.composeTab(jdd_context, tab, `tab-${id}-${index}`)}
{
/* HTML */ `<style>
body:has(#input-${id}-${index}:checked)
.tab-container#tab-${id}-${index} {
display: block;
}
</style>`
}
<input
name={`tabs-menu-${id}`}
class="tabs-menu-radio"
type="radio"
id={`input-${id}-${index}`}
checked={index == 0}
/>
</section>
);
})}
</div>
);
}
}