mirror of
https://github.com/acid-info/logos-press-engine.git
synced 2025-02-23 06:38:27 +00:00
refactor: separate article components
This commit is contained in:
parent
39fea87d83
commit
a9db723aca
19
src/components/Article/Article.Blocks.tsx
Normal file
19
src/components/Article/Article.Blocks.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import { getBodyBlocks } from '@/utils/data.utils'
|
||||
import { RenderArticleBlock } from './Article.Block'
|
||||
import { ArticlePostData } from '@/types/data.types'
|
||||
|
||||
type Props = {
|
||||
data: ArticlePostData
|
||||
}
|
||||
|
||||
const ArticleBlocks = ({ data }: Props) => {
|
||||
return data?.article.blocks.length ? (
|
||||
<>
|
||||
{getBodyBlocks(data.article).map((block, idx) => (
|
||||
<RenderArticleBlock key={'block-' + idx} block={block} />
|
||||
))}
|
||||
</>
|
||||
) : null
|
||||
}
|
||||
|
||||
export default ArticleBlocks
|
47
src/components/Article/Article.Body.tsx
Normal file
47
src/components/Article/Article.Body.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
import { ArticlePostData } from '@/types/data.types'
|
||||
import ArticleHeader from './Header/Article.Header'
|
||||
import ArticleFooter from './Footer/Article.Footer'
|
||||
import { MobileToc } from './Article.MobileToc'
|
||||
import ArticleBlocks from './Article.Blocks'
|
||||
|
||||
interface Props {
|
||||
data: ArticlePostData
|
||||
}
|
||||
|
||||
export default function ArticleBody({ data }: Props) {
|
||||
return (
|
||||
<ArticleContainer>
|
||||
<ArticleHeader {...data.article} />
|
||||
<MobileToc toc={data.article.toc} />
|
||||
<TextContainer>
|
||||
<ArticleBlocks data={data} />
|
||||
</TextContainer>
|
||||
<ArticleFooter data={data} />
|
||||
</ArticleContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ArticleContainer = styled.article`
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: 700px;
|
||||
margin-inline: 5%;
|
||||
padding-bottom: 80px;
|
||||
|
||||
// temporary breakpoint
|
||||
@media (max-width: 1024px) {
|
||||
margin-inline: 16px;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 80px;
|
||||
`
|
@ -28,6 +28,7 @@ const ThumbnailContainer = styled.div<{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 458px; // temporary max-height based on the Figma design's max height
|
||||
margin-bottom: 32px;
|
||||
`
|
||||
|
||||
const Thumbnail = styled(Image)`
|
||||
|
48
src/components/Article/Article.MobileToc.tsx
Normal file
48
src/components/Article/Article.MobileToc.tsx
Normal file
@ -0,0 +1,48 @@
|
||||
import styled from '@emotion/styled'
|
||||
import React from 'react'
|
||||
import { useArticleContainerContext } from '@/containers/ArticleContainer.Context'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styles from './Article.module.css'
|
||||
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
|
||||
import { Collapse } from '../Collapse'
|
||||
|
||||
type Props = {
|
||||
toc: Array<Partial<GoogleDocEnhanced>>
|
||||
}
|
||||
|
||||
export const MobileToc = ({ toc }: Props) => {
|
||||
const { tocIndex, setTocIndex } = useArticleContainerContext()
|
||||
|
||||
return toc?.length > 0 ? (
|
||||
<Collapse className={styles.mobileToc} label="Contents">
|
||||
{toc.map((toc, idx) => (
|
||||
<Content
|
||||
onClick={() => setTocIndex(idx)}
|
||||
active={idx === tocIndex}
|
||||
variant="body3"
|
||||
key={idx}
|
||||
>
|
||||
{toc.title}
|
||||
</Content>
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
}
|
||||
|
||||
const CustomTypography = styled(Typography)`
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
`
|
||||
|
||||
const Content = styled(CustomTypography)<{ active: boolean }>`
|
||||
padding: 8px 14px;
|
||||
background-color: ${(p) =>
|
||||
p.active
|
||||
? 'rgb(var(--lsd-theme-primary))'
|
||||
: 'rgb(var(--lsd-theme-secondary))'};
|
||||
color: ${(p) =>
|
||||
p.active
|
||||
? 'rgb(var(--lsd-theme-secondary))'
|
||||
: 'rgb(var(--lsd-theme-primary))'};
|
||||
`
|
36
src/components/Article/Article.Stats.tsx
Normal file
36
src/components/Article/Article.Stats.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleStats = ({
|
||||
dateStr,
|
||||
readingLength,
|
||||
}: {
|
||||
dateStr: string
|
||||
readingLength: number
|
||||
}) => (
|
||||
<div>
|
||||
<Row>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{readingLength} minutes read
|
||||
</Typography>
|
||||
<Typography variant="body3">•</Typography>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{new Date(dateStr).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'long', // TODO: Should be uppercase
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Typography>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
export default ArticleStats
|
@ -1,7 +1,3 @@
|
||||
.relatedArticles > div > button {
|
||||
border-top: none !important;
|
||||
}
|
||||
|
||||
/* temporary breakpoint */
|
||||
@media (min-width: 1024px) {
|
||||
.mobileToc {
|
||||
|
@ -1,338 +0,0 @@
|
||||
import { Quote, Tag, Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import Image from 'next/image'
|
||||
import { useMemo } from 'react'
|
||||
import { PostImageRatio, PostImageRatioOptions } from '../Post/Post'
|
||||
import styles from './Article.module.css'
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
import { useArticleContainerContext } from '@/containers/ArticleContainer.Context'
|
||||
import { ArticleReference } from '../ArticleReference'
|
||||
import {
|
||||
GoogleDocEnhanced,
|
||||
UnbodyGoogleDoc,
|
||||
UnbodyImageBlock,
|
||||
UnbodyTextBlock,
|
||||
UnbodyTocItem,
|
||||
} from '@/lib/unbody/unbody.types'
|
||||
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
|
||||
|
||||
import { RenderArticleBlock } from './Article.Block'
|
||||
import { ArticleImageBlockWrapper } from './Article.ImageBlockWrapper'
|
||||
import { getArticleCover, getBodyBlocks } from '@/utils/data.utils'
|
||||
import { ArticlePostData } from '@/types/data.types'
|
||||
|
||||
interface Props {
|
||||
data: ArticlePostData
|
||||
}
|
||||
|
||||
//@jinho
|
||||
//TODO please move everything to a separate file
|
||||
const ArticleTags = ({ tags }: { tags: string[] }) =>
|
||||
tags.length > 0 ? (
|
||||
<TagContainer>
|
||||
{tags.map((tag) => (
|
||||
<Tag size="small" disabled={false} key={tag}>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</TagContainer>
|
||||
) : null
|
||||
|
||||
const ArticleAuthor = ({
|
||||
mention,
|
||||
}: {
|
||||
mention: UnbodyGraphQl.Fragments.MentionItem
|
||||
}) => (
|
||||
<AuthorInfo key={mention.name}>
|
||||
<Typography variant="body3" component="p" genericFontFamily="sans-serif">
|
||||
{mention.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
href={`mailto:${mention.emailAddress}`}
|
||||
variant="body3"
|
||||
component="a"
|
||||
genericFontFamily="sans-serif"
|
||||
>
|
||||
{mention.emailAddress}
|
||||
</Typography>
|
||||
</AuthorInfo>
|
||||
)
|
||||
|
||||
const ArticleAuthors = ({
|
||||
mentions,
|
||||
}: {
|
||||
mentions: UnbodyGraphQl.Fragments.MentionItem[]
|
||||
}) =>
|
||||
mentions.length > 0 ? (
|
||||
<div>
|
||||
{mentions.map((mention) => (
|
||||
<ArticleAuthor key={mention.name} mention={mention} />
|
||||
))}
|
||||
</div>
|
||||
) : null
|
||||
|
||||
const ArticleStats = ({
|
||||
dateStr,
|
||||
readingLength,
|
||||
}: {
|
||||
dateStr: string
|
||||
readingLength: number
|
||||
}) => (
|
||||
<div>
|
||||
<Row>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{readingLength} minutes read
|
||||
</Typography>
|
||||
<Typography variant="body3">•</Typography>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{new Date(dateStr).toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'long', // TODO: Should be uppercase
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Typography>
|
||||
</Row>
|
||||
</div>
|
||||
)
|
||||
|
||||
const ArticleSummary = ({ summary }: { summary: string }) => (
|
||||
//TODO for ihor to work out the design for this
|
||||
<ArticleSummaryContainer>
|
||||
<Quote>{summary}</Quote>
|
||||
</ArticleSummaryContainer>
|
||||
)
|
||||
|
||||
const ArticleHeader = ({
|
||||
title,
|
||||
toc,
|
||||
summary,
|
||||
subtitle,
|
||||
mentions,
|
||||
tags,
|
||||
modifiedAt,
|
||||
blocks,
|
||||
}: GoogleDocEnhanced) => {
|
||||
const date = new Date(modifiedAt)
|
||||
|
||||
const _thumbnail = useMemo(() => {
|
||||
const coverImage = getArticleCover(blocks)
|
||||
if (!coverImage) return null
|
||||
return (
|
||||
<ArticleImageBlockWrapper
|
||||
ratio={PostImageRatio.LANDSCAPE}
|
||||
image={coverImage}
|
||||
/>
|
||||
)
|
||||
}, [blocks])
|
||||
|
||||
return (
|
||||
<header>
|
||||
<ArticleStats dateStr={modifiedAt} readingLength={3} />
|
||||
<ArticleTitle
|
||||
id={toc[0].href.substring(1)}
|
||||
variant={'h1'}
|
||||
genericFontFamily="serif"
|
||||
>
|
||||
{title}
|
||||
</ArticleTitle>
|
||||
{subtitle && <ArticleSubtitle>{subtitle}</ArticleSubtitle>}
|
||||
<ArticleTags tags={tags} />
|
||||
<ArticleAuthors mentions={mentions} />
|
||||
{_thumbnail}
|
||||
<ArticleSummary summary={summary} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
const ArticleFootenotes = ({
|
||||
footnotes,
|
||||
}: {
|
||||
footnotes: UnbodyGraphQl.Fragments.FootnoteItem[]
|
||||
}) =>
|
||||
footnotes.length > 0 ? (
|
||||
<Collapse label="Footenotes">
|
||||
{footnotes.map((footnote, idx) => (
|
||||
<Reference key={idx}>
|
||||
<Typography
|
||||
component="a"
|
||||
variant="body3"
|
||||
href={`#${footnote.refId}`}
|
||||
target="_blank"
|
||||
id={footnote.id.replace('#', '')}
|
||||
>
|
||||
{footnote.refValue}
|
||||
</Typography>
|
||||
<p dangerouslySetInnerHTML={{ __html: footnote.valueHTML }} />
|
||||
</Reference>
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
const RelatedArticles = ({ data }: { data: GoogleDocEnhanced[] }) =>
|
||||
data.length > 0 ? (
|
||||
<Collapse label="Related Articles">
|
||||
{data.map((article, idx) => (
|
||||
<ArticleReference key={idx} data={article} />
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
const FromSameAuthorsArticles = ({ data }: { data: GoogleDocEnhanced[] }) =>
|
||||
data.length > 0 ? (
|
||||
<Collapse label="From same authors">
|
||||
{data.map((article, idx) => (
|
||||
<ArticleReference key={idx} data={article} />
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
const ArticleFooter = ({ data }: { data: ArticlePostData }) => {
|
||||
const { article, relatedArticles, articlesFromSameAuthors } = data
|
||||
|
||||
const footnotes = useMemo(() => {
|
||||
return (
|
||||
article.blocks
|
||||
// @ts-ignore
|
||||
.flatMap((b) =>
|
||||
b.__typename === UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock
|
||||
? b.footnotes
|
||||
: [],
|
||||
)
|
||||
)
|
||||
}, [article])
|
||||
|
||||
return (
|
||||
<ArticleFooterContainer>
|
||||
<ArticleFootenotes
|
||||
footnotes={footnotes as Array<UnbodyGraphQl.Fragments.FootnoteItem>}
|
||||
/>
|
||||
<RelatedArticles data={relatedArticles} />
|
||||
<FromSameAuthorsArticles data={articlesFromSameAuthors} />
|
||||
</ArticleFooterContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomTypography = styled(Typography)`
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
`
|
||||
|
||||
const ArticleTitle = styled(CustomTypography)`
|
||||
//margin-bottom: 24px;
|
||||
`
|
||||
|
||||
const ArticleSubtitle = styled(CustomTypography)``
|
||||
|
||||
const ArticleSummaryContainer = styled('div')`
|
||||
padding-left: 40px;
|
||||
`
|
||||
|
||||
export default function ArticleBody({ data }: Props) {
|
||||
const { title, summary, subtitle, blocks, toc, createdAt, tags, mentions } =
|
||||
data.article
|
||||
const articleContainer = useArticleContainerContext()
|
||||
const { tocIndex, setTocIndex } = articleContainer
|
||||
|
||||
const _blocks = useMemo(() => {
|
||||
return getBodyBlocks(data.article).map((block, idx) => (
|
||||
<RenderArticleBlock key={'block-' + idx} block={block} />
|
||||
))
|
||||
}, [data.article])
|
||||
|
||||
console.log(data)
|
||||
|
||||
//TODO
|
||||
//@Jinho please move everything (starts with _ ) to a separate file and import it here
|
||||
const _mobileToc = useMemo(
|
||||
() =>
|
||||
toc?.length > 0 && (
|
||||
<Collapse className={styles.mobileToc} label="Contents">
|
||||
{toc.map((toc, idx) => (
|
||||
<Content
|
||||
onClick={() => setTocIndex(idx)}
|
||||
active={idx === tocIndex}
|
||||
variant="body3"
|
||||
key={idx}
|
||||
>
|
||||
{toc.title}
|
||||
</Content>
|
||||
))}
|
||||
</Collapse>
|
||||
),
|
||||
[toc, tocIndex],
|
||||
)
|
||||
|
||||
return (
|
||||
<ArticleContainer>
|
||||
<ArticleHeader {...data.article} />
|
||||
{_mobileToc}
|
||||
<TextContainer>{_blocks}</TextContainer>
|
||||
<ArticleFooter data={data} />
|
||||
</ArticleContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ArticleContainer = styled.article`
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
max-width: 700px;
|
||||
margin-inline: 5%;
|
||||
padding-bottom: 80px;
|
||||
|
||||
// temporary breakpoint
|
||||
@media (max-width: 1024px) {
|
||||
margin-inline: 16px;
|
||||
}
|
||||
`
|
||||
|
||||
const TextContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 80px;
|
||||
`
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
const TagContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const Content = styled(CustomTypography)<{ active: boolean }>`
|
||||
padding: 8px 14px;
|
||||
background-color: ${(p) =>
|
||||
p.active
|
||||
? 'rgb(var(--lsd-theme-primary))'
|
||||
: 'rgb(var(--lsd-theme-secondary))'};
|
||||
color: ${(p) =>
|
||||
p.active
|
||||
? 'rgb(var(--lsd-theme-secondary))'
|
||||
: 'rgb(var(--lsd-theme-primary))'};
|
||||
`
|
||||
|
||||
const Reference = styled.div`
|
||||
display: flex;
|
||||
padding: 8px 14px;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
const ArticleFooterContainer = styled.div`
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
const AuthorInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
`
|
43
src/components/Article/Footer/Article.Footer.tsx
Normal file
43
src/components/Article/Footer/Article.Footer.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
import { ArticlePostData } from '@/types/data.types'
|
||||
import { useMemo } from 'react'
|
||||
import ArticleFootnotes from './Article.Footnotes'
|
||||
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
|
||||
import styled from '@emotion/styled'
|
||||
import FromSameAuthorsArticles from './Article.FromSameAuthorsArticles'
|
||||
import ArticleRelatedArticles from './Article.RelatedArticles'
|
||||
|
||||
const ArticleFooter = ({ data }: { data: ArticlePostData }) => {
|
||||
const { article, relatedArticles, articlesFromSameAuthors } = data
|
||||
|
||||
const footnotes = useMemo(() => {
|
||||
return (
|
||||
article.blocks
|
||||
// @ts-ignore
|
||||
.flatMap((b) =>
|
||||
b.__typename === UnbodyGraphQl.UnbodyDocumentTypeNames.TextBlock
|
||||
? b.footnotes
|
||||
: [],
|
||||
)
|
||||
)
|
||||
}, [article])
|
||||
|
||||
return (
|
||||
<ArticleFooterContainer>
|
||||
<ArticleFootnotes
|
||||
footnotes={footnotes as Array<UnbodyGraphQl.Fragments.FootnoteItem>}
|
||||
/>
|
||||
<ArticleRelatedArticles data={relatedArticles} />
|
||||
<FromSameAuthorsArticles data={articlesFromSameAuthors} />
|
||||
</ArticleFooterContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const ArticleFooterContainer = styled.div`
|
||||
margin-top: 16px;
|
||||
|
||||
& > div:not(:first-child) > div > button {
|
||||
border-top: none;
|
||||
}
|
||||
`
|
||||
|
||||
export default ArticleFooter
|
36
src/components/Article/Footer/Article.Footnotes.tsx
Normal file
36
src/components/Article/Footer/Article.Footnotes.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleFootnotes = ({
|
||||
footnotes,
|
||||
}: {
|
||||
footnotes: UnbodyGraphQl.Fragments.FootnoteItem[]
|
||||
}) =>
|
||||
footnotes.length > 0 ? (
|
||||
<Collapse label="Footenotes">
|
||||
{footnotes.map((footnote, idx) => (
|
||||
<Reference key={idx}>
|
||||
<Typography
|
||||
component="a"
|
||||
variant="body3"
|
||||
href={`#${footnote.refId}`}
|
||||
target="_blank"
|
||||
id={footnote.id.replace('#', '')}
|
||||
>
|
||||
{footnote.refValue}
|
||||
</Typography>
|
||||
<p dangerouslySetInnerHTML={{ __html: footnote.valueHTML }} />
|
||||
</Reference>
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
const Reference = styled.div`
|
||||
display: flex;
|
||||
padding: 8px 14px;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
export default ArticleFootnotes
|
@ -0,0 +1,15 @@
|
||||
import { ArticleReference } from '@/components/ArticleReference'
|
||||
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
|
||||
import styles from '../Article.module.css'
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
|
||||
const FromSameAuthorsArticles = ({ data }: { data: GoogleDocEnhanced[] }) =>
|
||||
data.length > 0 ? (
|
||||
<Collapse className={styles.relatedArticles} label="From same authors">
|
||||
{data.map((article, idx) => (
|
||||
<ArticleReference key={idx} data={article} />
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
export default FromSameAuthorsArticles
|
14
src/components/Article/Footer/Article.RelatedArticles.tsx
Normal file
14
src/components/Article/Footer/Article.RelatedArticles.tsx
Normal file
@ -0,0 +1,14 @@
|
||||
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
|
||||
import { ArticleReference } from '@/components/ArticleReference'
|
||||
import { Collapse } from '@/components/Collapse'
|
||||
|
||||
const ArticleRelatedArticles = ({ data }: { data: GoogleDocEnhanced[] }) =>
|
||||
data.length > 0 ? (
|
||||
<Collapse label="Related Articles">
|
||||
{data.map((article, idx) => (
|
||||
<ArticleReference key={idx} data={article} />
|
||||
))}
|
||||
</Collapse>
|
||||
) : null
|
||||
|
||||
export default ArticleRelatedArticles
|
32
src/components/Article/Header/Article.Author.tsx
Normal file
32
src/components/Article/Header/Article.Author.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleAuthor = ({
|
||||
mention,
|
||||
}: {
|
||||
mention: UnbodyGraphQl.Fragments.MentionItem
|
||||
}) => (
|
||||
<AuthorInfo key={mention.name}>
|
||||
<Typography variant="body3" component="p" genericFontFamily="sans-serif">
|
||||
{mention.name}
|
||||
</Typography>
|
||||
<Typography
|
||||
href={`mailto:${mention.emailAddress}`}
|
||||
variant="body3"
|
||||
component="a"
|
||||
genericFontFamily="sans-serif"
|
||||
>
|
||||
{mention.emailAddress}
|
||||
</Typography>
|
||||
</AuthorInfo>
|
||||
)
|
||||
|
||||
const AuthorInfo = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
`
|
||||
|
||||
export default ArticleAuthor
|
25
src/components/Article/Header/Article.Authors.tsx
Normal file
25
src/components/Article/Header/Article.Authors.tsx
Normal file
@ -0,0 +1,25 @@
|
||||
import { UnbodyGraphQl } from '@/lib/unbody/unbody-content.types'
|
||||
import ArticleAuthor from './Article.Author'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleAuthors = ({
|
||||
mentions,
|
||||
}: {
|
||||
mentions: UnbodyGraphQl.Fragments.MentionItem[]
|
||||
}) =>
|
||||
mentions.length > 0 ? (
|
||||
<ArticleAuthorsContainer>
|
||||
{mentions.map((mention) => (
|
||||
<ArticleAuthor key={mention.name} mention={mention} />
|
||||
))}
|
||||
</ArticleAuthorsContainer>
|
||||
) : null
|
||||
|
||||
const ArticleAuthorsContainer = styled.div`
|
||||
margin-block: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
`
|
||||
|
||||
export default ArticleAuthors
|
67
src/components/Article/Header/Article.Header.tsx
Normal file
67
src/components/Article/Header/Article.Header.tsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
|
||||
import { getArticleCover } from '@/utils/data.utils'
|
||||
import { useMemo } from 'react'
|
||||
import { ArticleImageBlockWrapper } from '../Article.ImageBlockWrapper'
|
||||
import { PostImageRatio } from '../../Post/Post'
|
||||
import ArticleStats from '../Article.Stats'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import ArticleTags from './Article.Tags'
|
||||
import ArticleAuthors from './Article.Authors'
|
||||
import ArticleSummary from './Article.Summary'
|
||||
|
||||
const ArticleHeader = ({
|
||||
title,
|
||||
toc,
|
||||
summary,
|
||||
subtitle,
|
||||
mentions,
|
||||
tags,
|
||||
modifiedAt,
|
||||
blocks,
|
||||
}: GoogleDocEnhanced) => {
|
||||
const date = new Date(modifiedAt)
|
||||
|
||||
const _thumbnail = useMemo(() => {
|
||||
const coverImage = getArticleCover(blocks)
|
||||
if (!coverImage) return null
|
||||
return (
|
||||
<ArticleImageBlockWrapper
|
||||
ratio={PostImageRatio.LANDSCAPE}
|
||||
image={coverImage}
|
||||
/>
|
||||
)
|
||||
}, [blocks])
|
||||
|
||||
return (
|
||||
<header>
|
||||
<ArticleStats dateStr={modifiedAt} readingLength={3} />
|
||||
<ArticleTitle
|
||||
id={toc[0].href.substring(1)}
|
||||
variant={'h1'}
|
||||
genericFontFamily="serif"
|
||||
>
|
||||
{title}
|
||||
</ArticleTitle>
|
||||
{subtitle && <ArticleSubtitle>{subtitle}</ArticleSubtitle>}
|
||||
<ArticleTags tags={tags} />
|
||||
<ArticleAuthors mentions={mentions} />
|
||||
{_thumbnail}
|
||||
<ArticleSummary summary={summary} />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomTypography = styled(Typography)`
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
white-space: pre-wrap;
|
||||
`
|
||||
|
||||
const ArticleTitle = styled(CustomTypography)`
|
||||
margin-bottom: 24px;
|
||||
`
|
||||
|
||||
const ArticleSubtitle = styled(CustomTypography)``
|
||||
|
||||
export default ArticleHeader
|
16
src/components/Article/Header/Article.Summary.tsx
Normal file
16
src/components/Article/Header/Article.Summary.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import { Quote } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleSummary = ({ summary }: { summary: string }) => (
|
||||
//TODO for ihor to work out the design for this
|
||||
<ArticleSummaryContainer>
|
||||
<Quote>{summary}</Quote>
|
||||
</ArticleSummaryContainer>
|
||||
)
|
||||
|
||||
const ArticleSummaryContainer = styled('div')`
|
||||
padding-left: 40px;
|
||||
margin-bottom: 32px;
|
||||
`
|
||||
|
||||
export default ArticleSummary
|
21
src/components/Article/Header/Article.Tags.tsx
Normal file
21
src/components/Article/Header/Article.Tags.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import { Tag } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
const ArticleTags = ({ tags }: { tags: string[] }) =>
|
||||
tags.length > 0 ? (
|
||||
<TagContainer>
|
||||
{tags.map((tag) => (
|
||||
<Tag size="small" disabled={false} key={tag}>
|
||||
{tag}
|
||||
</Tag>
|
||||
))}
|
||||
</TagContainer>
|
||||
) : null
|
||||
|
||||
const TagContainer = styled.div`
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-top: 16px;
|
||||
`
|
||||
|
||||
export default ArticleTags
|
@ -1 +1 @@
|
||||
export { default as Article } from './Article'
|
||||
export { default as Article } from './Article.Body'
|
||||
|
@ -1,5 +1,3 @@
|
||||
import { ArticleReferenceType } from '@/components/ArticleReference/ArticleReference'
|
||||
|
||||
// temporary type
|
||||
export type ReferenceType = {
|
||||
text: string
|
||||
@ -18,7 +16,7 @@ export const references: ReferenceType[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const moreFromAuthor: ArticleReferenceType[] = [
|
||||
export const moreFromAuthor = [
|
||||
{
|
||||
title: 'How to Build a Practical Household Bike Generator',
|
||||
author: 'Jason Freeman',
|
||||
@ -31,7 +29,7 @@ export const moreFromAuthor: ArticleReferenceType[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const relatedArticles: ArticleReferenceType[] = [
|
||||
export const relatedArticles = [
|
||||
{
|
||||
title: 'How to Build a Practical Household Bike Generator',
|
||||
author: 'Jason Freeman',
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { GoogleDocEnhanced, UnbodyGoogleDoc } from '@/lib/unbody/unbody.types'
|
||||
import { GoogleDocEnhanced } from '@/lib/unbody/unbody.types'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
|
||||
@ -24,7 +24,7 @@ export default function ArticleReference({
|
||||
<div>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{/*TODO we need handle multiple authors for same article*/}
|
||||
{mentions[0].name}
|
||||
{mentions[0]?.name}
|
||||
</Typography>
|
||||
<Typography variant="body3">•</Typography>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
|
@ -1,14 +1,8 @@
|
||||
import { TableOfContents } from '@/components/TableOfContents'
|
||||
import styled from '@emotion/styled'
|
||||
import { useState } from 'react'
|
||||
import { uiConfigs } from '@/configs/ui.configs'
|
||||
import { ArticleContainerContext } from '@/containers/ArticleContainer.Context'
|
||||
import {
|
||||
GoogleDocEnhanced,
|
||||
UnbodyGoogleDoc,
|
||||
UnbodyTocItem,
|
||||
} from '@/lib/unbody/unbody.types'
|
||||
import ArticleBody from '@/components/Article/ArticleBody'
|
||||
import ArticleBody from '@/components/Article/Article.Body'
|
||||
import { ArticlePostData } from '@/types/data.types'
|
||||
|
||||
interface Props {
|
||||
|
Loading…
x
Reference in New Issue
Block a user