Skip to content

Commit

Permalink
styles(blog): UI improvements (#468)
Browse files Browse the repository at this point in the history
* fix: truncate featured article title and excerpt

* styles: allow more excerpt lines in larger viewports

* styles: gradient fill to category label

* feat: change the blog date to en-US format

* feat: create a Meta component for blog posts

* styles: hover effect on blog article cards

* styles: update Breadcrumbs style

* styles: move cover image and excerpt

* styles: share buttons

* style: table of contents

* styles: post meta data

* styles: change breadcrumbs color
  • Loading branch information
DiogoSoaress authored Dec 16, 2024
1 parent 471dab5 commit c9f2ba7
Show file tree
Hide file tree
Showing 16 changed files with 283 additions and 249 deletions.
32 changes: 11 additions & 21 deletions src/components/Blog/Authors/index.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
import { Avatar, AvatarGroup, Typography } from '@mui/material'
import css from '../styles.module.css'
import { Avatar, AvatarGroup } from '@mui/material'
import type { Entry } from 'contentful'
import type { TypeAuthorSkeleton } from '@/contentful/types'
import { isAsset } from '@/lib/typeGuards'
import { formatAuthorsList } from '@/components/Blog/utils/formatAuthorsList'

export type AuthorsProps = Entry<TypeAuthorSkeleton, undefined, string>[]

const Authors = ({ authors }: { authors: AuthorsProps }) => {
return (
<div className={css.authors}>
<AvatarGroup className={css.avatarGroup} max={3}>
{authors.map((author) => {
const { name, picture } = author.fields
const Authors = ({ authors }: { authors: AuthorsProps }) => (
<AvatarGroup max={3}>
{authors.map((author) => {
const { name, picture } = author.fields

return isAsset(picture) && picture.fields.file?.url ? (
<Avatar key={name} src={picture.fields.file.url} alt={picture.fields.title} />
) : undefined
})}
</AvatarGroup>

<Typography variant="caption" color="text.primary">
{formatAuthorsList(authors)}
</Typography>
</div>
)
}
return isAsset(picture) && picture.fields.file?.url ? (
<Avatar key={name} src={picture.fields.file.url} alt={picture.fields.title} />
) : undefined
})}
</AvatarGroup>
)

export default Authors
29 changes: 12 additions & 17 deletions src/components/Blog/BreadcrumbsNav/index.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import type { ReactNode } from 'react'
import type { UrlObject } from 'url'
import { Breadcrumbs, Typography } from '@mui/material'
import Link from 'next/link'
import css from './styles.module.css'
import { type UrlObject } from 'url'
import CategoryIcon from '@/public/images/Blog/category-icon.svg'
import { type ReactNode } from 'react'
import { AppRoutes } from '@/config/routes'
import AngleIcon from '@/public/images/angle-icon.svg'
import css from './styles.module.css'

const TYPOGRAPHY_VARIANT = 'caption'
const TYPOGRAPHY_COLOR = 'text.primary'
const TYPOGRAPHY_VARIANT = 'body2'

type BreadcrumbsType = {
category: string
Expand All @@ -16,7 +15,7 @@ type BreadcrumbsType = {

const createBreadcrumb = (key: string, text: ReactNode, linkProps: string | UrlObject) => (
<Link key={key} href={linkProps}>
<Typography variant={TYPOGRAPHY_VARIANT} color={TYPOGRAPHY_COLOR}>
<Typography variant={TYPOGRAPHY_VARIANT} color="primary.light">
{text}
</Typography>
</Link>
Expand All @@ -25,21 +24,17 @@ const createBreadcrumb = (key: string, text: ReactNode, linkProps: string | UrlO
const BreadcrumbsNav = ({ category, title }: BreadcrumbsType) => {
const breadcrumbs = [
createBreadcrumb('1', 'Blog', { pathname: AppRoutes.blog.index }),
createBreadcrumb(
'2',
<div className={css.category}>
<CategoryIcon />
{category}
</div>,
{ pathname: AppRoutes.blog.index, query: { category } },
),
<Typography key="3" variant={TYPOGRAPHY_VARIANT} color={TYPOGRAPHY_COLOR}>
createBreadcrumb('2', category, {
pathname: AppRoutes.blog.index,
query: { category },
}),
<Typography key="3" variant={TYPOGRAPHY_VARIANT} className={css.title}>
{title}
</Typography>,
]

return (
<Breadcrumbs separator=">" className={css.breadcrumbs}>
<Breadcrumbs separator={<AngleIcon />} className={css.breadcrumbs}>
{breadcrumbs}
</Breadcrumbs>
)
Expand Down
27 changes: 15 additions & 12 deletions src/components/Blog/BreadcrumbsNav/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,23 +1,26 @@
.breadcrumbs,
.breadcrumbs li {
display: flex;
align-items: center;
}

.breadcrumbs a {
color: var(--mui-palette-text-primary);
margin-bottom: 24px;
transition-duration: var(--transition-duration);
}

.breadcrumbs a:hover {
color: var(--mui-palette-primary-light);
text-decoration: underline;
}

.breadcrumbs :global .MuiBreadcrumbs-separator {
margin: 0 4px;
width: 8px;
height: 8px;
}

.breadcrumbs :global .MuiTypography-root {
line-height: inherit;
}

.category {
display: flex;
gap: 4px;
align-items: center;
.title {
width: 290px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: var(--mui-palette-primary-light);
}
15 changes: 4 additions & 11 deletions src/components/Blog/Card/index.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
import Image from 'next/image'
import Link from 'next/link'
import { Box, Typography } from '@mui/material'
import css from './styles.module.css'
import { calculateReadingTimeInMin } from '@/components/Blog/utils/calculateReadingTime'
import Tags from '@/components/Blog/Tags'
import CategoryIcon from '@/public/images/Blog/category-icon.svg'
import { isAsset } from '@/lib/typeGuards'
import type { BlogPostEntry } from '@/config/types'
import Meta from '@/components/Blog/Meta'
import { AppRoutes } from '@/config/routes'
import css from './styles.module.css'

const Card = (props: BlogPostEntry) => {
const { slug, title, content, coverImage, tags, category } = props.fields
const { slug, title, coverImage, tags } = props.fields

return (
<div className={css.postCard}>
Expand All @@ -27,13 +26,7 @@ const Card = (props: BlogPostEntry) => {
) : undefined}

<div className={css.cardBody}>
<div className={css.meta}>
<CategoryIcon />
<Typography variant="caption" color="text.primary">
{category}
</Typography>
<Typography variant="caption">{calculateReadingTimeInMin(content)}</Typography>
</div>
<Meta post={props} />

<div className={css.title}>
<Typography variant="h5">{title}</Typography>
Expand Down
33 changes: 23 additions & 10 deletions src/components/Blog/Card/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,26 @@
display: flex;
flex-direction: column;
border-radius: 8px;
border: 1px solid var(--mui-palette-border-main);
overflow: hidden;
outline: 1px solid var(--mui-palette-border-main);
transition-duration: var(--transition-duration);
}

.postCard:hover {
border: 1px solid var(--mui-palette-primary-main);
.postCard::before {
content: '';
position: absolute;
border-radius: 11px;
top: -3px;
left: -3px;
right: -3px;
bottom: -3px;
z-index: -1;
background: linear-gradient(to bottom left, #12ff80, #5fddff);
opacity: 0;
}

.postCard:hover::before {
opacity: 1;
box-shadow: 10px 10px 25px 0 rgba(18, 255, 128, 0.2);
}

.link {
Expand All @@ -27,25 +40,25 @@
object-position: center left;
border-bottom: 1px solid var(--mui-palette-border-main);
transition-duration: var(--transition-duration);
border-top-left-radius: 8px;
border-top-right-radius: 8px;
}

.cardBody {
padding: 24px 16px;
display: flex;
flex-direction: column;
flex-grow: 1;
}

.meta {
display: flex;
gap: 8px;
align-items: center;
background-color: var(--mui-palette-background-default);
border-bottom-left-radius: 8px;
border-bottom-right-radius: 8px;
}

.title {
margin-top: 16px;
overflow: hidden;
display: -webkit-box;
line-clamp: 3;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
40 changes: 24 additions & 16 deletions src/components/Blog/ContentsTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useMemo } from 'react'
import { scrollToElement } from '@/lib/scrollSmooth'
import { Typography } from '@mui/material'
import Link from 'next/link'
import css from '../styles.module.css'
import css from './styles.module.css'

const ContentsTable = ({ content }: { content: ContentfulDocument }) => {
const handleContentTableClick = (e: React.MouseEvent<HTMLAnchorElement>, target: string) => {
Expand All @@ -25,22 +25,30 @@ const ContentsTable = ({ content }: { content: ContentfulDocument }) => {
[content],
)

if (!headings.length) return null

return (
<ul className={css.contentsTable}>
{headings.map((heading) => {
const headingKey = kebabCase(heading.id)

return (
<li key={headingKey}>
<Typography>
<Link onClick={(e) => handleContentTableClick(e, headingKey)} href={`#${headingKey}`}>
{heading.text}
</Link>
</Typography>
</li>
)
})}
</ul>
<div className={css.contentsTable}>
<Typography variant="caption" color="text.primary">
Table of contents
</Typography>

<ol>
{headings.map((heading) => {
const headingKey = kebabCase(heading.id)

return (
<li key={headingKey}>
<Typography>
<Link onClick={(e) => handleContentTableClick(e, headingKey)} href={`#${headingKey}`}>
{heading.text}
</Link>
</Typography>
</li>
)
})}
</ol>
</div>
)
}

Expand Down
30 changes: 30 additions & 0 deletions src/components/Blog/ContentsTable/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
.contentsTable {
margin: 0;
border: 1px solid var(--mui-palette-border-light);
border-radius: 8px;
padding: 24px;
margin-bottom: 24px;
}

.contentsTable ol {
list-style-type: none;
padding-left: 0px;
}

.contentsTable li {
counter-increment: step-counter;
display: flex;
align-items: flex-start;
gap: 8px;
}

.contentsTable li::before {
content: counter(step-counter) ' ';
white-space: pre;
color: var(--mui-palette-primary-light);
}

.contentsTable a:hover {
text-decoration: underline;
color: var(--mui-palette-primary-main);
}
20 changes: 3 additions & 17 deletions src/components/Blog/FeaturedPost/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,14 @@ import Image from 'next/image'
import Tags from '@/components/Blog/Tags'
import { Box, Grid, Link, Typography } from '@mui/material'
import css from './styles.module.css'
import { formatBlogDate } from '@/components/Blog/utils/formatBlogDate'
import { calculateReadingTimeInMin } from '@/components/Blog/utils/calculateReadingTime'
import type { BlogPostEntry } from '@/config/types'
import { isAsset } from '@/lib/typeGuards'
import CategoryIcon from '@/public/images/Blog/category-icon.svg'
import { AppRoutes } from '@/config/routes'
import { containsTag, PRESS_RELEASE_TAG } from '@/lib/containsTag'
import Meta from '@/components/Blog/Meta'

const FeaturedPost = ({ post }: { post: BlogPostEntry }) => {
const { slug, coverImage, category, date, title, excerpt, tags, content } = post.fields
const { slug, coverImage, title, excerpt, tags } = post.fields

const isPressRelease = containsTag(tags, PRESS_RELEASE_TAG)

Expand All @@ -38,19 +36,7 @@ const FeaturedPost = ({ post }: { post: BlogPostEntry }) => {
</Grid>

<Grid item lg={5} className={css.body}>
<div className={css.meta}>
<div className={css.metaStart}>
<CategoryIcon />

<Typography variant="caption" color="text.primary">
{category}
</Typography>

<Typography variant="caption">{calculateReadingTimeInMin(content)}</Typography>
</div>

<Typography variant="caption">{formatBlogDate(date)}</Typography>
</div>
<Meta post={post} />

<Typography variant="h3" className={css.title}>
<Link href={`${AppRoutes.blog.index}/${slug}`}>{title}</Link>
Expand Down
14 changes: 0 additions & 14 deletions src/components/Blog/FeaturedPost/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@
gap: 16px;
}

.meta {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}

.metaStart {
display: flex;
flex-wrap: nowrap;
align-items: center;
gap: 8px;
}

.title,
.excerpt {
overflow: hidden;
Expand Down
Loading

0 comments on commit c9f2ba7

Please sign in to comment.