Skip to content

Commit

Permalink
feat: migrate newsroom components (#2734)
Browse files Browse the repository at this point in the history
Co-authored-by: Akshat Nema <[email protected]>%0ACo-authored-by: akshatnema <[email protected]>
  • Loading branch information
anshgoyalevil and akshatnema authored Mar 9, 2024
1 parent e4ece93 commit 4e91da6
Show file tree
Hide file tree
Showing 15 changed files with 771 additions and 11 deletions.
6 changes: 0 additions & 6 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -183,12 +183,6 @@
}
],
"max-depth": "error",
"max-len": [
"error",
{
"code": 120
}
],
"max-lines": [
"error",
{
Expand Down
44 changes: 44 additions & 0 deletions components/AuthorAvatars.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';

interface Author {
name: string;
photo: string;
link?: string;
}

interface AuthorAvatarsProps {
authors: Author[];
}

/**
* @description This component takes an array of authors and renders their avatars.
* @param {AuthorAvatarsProps} props - The component props.
* @param {Author[]} props.authors - The authors to render avatars for.
*/
export default function AuthorAvatars({ authors = [] }: AuthorAvatarsProps) {
return (
<>
{authors.map((author, index) => {
const avatar = (
<img
key={index}
title={author.name}
className={`${index > 0 ? `left- absolute${index * 7} top-0` : `mr- relative${(authors.length - 1) * 7}`} z-${(authors.length - 1 - index) * 10} size-10 rounded-full border-2 border-white object-cover hover:z-50`}
src={author.photo}
loading='lazy'
data-testid='AuthorAvatars-img'
alt={author.name} // Added alt attribute here
/>
);

return author.link ? (
<a href={author.link} key={index} data-testid='AuthorAvatars-link'>
{avatar}
</a>
) : (
<React.Fragment key={index}>{avatar}</React.Fragment>
);
})}
</>
);
}
127 changes: 127 additions & 0 deletions components/navigation/BlogPostItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import moment from 'moment';
import Link from 'next/link';
import type { Ref } from 'react';
import { forwardRef } from 'react';
import TextTruncate from 'react-text-truncate';

import { BlogPostType } from '@/types/components/navigation/BlogPostType';
import type { IBlogPost } from '@/types/post';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import AuthorAvatars from '../AuthorAvatars';
import Heading from '../typography/Heading';
import Paragraph from '../typography/Paragraph';

interface BlogPostItemProps {
post: IBlogPost;
className?: string;
id?: string;
}

/**
* @description Functional component representing a single blog post item.
* @param {Object} props - Props for the BlogPostItem component.
* @param {IBlogPost} props.post - The blog post data.
* @param {string} [props.className=''] - Additional CSS classes for styling.
* @param {string} [props.id=''] - The HTML id attribute for the component.
* @param {Ref<HTMLLIElement>} ref - Reference object for the component.
*/
export default forwardRef(function BlogPostItem({ post, className = '', id = '' }: BlogPostItemProps,
ref: Ref<HTMLLIElement>) {
let typeColors: [string, string] = ['bg-indigo-100', 'text-indigo-800'];

switch (post.type.toLowerCase()) {
case BlogPostType.Video:
typeColors = ['bg-pink-100', 'text-pink-800'];
break;
case BlogPostType.Marketing:
typeColors = ['bg-orange-100', 'text-orange-800'];
break;
case BlogPostType.Strategy:
typeColors = ['bg-green-100', 'text-green-800'];
break;
case BlogPostType.Communication:
typeColors = ['bg-teal-100', 'text-teal-800'];
break;
default:
}

return (
<li className={`rounded-lg ${className}`} ref={ref} id={id}>
<article className='h-full rounded-lg'>
<Link legacyBehavior href={post.slug} passHref>
<a
className={
'flex h-full cursor-pointer flex-col divide-y divide-gray-200 overflow-hidden rounded-lg border border-gray-200 shadow-md transition-all duration-300 ease-in-out hover:shadow-lg'
}
data-testid='BlogPostItem-Link'
>
<img
className='h-48 w-full object-cover'
src={post.cover}
alt=''
loading='lazy'
data-testid='BlogPostItem-Img'
/>
<div className='flex flex-1 flex-col justify-between bg-white p-6'>
<div className='flex-1'>
<Paragraph typeStyle={ParagraphTypeStyle.sm} textColor='text-indigo-500'>
<span
className={`inline-flex items-center rounded-full px-3 py-0.5 ${typeColors[0]} ${typeColors[1]}`}
>
{post.type}
</span>
</Paragraph>
<Link legacyBehavior href={post.slug}>
<a className='block'>
<Heading level={HeadingLevel.h5} typeStyle={HeadingTypeStyle.smSemibold} className='mt-2'>
{post.title}
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='mt-3'>
<TextTruncate element='span' line={4} text={post.excerpt} />
</Paragraph>
</a>
</Link>
</div>
<div className='mt-6 flex items-center'>
<div className='relative shrink-0'>
<AuthorAvatars authors={post.authors} />
</div>
<div className='ml-3'>
<Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.xsSemibold} textColor='text-gray-900'>
<span className='hover:underline'>
{post.authors
.map((author, index) => author.link ? (
<a
key={index}
data-alt={author.name}
href={author.link}
onClick={(e) => {
e.stopPropagation();
}}
target='_blank'
rel='noreferrer'
>
{author.name}
</a>
) : (
author.name
))
.reduce((prev, curr) => [prev, ' & ', curr].join(''))}
</span>
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='flex'>
<time dateTime={post.date}>{moment(post.date).format('MMMM D, YYYY')}</time>
<span className='mx-1'>&middot;</span>
<span>{post.readingTime} min read</span>
</Paragraph>
</div>
</div>
</div>
</a>
</Link>
</article>
</li>
);
});
120 changes: 120 additions & 0 deletions components/newsroom/FeaturedBlogPost.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import moment from 'moment';
import Link from 'next/link';
import TextTruncate from 'react-text-truncate';

import { BlogPostType } from '@/types/components/navigation/BlogPostType';
import type { IBlogPost } from '@/types/post';
import { HeadingLevel, HeadingTypeStyle } from '@/types/typography/Heading';
import { ParagraphTypeStyle } from '@/types/typography/Paragraph';

import AuthorAvatars from '../AuthorAvatars';
import Heading from '../typography/Heading';
import Paragraph from '../typography/Paragraph';

interface FeaturedBlogPostProps {
post: IBlogPost;
className?: string;
}

/**
* @description Renders a featured blog post with the provided data.
* @param {FeaturedBlogPostProps} props - The component props.
* @param {string} [props.className=''] - Additional CSS classes for styling.
*/
export default function FeaturedBlogPost({ post, className = '' }: FeaturedBlogPostProps) {
let typeColors = ['bg-indigo-100', 'text-indigo-800'];

switch (post.type.toLowerCase()) {
case BlogPostType.Video:
typeColors = ['bg-pink-100', 'text-pink-800'];
break;
case BlogPostType.Marketing:
typeColors = ['bg-orange-100', 'text-orange-800'];
break;
case BlogPostType.Strategy:
typeColors = ['bg-green-100', 'text-green-800'];
break;
case BlogPostType.Communication:
typeColors = ['bg-teal-100', 'text-teal-800'];
break;
default:
}

return (
<div className={`rounded-lg ${className}`}>
<article className='h-full rounded-lg'>
<Link legacyBehavior href={post.slug} passHref>
<a
className={'flex h-full cursor-pointer flex-col divide-y divide-gray-200 overflow-hidden rounded-lg border border-gray-200 shadow-md transition-all duration-300 ease-in-out hover:shadow-lg md:max-w-164 md:flex-row'}
data-testid='FeaturedBlogPostItem-Link'
>
<img
className='w-full object-cover md:w-56'
src={post.cover}
alt=''
data-testid='FeaturedBlogPostItem-Img'
/>
<div className='flex flex-1 flex-col justify-between border-none bg-white p-6 text-left'>
<div className='flex-1'>
<Paragraph typeStyle={ParagraphTypeStyle.sm} textColor='text-indigo-500'>
<span
className={`inline-flex items-center rounded-full px-3 py-0.5 ${typeColors[0]} ${typeColors[1]}`}
data-testid='FeaturedBlogPost-type'
>
{post.type}
</span>
</Paragraph>
<Link legacyBehavior href={post.slug}>
<a className='block' data-testid='FeaturedBlog-title'>
<Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.smSemibold} className='mt-2'>
{post.title}
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='mt-3'>
<TextTruncate element='span' line={2} text={post.excerpt} />
</Paragraph>
</a>
</Link>
</div>
<div className='mt-6 flex items-center'>
<div className='relative shrink-0' data-testid='FeaturedBlog-Authorimg'>
<AuthorAvatars authors={post.authors} />
</div>
<div className='ml-3'>
<Heading level={HeadingLevel.h3} typeStyle={HeadingTypeStyle.xsSemibold} textColor='text-gray-900'>
<span className='hover:underline' data-testid='FeaturedBlogPost-AuthorName'>
{post.authors
.map((author, index) => author.link ? (
<a
key={index}
data-alt={author.name}
href={author.link}
onClick={(e) => {
e.stopPropagation();
}}
target='_blank'
rel='noreferrer'
>
{author.name}
</a>
) : (
author.name
))
.reduce((prev, curr) => [prev, ' & ', curr].join(''))}
</span>
</Heading>
<Paragraph typeStyle={ParagraphTypeStyle.sm} className='flex'>
<time dateTime={post.date} data-testid='FeaturedBlogPost-date'>
{moment(post.date).format('MMMM D, YYYY')}
</time>
<span className='mx-1'>&middot;</span>
<span data-testid='FeaturedBlogPost-RT'>{post.readingTime} min read</span>
</Paragraph>
</div>
</div>
</div>
</a>
</Link>
</article>
</div>
);
}
Loading

0 comments on commit 4e91da6

Please sign in to comment.