-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(ui): add recent project cards & latest post to home page
- Loading branch information
Showing
17 changed files
with
3,644 additions
and
3,161 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
import { graphql, navigate, useStaticQuery } from 'gatsby'; | ||
import { useCallback, useMemo } from 'react'; | ||
import { Text, Badge, Card, Group, Image } from '@mantine/core'; | ||
|
||
import { IconArticle } from '@tabler/icons-react'; | ||
import { formatDateMonthName } from '../utils/format-date'; | ||
import getImageUrl from '../utils/getImageUrl'; | ||
import PaneContainer from './pane-container'; | ||
import Placeholder from './placeholder'; | ||
|
||
interface LatestPostCardProps { | ||
data: Queries.LatestPostQuery['allStrapiArticle']['nodes'][0]; | ||
onClick: () => void; | ||
} | ||
|
||
const LatestPostCard = ({ data, onClick }: LatestPostCardProps) => { | ||
const sortedTag = useMemo(() => { | ||
const tags = ( | ||
data.tags as { | ||
name: string; | ||
color: string; | ||
}[] | ||
).sort((prev, next) => { | ||
if (prev.name === next.name) return 0; | ||
if (prev.name > next.name) return 1; | ||
return -1; | ||
}); | ||
return tags; | ||
}, [data.tags]); | ||
|
||
return ( | ||
<Card | ||
shadow="sm" | ||
padding="lg" | ||
radius="md" | ||
withBorder | ||
onClick={onClick} | ||
className="transition-all border-4 cursor-pointer latest-post-border" | ||
> | ||
<Card.Section> | ||
<Image | ||
src={getImageUrl(data.cover?.formats?.medium?.url ?? '')} | ||
className="brightness-[0.5] h-full w-full bg-clip-text" | ||
/> | ||
</Card.Section> | ||
<Group justify="space-between" mt="md" mb="xs" className="flex flex-col"> | ||
<Text fw={900} className="flex flex-row gap-2 text-center"> | ||
<IconArticle /> | ||
{data.title} | ||
</Text> | ||
<div className="flex flex-wrap gap-1 uppercase"> | ||
{sortedTag != null && | ||
sortedTag.map((t) => ( | ||
<Badge key={t!.name} color={t!.color as string} size="xs"> | ||
{t!.name as string} | ||
</Badge> | ||
))} | ||
</div> | ||
<div className="flex text-xs uppercase"> | ||
{formatDateMonthName(data.publishedAt ?? '')} | ||
</div> | ||
</Group> | ||
</Card> | ||
); | ||
}; | ||
|
||
const LatestPost = () => { | ||
const data: Queries.LatestPostQuery = useStaticQuery(graphql` | ||
query LatestPost { | ||
allStrapiArticle(sort: { publishedAt: DESC }) { | ||
nodes { | ||
id | ||
title | ||
slug | ||
publishedAt | ||
tags { | ||
name | ||
color | ||
} | ||
cover { | ||
formats { | ||
medium { | ||
url | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`); | ||
|
||
const latestPost = data.allStrapiArticle.nodes[0]; | ||
|
||
const handleReadPost = useCallback(() => { | ||
navigate(`/blog/${latestPost.slug}`); | ||
}, [latestPost.slug]); | ||
|
||
return ( | ||
<> | ||
<PaneContainer className="!bg-transparent flex flex-col !items-center !justify-center !w-3/4 !h-4/5 !border-none"> | ||
<div className="self-start my-8 text-xl uppercase"> | ||
# Latest Post <span className="animate-ping">█</span> | ||
</div> | ||
<LatestPostCard | ||
key={latestPost.id} | ||
data={latestPost} | ||
onClick={handleReadPost} | ||
/> | ||
</PaneContainer> | ||
<Placeholder /> | ||
</> | ||
); | ||
}; | ||
|
||
export default LatestPost; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import { Badge, Card, Group, Indicator, Text } from '@mantine/core'; | ||
import { IconGrain } from '@tabler/icons-react'; | ||
import { graphql, useStaticQuery } from 'gatsby'; | ||
import { GatsbyImage, IGatsbyImageData } from 'gatsby-plugin-image'; | ||
import PaneContainer from './pane-container'; | ||
import Placeholder from './placeholder'; | ||
|
||
interface Project { | ||
name: string; | ||
languages: string[]; | ||
description: string; | ||
// eslint-disable-next-line react/no-unused-prop-types | ||
isNew?: boolean; | ||
url: string; | ||
image?: IGatsbyImageData; | ||
} | ||
|
||
const projectData: Project[] = [ | ||
{ | ||
name: 'myaPoll', | ||
languages: ['Typescript', 'NextJS'], | ||
description: | ||
'Youtube live stream poll app powered by Next & official data APIv3.', | ||
isNew: true, | ||
url: 'https://github.com/sawaYch/myaPoll', | ||
}, | ||
{ | ||
name: 'next-youtube-livechat', | ||
languages: ['Typescript', 'NextJS', 'React Hook Library'], | ||
description: | ||
'Fetch YouTube live chat without official API using NextJS. Package available on NPM registry.', | ||
isNew: true, | ||
url: 'https://github.com/sawaYch/next-youtube-livechat', | ||
}, | ||
{ | ||
name: 'sawaYch.github.io', | ||
languages: ['Typescript', 'Gatsby'], | ||
description: "Nice to meet you.\nCodebase of Sawa's personal site.", | ||
url: 'https://github.com/sawaYch/sawaYch.github.io', | ||
}, | ||
{ | ||
name: 'mya88', | ||
languages: ['Typescript', 'NextJS'], | ||
description: | ||
'Youtube live stream chat message viewer powered by Next & official data APIv3.', | ||
url: 'https://github.com/sawaYch/mya88', | ||
}, | ||
]; | ||
|
||
const ProjectCard = ({ name, languages, description, url, image }: Project) => ( | ||
<a href={url} target="_blank" rel="noopener noreferrer"> | ||
<Card | ||
key={name} | ||
withBorder | ||
shadow="sm" | ||
radius="md" | ||
className="bg-[#1b2735]/75 backdrop-md h-fit cursor-pointer" | ||
> | ||
<Card.Section withBorder inheritPadding py="xs"> | ||
<Group justify="space-between"> | ||
<Text fw={500}>{name}</Text> | ||
<IconGrain size="1rem" color="gray" /> | ||
</Group> | ||
</Card.Section> | ||
{image && ( | ||
<Card.Section> | ||
<GatsbyImage | ||
className="pointer-events-none select-none" | ||
image={image} | ||
alt={`${name}-image`} | ||
/> | ||
</Card.Section> | ||
)} | ||
<pre className="mt-2 text-sm text-gray-400 whitespace-pre-wrap"> | ||
{description} | ||
</pre> | ||
<Card.Section mt="xl" p="sm"> | ||
<div className="flex flex-wrap gap-1"> | ||
{languages.map((lang) => ( | ||
<Badge key={`${name}-${lang}`} color="indigo"> | ||
{lang} | ||
</Badge> | ||
))} | ||
</div> | ||
</Card.Section> | ||
</Card> | ||
</a> | ||
); | ||
|
||
const ProjectCardWrapper = ({ isNew, ...props }: Project) => | ||
isNew ? ( | ||
<Indicator inline label="✨Latest✨" size={20}> | ||
<ProjectCard key={props.name} {...props} /> | ||
</Indicator> | ||
) : ( | ||
<ProjectCard key={props.name} {...props} /> | ||
); | ||
|
||
const PinnedProject = () => { | ||
const data: Queries.ProjectImageFilesQuery = useStaticQuery(graphql` | ||
query ProjectImageFiles { | ||
allFile( | ||
filter: { | ||
extension: { regex: "/(png)/" } | ||
relativeDirectory: { eq: "projects" } | ||
} | ||
) { | ||
edges { | ||
node { | ||
id | ||
name | ||
childImageSharp { | ||
gatsbyImageData( | ||
width: 720 | ||
placeholder: BLURRED | ||
formats: [AUTO, WEBP] | ||
) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
`); | ||
return ( | ||
<> | ||
<PaneContainer className="!bg-transparent !border-0 flex flex-col !items-center !justify-center !w-3/4 !h-1/2"> | ||
<div className="self-start my-8 text-xl uppercase"> | ||
# Recent Projects <span className="animate-ping">█</span> | ||
</div> | ||
<div className="grid w-full grid-cols-1 gap-x-24 gap-y-24 sm:grid-cols-2"> | ||
{projectData.map((d) => { | ||
const imageNode = data.allFile.edges.find((queryData) => | ||
queryData.node.name.toLowerCase().includes(d.name.toLowerCase()) | ||
); | ||
return ( | ||
<ProjectCardWrapper | ||
key={d.name} | ||
image={imageNode?.node?.childImageSharp?.gatsbyImageData} | ||
{...d} | ||
/> | ||
); | ||
})} | ||
</div> | ||
</PaneContainer> | ||
<Placeholder /> | ||
</> | ||
); | ||
}; | ||
|
||
export default PinnedProject; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.