feat(components): ✨ refactor and add BlogCard
and BlogPost
components
This commit is contained in:
parent
7cafcb5acd
commit
10f36ff4d3
164
components/BlogBlocks.css
Normal file
164
components/BlogBlocks.css
Normal file
|
@ -0,0 +1,164 @@
|
|||
.components__blog_block {
|
||||
min-width: 10rem;
|
||||
aspect-ratio: 3 / 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 0 0.4rem 0.2rem var(--_translucent);
|
||||
border: var(--_border-size) solid transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 80%;
|
||||
background-position: center var(--_gap);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
background-color: var(--_background-color);
|
||||
|
||||
&:has(a:focus-visible),
|
||||
&:hover {
|
||||
border: var(--_border-size) solid var(--_accent-color);
|
||||
}
|
||||
|
||||
& h3 {
|
||||
margin: 0;
|
||||
padding: var(--_gap) var(--_gap-half);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
background-color: var(--_translucent);
|
||||
border-bottom: 1px solid currentColor;
|
||||
}
|
||||
|
||||
& a {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_block--card {
|
||||
max-width: 20rem;
|
||||
}
|
||||
|
||||
.components__blog_block--placeholder {
|
||||
animation: var(--animation-blink);
|
||||
}
|
||||
|
||||
.components__blog_block--fallback {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
:is(.components__blog_block--placeholder, .components__blog_block--fallback) {
|
||||
h3 {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_block__spacer {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.components__blog_block--card .components__blog_block__spacer {
|
||||
height: 30%;
|
||||
}
|
||||
|
||||
.components__blog_block__links {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
gap: var(--_gap-half);
|
||||
justify-content: start;
|
||||
padding: var(--_gap-half);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
background-color: var(--_translucent);
|
||||
border-bottom: 1px solid currentColor;
|
||||
|
||||
& > a::before {
|
||||
content: '🔗';
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_block__tags {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
gap: var(--_gap-half);
|
||||
justify-content: start;
|
||||
padding: var(--_gap-half);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
background-color: var(--_translucent);
|
||||
border-bottom: 1px solid currentColor;
|
||||
|
||||
& > span::before {
|
||||
content: '#';
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_block__status {
|
||||
display: block;
|
||||
padding: var(--_gap-half);
|
||||
background-color: var(--_translucent);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
}
|
||||
|
||||
.components__blog_block--card .components__blog_block__status {
|
||||
position: absolute;
|
||||
top: var(--_gap-half);
|
||||
left: var(--_gap-half);
|
||||
font-size: larger;
|
||||
}
|
||||
|
||||
.components__blog_block__publisher {
|
||||
display: block;
|
||||
padding: var(--_gap-half);
|
||||
background-color: var(--_translucent);
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
}
|
||||
|
||||
.components__blog_block--card .components__blog_block__publisher {
|
||||
position: absolute;
|
||||
top: var(--_gap-half);
|
||||
right: var(--_gap-half);
|
||||
}
|
||||
|
||||
.components__blog_block__description {
|
||||
text-wrap: balance;
|
||||
flex-grow: 1;
|
||||
backdrop-filter: blur(var(--_blur));
|
||||
padding: var(--_gap-half);
|
||||
}
|
||||
|
||||
.components__blog_block--card .components__blog_block__description {
|
||||
min-height: 25%;
|
||||
}
|
||||
|
||||
.components__blog_block__body {
|
||||
text-wrap: balance;
|
||||
flex-grow: 1;
|
||||
padding: var(--_gap-half);
|
||||
}
|
||||
|
||||
.components__blog_block__footer {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
gap: var(--_gap);
|
||||
justify-content: space-between;
|
||||
padding: var(--_gap-half);
|
||||
background-color: var(--_background-color);
|
||||
}
|
||||
|
||||
.components__blog_post__infos {
|
||||
display: flex;
|
||||
gap: var(--_gap-half);
|
||||
margin-block: var(--_gap-half);
|
||||
|
||||
& > * {
|
||||
border: none;
|
||||
padding: var(--_gap-half);
|
||||
background-color: var(--_translucent);
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_post__description {
|
||||
margin-block: var(--_gap-half);
|
||||
font-family: var(--_font-family-code);
|
||||
}
|
||||
|
||||
.components__blog_block--post {
|
||||
width: var(--_readable-screen);
|
||||
margin-inline: auto;
|
||||
}
|
157
components/BlogBlocks.tsx
Normal file
157
components/BlogBlocks.tsx
Normal file
|
@ -0,0 +1,157 @@
|
|||
import { Markdown } from ':components/Markdown.tsx'
|
||||
import { NewsFrontMatter } from ':src/blog/types.ts'
|
||||
|
||||
export type BlogProps = {
|
||||
title: string
|
||||
description: string
|
||||
body: string
|
||||
author: string
|
||||
publisher: string
|
||||
lastUpdate: Date
|
||||
name: string
|
||||
url: string
|
||||
hash: string
|
||||
options: NewsFrontMatter['x-cohabit']
|
||||
tags: NewsFrontMatter['tags']
|
||||
}
|
||||
|
||||
export function BlogCard(
|
||||
{ title, description, author, lastUpdate, name, options, tags, publisher }:
|
||||
BlogProps,
|
||||
) {
|
||||
return (
|
||||
<div
|
||||
class='components__blog_block components__blog_block--card'
|
||||
style={{ backgroundImage: `url(${options.thumbnail})` }}
|
||||
>
|
||||
<div class='components__blog_block__spacer'></div>
|
||||
<h3>
|
||||
<a href={`/blog/${name}`}>{title}</a>
|
||||
</h3>
|
||||
<NewsLinks links={options.links} />
|
||||
<NewsTags tags={tags} />
|
||||
<span class='components__blog_block__publisher'>
|
||||
{`@${publisher}`}
|
||||
</span>
|
||||
<NewsStatus status={options.status} />
|
||||
<div class='components__blog_block__description'>
|
||||
{description}
|
||||
</div>
|
||||
<NewsFooter author={author} lastUpdate={lastUpdate} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function BlogPost(
|
||||
{
|
||||
title,
|
||||
description,
|
||||
author,
|
||||
lastUpdate,
|
||||
body,
|
||||
url,
|
||||
options,
|
||||
tags,
|
||||
publisher,
|
||||
}: BlogProps,
|
||||
) {
|
||||
return (
|
||||
<div class='components__blog_block--post'>
|
||||
<h1>{title}</h1>
|
||||
<div class='components__blog_post__infos'>
|
||||
<span class='components__blog_block__publisher'>
|
||||
{`@${publisher}`}
|
||||
</span>
|
||||
<NewsStatus status={options.status} long />
|
||||
<NewsLinks links={options.links} />
|
||||
<NewsTags tags={tags} />
|
||||
</div>
|
||||
<div class='components__blog_post__infos'>
|
||||
<span>{`Visibilité : ${options.visibility}`}</span>
|
||||
<span>
|
||||
{`Date de délivrance : ${
|
||||
new Date(options.dueDate).toLocaleString()
|
||||
}`}
|
||||
</span>
|
||||
</div>
|
||||
<div class='components__blog_post__description'>
|
||||
{description}
|
||||
</div>
|
||||
<div class='components__blog_post__body'>
|
||||
<Markdown options={{ allowMath: true, baseUrl: url }}>
|
||||
{body}
|
||||
</Markdown>
|
||||
</div>
|
||||
<NewsFooter author={author} lastUpdate={lastUpdate} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsTags({ tags }: Pick<BlogProps, 'tags'>) {
|
||||
return (
|
||||
<div class='components__blog_block__tags'>
|
||||
{tags
|
||||
? tags.map((tag) => <span>{tag}</span>)
|
||||
: <span>Aucun tag</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsFooter(
|
||||
{ author, lastUpdate }: Pick<BlogProps, 'author' | 'lastUpdate'>,
|
||||
) {
|
||||
return (
|
||||
<div class='components__blog_block__footer'>
|
||||
<div>
|
||||
<i class='ri-quill-pen-line'></i>
|
||||
<span>{author}</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class='ri-refresh-line'></i>
|
||||
<span>{lastUpdate.toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsLinks({ links }: Pick<BlogProps['options'], 'links'>) {
|
||||
return (
|
||||
<div class='components__blog_block__links'>
|
||||
{links
|
||||
? links.flatMap(Object.entries).map((
|
||||
[name, link],
|
||||
) => <a href={link} target='_blank' title={name}>{name}</a>)
|
||||
: <span>Aucun lien rapide</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function NewsStatus(
|
||||
{ status, long = false }: Pick<BlogProps['options'], 'status'> & {
|
||||
long?: boolean
|
||||
},
|
||||
) {
|
||||
const title = status === 'canceled'
|
||||
? 'Annulé'
|
||||
: status === 'current'
|
||||
? 'En cours'
|
||||
: status === 'finished'
|
||||
? 'Terminé'
|
||||
: 'Prévu'
|
||||
|
||||
return (
|
||||
<span
|
||||
class='components__blog_block__status'
|
||||
title={title}
|
||||
>
|
||||
{status === 'canceled'
|
||||
? <i class='ri-calendar-close-line'></i>
|
||||
: status === 'current'
|
||||
? <i class='ri-calendar-2-line'></i>
|
||||
: status === 'finished'
|
||||
? <i class='ri-calendar-check-line'></i>
|
||||
: <i class='ri-calendar-2-line'></i>}
|
||||
{long ? ` ${title}` : ''}
|
||||
</span>
|
||||
)
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
.components__blog_card {
|
||||
min-width: 10rem;
|
||||
aspect-ratio: 3 / 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--_gap-half);
|
||||
gap: var(--_gap);
|
||||
box-shadow: 0 0 0.4rem 0.2rem var(--_translucent);
|
||||
border: var(--_border-size) solid transparent;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
|
||||
&:has(a:focus-visible),
|
||||
&:hover {
|
||||
border: var(--_border-size) solid var(--_accent-color);
|
||||
}
|
||||
|
||||
& h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& a {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.components__blog_card__spacer {
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.components__blog_card__text {
|
||||
text-wrap: balance;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.components__blog_card__footer {
|
||||
height: fit-content;
|
||||
display: flex;
|
||||
gap: var(--_gap);
|
||||
justify-content: space-between;
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
type BlogCardProps = {
|
||||
img: string
|
||||
title: string
|
||||
text: string
|
||||
author: string
|
||||
lasUpdate: Date
|
||||
id: string
|
||||
}
|
||||
|
||||
export function BlogCard(
|
||||
{ img, title, text, author, lasUpdate, id }: BlogCardProps,
|
||||
) {
|
||||
return (
|
||||
<div class='components__blog_card' style={{ backgroundImage: img }}>
|
||||
<div class='components__blog_card__spacer'></div>
|
||||
<h3>
|
||||
<a href={`/blog/${id}`}>{title}</a>
|
||||
</h3>
|
||||
<div class='components__blog_card__text'>
|
||||
{`${text.slice(0, 150)} ...`}
|
||||
</div>
|
||||
<div class='components__blog_card__footer'>
|
||||
<div>
|
||||
<i class='ri-quill-pen-line'></i>
|
||||
<span>{author}</span>
|
||||
</div>
|
||||
<div>
|
||||
<i class='ri-refresh-line'></i>
|
||||
<span>{lasUpdate.toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const text =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Qui, perferendis enim blanditiis consequatur at porro quod, eligendi alias recusandae modi aliquam non? Quos voluptates quisquam provident animi nisi in ratione.'
|
||||
|
||||
export const blogMock: BlogCardProps[] = Array(50).fill(undefined).map(
|
||||
(_, index) => {
|
||||
return {
|
||||
author: 'PGP',
|
||||
lasUpdate: randomDate(),
|
||||
title: `Some title here ${index}`,
|
||||
text,
|
||||
img: `url("https://picsum.photos/id/${index}/300/200")`,
|
||||
id: String(index),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
function randomDate() {
|
||||
return new Date(Date.now() - Math.random() * 1e10)
|
||||
}
|
|
@ -44,7 +44,10 @@
|
|||
"@simplewebauthn/server": "npm:@simplewebauthn/server@^10.0.0",
|
||||
"@simplewebauthn/types": "npm:@simplewebauthn/types@^10.0.0",
|
||||
"@std/encoding": "jsr:@std/encoding@^0.224.3",
|
||||
"@std/front-matter": "jsr:@std/front-matter@^0.224.2",
|
||||
"@std/http": "jsr:@std/http@^0.224.4",
|
||||
"@std/json": "jsr:@std/json@^0.224.1",
|
||||
"@std/streams": "jsr:@std/streams@^0.224.5",
|
||||
"@univoq/": "https://deno.land/x/univoq@0.2.0/",
|
||||
"gfm": "https://deno.land/x/gfm@0.6.0/mod.ts",
|
||||
"preact": "https://esm.sh/preact@10.19.6",
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
@import url('../../components/Heros.css');
|
||||
@import url('../../components/SponsorCards.css');
|
||||
@import url('../../components/CohabitInfoTable.css');
|
||||
@import url('../../components/BlogCard.css');
|
||||
@import url('../../components/BlogBlocks.css');
|
||||
@import url('../../components/MachineCard.css');
|
||||
@import url('../../components/ProjectCard.css');
|
||||
@import url('../../components/AutoGrid.css');
|
||||
|
|
Loading…
Reference in a new issue