Skip to content

Commit 5a76a5b

Browse files
authored
feat: add date and author to blog card (#38)
1 parent 2fcce00 commit 5a76a5b

File tree

5 files changed

+151
-63
lines changed

5 files changed

+151
-63
lines changed

src/components/AuthorsByline.tsx

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { CollectionEntry } from 'astro:content'
2+
import Avatar from './Avatar'
3+
4+
interface AuthorsBylineProps {
5+
authors: CollectionEntry<'authors'>[]
6+
inline?: boolean
7+
}
8+
9+
const AuthorsByline: React.FC<AuthorsBylineProps> = ({ authors, inline }) => {
10+
return (
11+
<p className={`text-lg ${inline ? 'font-normal text-black/50' : 'font-medium pt-4'}`}>
12+
{inline ? 'v' : 'V'}on{' '}
13+
<Avatar
14+
jpgSrc={authors[0]?.data.image.default}
15+
webpSrc={authors[0]?.data.image.webp}
16+
alt={authors[0]?.data.image.alt}
17+
small
18+
/>{' '}
19+
{authors[0].data.firstName + ' ' + authors[0].data.lastName}
20+
{authors.length > 1 && (
21+
<>
22+
{' '}
23+
&
24+
<Avatar
25+
jpgSrc={authors[1]?.data.image.default}
26+
webpSrc={authors[1]?.data.image.webp}
27+
alt={authors[1]?.data.image.alt}
28+
small
29+
/>{' '}
30+
{authors[1].data.firstName + ' ' + authors[1].data.lastName}
31+
</>
32+
)}
33+
</p>
34+
)
35+
}
36+
37+
export default AuthorsByline

src/components/Card.tsx

Lines changed: 57 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import type { CollectionEntry } from 'astro:content'
2+
import { ConditionalWrapper } from './ConditionalWrapper'
13
import LinkButton from './LinkButton'
4+
import AuthorsByline from './AuthorsByline'
25

36
interface Props {
47
img?: string
@@ -8,6 +11,9 @@ interface Props {
811
href?: string
912
teaser?: string
1013
className?: string
14+
showButton?: boolean
15+
date?: string
16+
author?: CollectionEntry<'authors'>
1117
}
1218

1319
const Card: React.FC<Props> = ({
@@ -18,34 +24,61 @@ const Card: React.FC<Props> = ({
1824
teaser,
1925
href,
2026
className,
27+
showButton,
28+
date,
29+
author,
2130
}: Props) => {
2231
return (
23-
<div className={`flex-1 flex flex-col group ${className || ''}`}>
24-
{img && (
25-
<img
26-
className='w-full h-64 object-cover grayscale group-hover:grayscale-0 transition duration-300 ease-in-out'
27-
src={img}
28-
alt={imgAlt}
29-
/>
32+
<ConditionalWrapper
33+
condition={!showButton}
34+
wrapper={(children) => (
35+
<a href={href} className={`flex-1 flex flex-col gap-3 group ${className || ''}`}>
36+
{children}
37+
</a>
3038
)}
31-
{subtitle && (
32-
<h3
33-
className={`font-sans text-lg leading-5 tracking-widest uppercase font-normal text-black opacity-70 ${
34-
img ? 'pt-8' : ''
35-
} line-clamp-1`}
36-
title={subtitle}
37-
>
38-
{subtitle}
39-
</h3>
39+
falseWrapper={(children) => (
40+
<div className={`flex-1 flex flex-col gap-3 group ${className || ''}`}>{children}</div>
4041
)}
41-
{title && (
42-
<h2 className='font-grotesk font-medium text-2xl leading-8 mt-3 line-clamp-2'>{title}</h2>
43-
)}
44-
{teaser && <p className='pt-4'>{teaser}</p>}
45-
{href && (
46-
<LinkButton small caption={'Weiterlesen'} variant='dark' href={href} className='mt-5' />
47-
)}
48-
</div>
42+
>
43+
<>
44+
{img && (
45+
<img
46+
className='w-full h-64 object-cover grayscale group-hover:grayscale-0 transition duration-300 ease-in-out'
47+
src={img}
48+
alt={imgAlt}
49+
/>
50+
)}
51+
{subtitle && (
52+
<h3
53+
className={`font-sans text-lg leading-5 tracking-widest uppercase font-normal text-black opacity-70 ${
54+
img ? 'pt-4' : ''
55+
} line-clamp-1`}
56+
title={subtitle}
57+
>
58+
{subtitle}
59+
</h3>
60+
)}
61+
{title && (
62+
<h2 className='font-grotesk font-medium text-2xl leading-8 line-clamp-2'>{title}</h2>
63+
)}
64+
{teaser && <p className='pt-4 text-lg leading-6 text-black/75'>{teaser}</p>}
65+
{(date || author) && (
66+
<div className='flex gap-2 items-center font-medium'>
67+
{date && <p className='text-lg font-normal text-black/50'>{date}</p>}
68+
{author && (
69+
<>
70+
{'·'}
71+
<AuthorsByline authors={[author]} inline />
72+
</>
73+
)}
74+
</div>
75+
)}
76+
77+
{href && showButton && (
78+
<LinkButton small caption={'Weiterlesen'} variant='dark' href={href} className='mt-2' />
79+
)}
80+
</>
81+
</ConditionalWrapper>
4982
)
5083
}
5184

src/components/ConditionalWrapper.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { ReactElement, ReactNode } from 'react'
2+
3+
interface ConditionalWrapperProps {
4+
condition: boolean
5+
children: ReactElement | ReactNode
6+
wrapper: (c: ReactElement | ReactNode) => ReactElement | ReactNode | JSX.Element
7+
falseWrapper?: (c: ReactElement | ReactNode) => ReactElement | ReactNode | JSX.Element
8+
}
9+
10+
export const ConditionalWrapper: React.FC<ConditionalWrapperProps> = ({
11+
condition,
12+
wrapper,
13+
falseWrapper,
14+
children,
15+
}) => (condition ? wrapper(children) : falseWrapper ? falseWrapper(children) : children)

src/pages/blog/[slug].astro

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
---
22
import { SectionGridContainer, Avatar } from '@components'
3+
import AuthorsByline from '@components/AuthorsByline'
34
import BaseLayout from '@layouts/BaseLayout.astro'
45
import { getEntry, getCollection } from 'astro:content'
56
import type { CollectionEntry } from 'astro:content'
@@ -96,33 +97,7 @@ const schema = {
9697
{teaser}
9798
</p>
9899

99-
{
100-
Boolean(authors.length) && (
101-
<p class='text-lg font-medium pt-4'>
102-
Von
103-
<Avatar
104-
jpgSrc={authors[0]?.data.image.default}
105-
webpSrc={authors[0]?.data.image.webp}
106-
alt={authors[0]?.data.image.alt}
107-
small
108-
/>{' '}
109-
{authors[0].data.firstName + ' ' + authors[0].data.lastName}
110-
{authors.length > 1 && (
111-
<>
112-
{' '}
113-
&
114-
<Avatar
115-
jpgSrc={authors[1]?.data.image.default}
116-
webpSrc={authors[1]?.data.image.webp}
117-
alt={authors[1]?.data.image.alt}
118-
small
119-
/>{' '}
120-
{authors[1].data.firstName + ' ' + authors[1].data.lastName}
121-
</>
122-
)}
123-
</p>
124-
)
125-
}
100+
{Boolean(authors.length) && <AuthorsByline authors={authors} />}
126101
<div class='pb-12'>
127102
{
128103
!!date && (

src/pages/blog/index.astro

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
---
22
import BaseLayout from '@layouts/BaseLayout.astro'
33
import { Card, SectionGridContainer, SectionMark } from '@components'
4-
import { getCollection } from 'astro:content'
4+
import { getCollection, getEntry, type CollectionEntry } from 'astro:content'
55
import BbBracket from '@assets/bb_bracket.svg?react'
66
77
const contentSortedByDate = (await getCollection('blog')).sort(
88
(postA, postB) => postB?.data?.date?.getTime() - postA?.data?.date?.getTime(),
99
)
1010
const [firstPost, ...restPosts] = contentSortedByDate
11+
12+
const firstPostAuthor = await getEntry(
13+
firstPost.data.authors[0].collection,
14+
firstPost.data.authors[0].id,
15+
)
1116
---
1217

1318
<BaseLayout
@@ -28,17 +33,24 @@ const [firstPost, ...restPosts] = contentSortedByDate
2833
subtitle={firstPost.data.subtitle}
2934
href={'/blog/' + firstPost.slug + '/'}
3035
teaser={firstPost.data.teaser}
36+
date={firstPost.data.date.toLocaleDateString('de-DE', {
37+
year: 'numeric',
38+
month: 'long',
39+
})}
40+
author={firstPostAuthor}
3141
className='order-2 lg:order-1 col-span-1 lg:col-start-2 lg:col-span-4 px-5 lg:px-0'
42+
showButton
3243
/>
33-
<div
44+
<a
3445
class='order-1 lg:order-2 lg:col-start-7 col-span-1 lg:col-span-8 lg:h-[28rem] overflow-hidden lg:-mb-32'
46+
href={'/blog/' + firstPost.slug + '/'}
3547
>
3648
<img
3749
src={firstPost.data.image.url}
3850
alt={firstPost.data.image.alt}
3951
class='w-full h-auto object-cover'
4052
/>
41-
</div>
53+
</a>
4254
</SectionGridContainer>
4355

4456
<SectionGridContainer className='pt-16 lg:pt-48 pb-16' innerClassName='px-5 lg:px-0'>
@@ -51,15 +63,31 @@ const [firstPost, ...restPosts] = contentSortedByDate
5163
</div>
5264
<div class='col-span-1 lg:col-span-8 grid grid-cols-1 lg:grid-cols-2 gap-x-6 gap-y-16'>
5365
{
54-
restPosts.map((post) => (
55-
<Card
56-
title={post.data.title}
57-
subtitle={post.data.subtitle}
58-
img={post.data.image.url}
59-
imgAlt={post.data.image.alt}
60-
href={'/blog/' + post.slug + '/'}
61-
/>
62-
))
66+
restPosts.map(async (post) => {
67+
let authors = []
68+
69+
for (const author of post.data.authors) {
70+
const authorObj = await getEntry(author.collection, author.id)
71+
if (authorObj) {
72+
authors.push(authorObj)
73+
}
74+
}
75+
76+
return (
77+
<Card
78+
title={post.data.title}
79+
subtitle={post.data.subtitle}
80+
img={post.data.image.url}
81+
imgAlt={post.data.image.alt}
82+
href={'/blog/' + post.slug + '/'}
83+
date={post.data.date.toLocaleDateString('de-DE', {
84+
year: 'numeric',
85+
month: 'long',
86+
})}
87+
author={authors[0]}
88+
/>
89+
)
90+
})
6391
}
6492
</div>
6593
</SectionGridContainer>

0 commit comments

Comments
 (0)