feat: implement content block component

This commit is contained in:
jinhojang6 2023-05-12 01:00:04 +09:00 committed by Jinho Jang
parent 87ed14e1b2
commit 39fea87d83
12 changed files with 287 additions and 93 deletions

View File

@ -0,0 +1,32 @@
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
type Props = {
title: string
author: string
}
const ContentBlockBody = ({ title, author }: Props) => {
return (
<BlockBodyContainer>
<Typography variant="body1" component="div" genericFontFamily="serif">
{title}
</Typography>
<Typography
variant="body3"
component="div"
genericFontFamily="sans-serif"
>
{author}
</Typography>
</BlockBodyContainer>
)
}
const BlockBodyContainer = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
`
export default ContentBlockBody

View File

@ -0,0 +1,35 @@
import styled from '@emotion/styled'
import { Typography } from '@acid-info/lsd-react'
import { PostClassType } from '../Post/Post'
type Props = {
type: PostClassType
date: string
}
const ContentBlockHeader = ({ type, date }: Props) => {
return (
<ContentBlockInfo>
<Typography variant="body3" genericFontFamily="sans-serif">
{type.toUpperCase()}
</Typography>
<Typography variant="body3"></Typography>
<Typography variant="body3" genericFontFamily="sans-serif">
{new Date(date).toLocaleString('en-GB', {
day: 'numeric',
month: 'long',
year: 'numeric',
})}
</Typography>
</ContentBlockInfo>
)
}
const ContentBlockInfo = styled.div`
display: flex;
flex-direction: row;
align-items: center;
gap: 8px;
`
export default ContentBlockHeader

View File

@ -0,0 +1,64 @@
import Link from 'next/link'
import styled from '@emotion/styled'
import Image from 'next/image'
import { SearchResultItem } from '@/types/data.types'
import { UnbodyImageBlock } from '@/lib/unbody/unbody.types'
import { GridItem } from '../Grid/Grid'
import { PostClassType } from '../Post/Post'
import ContentBlockHeader from './ContentBlock.Header'
import ContentBlockBody from './ContentBlock.Body'
type Props = Omit<SearchResultItem<UnbodyImageBlock>, 'score'>
const ImageBlock = ({ doc }: Props) => {
return (
<CustomGridItem className="w-2">
<BlockLink href={`/article/${doc.document[0].slug}`}>
<Container>
<ImageContainer>
<Image fill src={doc.url} alt={doc.alt} />
</ImageContainer>
<ContentBlockHeader
type={PostClassType.ARTICLE}
// TODO: type error with GoogleCalendarEvent
// which one do we use for date between createdAt and modifiedAt?
date={doc?.document[0].modifiedAt}
/>
<ContentBlockBody
title={doc.document[0].title}
author="Jason Freeman"
/>
</Container>
</BlockLink>
</CustomGridItem>
)
}
const CustomGridItem = styled(GridItem)`
@media (max-width: 768px) {
grid-column: span 8 !important;
}
`
const BlockLink = styled(Link)`
text-decoration: none;
`
const Container = styled.div`
display: flex;
flex-direction: column;
gap: 8px;
padding: 16px 0;
border-top: 1px solid rgb(var(--lsd-theme-primary));
position: relative;
`
const ImageContainer = styled.div`
position: relative;
margin-bottom: 8px;
aspect-ratio: 1 / 1; // fixed aspect ratio temporarily
`
export default ImageBlock

View File

@ -0,0 +1,49 @@
import Link from 'next/link'
import styled from '@emotion/styled'
import { SearchResultItem } from '@/types/data.types'
import { UnbodyTextBlock } from '@/lib/unbody/unbody.types'
import { GridItem } from '../Grid/Grid'
import { Typography } from '@acid-info/lsd-react'
import { PostClassType } from '../Post/Post'
import ContentBlockHeader from './ContentBlock.Header'
import ContentBlockBody from './ContentBlock.Body'
type Props = Omit<SearchResultItem<UnbodyTextBlock>, 'score'>
const TextBlock = ({ doc }: Props) => {
return (
<GridItem className="w-4">
<BlockLink href={`/article/${doc.document[0].slug}`}>
<Container>
<ContentBlockHeader
type={PostClassType.ARTICLE}
date={doc?.document[0].modifiedAt}
/>
<Typography variant="body2" genericFontFamily="sans-serif">
{doc.text}
</Typography>
<ContentBlockBody
title={doc.document[0].title}
author="Jason Freeman"
/>
</Container>
</BlockLink>
</GridItem>
)
}
const BlockLink = styled(Link)`
text-decoration: none;
`
const Container = styled.div`
display: flex;
flex-direction: column;
gap: 16px;
padding: 16px 0;
border-top: 1px solid rgb(var(--lsd-theme-primary));
`
export default TextBlock

View File

@ -0,0 +1,2 @@
export { default as ImageBlock } from './ImageBlock'
export { default as TextBlock } from './TextBlock'

View File

@ -6,9 +6,10 @@ export const Grid = styled.div`
padding: 16px;
gap: 16px;
@media (max-width: 768px) {
// TODO: The mobile design works when commenting this out
/* @media (max-width: 768px) {
grid-template-columns: 100%;
}
} */
`
export const GridItem = styled.div`

View File

@ -0,0 +1,41 @@
import { getArticleCover } from '@/utils/data.utils'
import { Typography } from '@acid-info/lsd-react'
import styled from '@emotion/styled'
import { PostsList } from '../PostList/PostList'
import { Section } from '../Section/Section'
import { SearchResultItem } from '@/types/data.types'
import { UnbodyGoogleDoc } from '@/lib/unbody/unbody.types'
type Props = {
articles: SearchResultItem<UnbodyGoogleDoc>[]
}
export default function RelatedArticles({ articles }: Props) {
return (
<Container>
<Section title={'Related Articles'} matches={articles?.length}>
<PostsList
posts={articles.map((article) => ({
slug: article.doc.slug,
date: article.doc.modifiedAt,
title: article.doc.title,
description: article.doc.subtitle, // TODO: summary is not available
author: 'Jinho',
tags: article.doc.tags,
coverImage: getArticleCover(article.doc.blocks),
}))}
/>
</Section>
</Container>
)
}
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
@media (max-width: 768px) {
align-items: flex-start;
}
`

View File

@ -0,0 +1 @@
export { default as RelatedArticles } from './RelatedArticles'

View File

@ -0,0 +1,51 @@
import styled from '@emotion/styled'
import { Section } from '../Section/Section'
import { SearchResultItem } from '@/types/data.types'
import { UnbodyImageBlock, UnbodyTextBlock } from '@/lib/unbody/unbody.types'
import { Grid } from '../Grid/Grid'
import { ImageBlock, TextBlock } from '../ContentBlock'
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
type Props = {
blocks: SearchResultItem<UnbodyImageBlock | UnbodyTextBlock>[]
}
export default function RelatedContent({ blocks }: Props) {
return (
<Container>
<Section title={'Related Content'} matches={blocks?.length}>
<Grid>
{blocks.map(
(block: SearchResultItem<UnbodyImageBlock | UnbodyTextBlock>) => {
if (!block.doc.document || !block.doc.document[0]) return null
let refArticle = null
if (UnbodyGraphQl.UnbodyDocumentTypeNames.GoogleDoc) {
refArticle = block.doc.document[0]
}
switch (block.doc.__typename) {
case UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock:
return <TextBlock doc={block.doc} />
case UnbodyGraphQl.UnbodyDocumentTypeNames.ImageBlock: {
return <ImageBlock doc={block.doc} />
}
}
},
)}
</Grid>
</Section>
</Container>
)
}
const Container = styled.div`
display: flex;
flex-direction: column;
align-items: center;
margin-top: 108px;
@media (max-width: 768px) {
align-items: flex-start;
}
`

View File

@ -0,0 +1 @@
export { default as RelatedContent } from './RelatedContent'

View File

@ -4,30 +4,18 @@ import {
UnbodyImageBlock,
UnbodyTextBlock,
} from '@/lib/unbody/unbody.types'
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
import Image from 'next/image'
import unbodyApi from '@/services/unbody.service'
import { PostTypes, SearchResultItem } from '@/types/data.types'
import {
PostTypes,
SearchHook,
SearchHookDataPayload,
SearchResultItem,
SearchResults,
} from '@/types/data.types'
import {
createMinimizedSearchText,
extractQueryFromQuery,
extractTopicsFromQuery,
} from '@/utils/search.utils'
import { useRouter } from 'next/router'
import { ReactNode, useEffect, useRef, useState } from 'react'
import Link from 'next/link'
import { SearchLayout } from '@/layouts/SearchLayout'
import { Section } from '@/components/Section/Section'
import { PostsList } from '@/components/PostList/PostList'
import { getArticleCover } from '@/utils/data.utils'
import { RelatedArticles } from '@/components/RelatedArticles'
import { RelatedContent } from '@/components/RelatedContent'
interface SearchPageProps {
articles: SearchResultItem<UnbodyGoogleDoc>[]
@ -83,83 +71,10 @@ export default function SearchPage({
return (
<div>
{articles.data?.length && (
<Section title={'Related Articles'} matches={articles.data?.length}>
<PostsList
posts={articles.data.map((article) => ({
slug: article.doc.slug,
date: article.doc.modifiedAt,
title: article.doc.title,
description: article.doc.subtitle, // TODO: summary is not available
author: 'Jinho',
tags: article.doc.tags,
coverImage: getArticleCover(article.doc.blocks),
}))}
/>
</Section>
)}
{articles.data?.length && <RelatedArticles articles={articles.data} />}
<section>
<strong>Related content blocks</strong>
<hr />
<div>
{blocks.loading && <div>...</div>}
{!blocks.error && blocks.data && blocks.data.length > 0 ? (
blocks.data.map(
(block: SearchResultItem<UnbodyImageBlock | UnbodyTextBlock>) => {
if (!block.doc.document || !block.doc.document[0]) return null
let refArticle = null
if (UnbodyGraphQl.UnbodyDocumentTypeNames.GoogleDoc) {
refArticle = block.doc.document[0]
}
switch (block.doc.__typename) {
case UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock:
return (
<div key={block.doc.slug}>
{refArticle && (
<h3>
<Link href={`/articles/${refArticle.slug}`}>
{refArticle.title}
</Link>
</h3>
)}
{
<p
dangerouslySetInnerHTML={{ __html: block.doc.html }}
/>
}
</div>
)
case UnbodyGraphQl.UnbodyDocumentTypeNames.ImageBlock: {
return (
<div key={block.doc.slug}>
{refArticle && (
<h3>
<Link href={`/articles/${refArticle.slug}`}>
{refArticle.title}
</Link>
</h3>
)}
<Image
title={block.doc.alt}
src={block.doc.url}
width={block.doc.width}
height={block.doc.height}
alt={block.doc.alt}
/>
</div>
)
}
}
},
)
) : (
<div>Nothing found</div>
)}
</div>
</section>
{/* TODO: used initialBlocks instead of blocks.data temporarily to render data */}
{initialBlocks?.length && <RelatedContent blocks={initialBlocks} />}
</div>
)
}

View File

@ -21,6 +21,7 @@ export const getSearchBlocksQuery = (args: UnbodyGetFilters = defaultArgs) =>
title
mentions
slug
modifiedAt
__typename
}
}
@ -39,6 +40,7 @@ export const getSearchBlocksQuery = (args: UnbodyGetFilters = defaultArgs) =>
title
slug
mentions
modifiedAt
__typename
}
}