Skip to content

Commit

Permalink
Adds co-author details below the post subtitle for hashnode theme
Browse files Browse the repository at this point in the history
  • Loading branch information
Harsh062 committed Dec 19, 2023
1 parent f03208e commit af9db15
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Button from './hn-button';
import { CloseSVG } from './icons/svgs';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import CustomScrollArea from './scroll-area';
import { DEFAULT_AVATAR } from '../utils/const/images';
import { ResizableImage } from './resizable-image';
import { useAppContext } from './contexts/appContext';
import { PostFullFragment } from '../generated/graphql';

type CoAuthorsModalProps = {
closeModal: () => void;
};

const AuthorCard = ({ author }: { author: PostFullFragment['author']; }) => {

return (
<div className="flex flex-row items-center justify-between" key={author.id.toString()}>
<div
className="flex w-full flex-wrap items-center justify-between overflow-hidden px-0 py-2.5"
>
<div className="flex flex-wrap items-center overflow-hidden">
<a href={`https://hashnode.com/@${author.username}`} title={author.name} className="mr-2 w-8">
<ResizableImage
src={author.profilePicture || DEFAULT_AVATAR}
resize={{ w: 200, h: 200, c: 'face' }}
className="mr-3 h-8 w-8 rounded-full"
/>
</a>
<div className="flex flex-row items-center text-clip">
<a
href={`https://hashnode.com/@${author.username}`}
title={author.name}
className="truncate font-sans text-sm font-medium text-slate-700 dark:text-slate-200"
>
{author.name}
</a>
</div>
</div>
</div>
</div>
);
};

export default function CoAuthorsModal({ closeModal }: CoAuthorsModalProps) {
const { post } = useAppContext();
const authors = [post?.author, ...(post?.coAuthors || [])];

return (
<DialogPrimitive.Root open>
<DialogPrimitive.Portal>
<DialogPrimitive.Overlay
onClick={closeModal}
className="fixed inset-0 z-50 bg-slate-900 opacity-50 transition-opacity duration-300 ease-out dark:bg-slate-600"
/>
<DialogPrimitive.Content
onEscapeKeyDown={closeModal}
className="fixed bottom-0 left-0 right-0 z-50 flex w-full max-w-[1200px] flex-col items-center overflow-hidden rounded-b-none rounded-t-lg border-slate-200 bg-white text-slate-700 dark:border-slate-800 dark:bg-slate-900 dark:text-slate-50 md:bottom-50 md:left-50 md:w-96 md:-translate-x-1/2 md:translate-y-1/2 md:rounded-xl lg:w-108 xl:w-116"
>
<DialogPrimitive.DialogTitle className="w-full px-6 py-4 text-lg font-semibold">
Authors in this article
</DialogPrimitive.DialogTitle>
<DialogPrimitive.Close className="absolute right-2 top-4 text-slate-900 dark:text-slate-50" asChild>
<Button
className="p-1 focus:outline-none focus-visible:ring focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-offset-slate-800"
onClick={closeModal}
aria-label="Close sign in modal"
variant="transparent"
>
<CloseSVG className="h-5 w-5 fill-current" />
</Button>
</DialogPrimitive.Close>
<CustomScrollArea>
<div className="px-6 pb-8">
{authors.map((author) => {
if (!author) {
return null;
}
return <AuthorCard author={author} key={author.id.toString()} />;
})}
</div>
</CustomScrollArea>
</DialogPrimitive.Content>
</DialogPrimitive.Portal>
</DialogPrimitive.Root>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { resizeImage } from '@starter-kit/utils/image';
import handleMathJax from '@starter-kit/utils/handle-math-jax';
import { PostFullFragment } from '../generated/graphql';
import CustomImage from './custom-image';
import CoAuthorsModal from './co-authors-modal';
import { blurImageDimensions } from '../utils/const/images';
import { getBlurHash, imageReplacer } from '../utils/image';
import ProfileImage from './profile-image';
Expand Down Expand Up @@ -84,6 +85,13 @@ export const PostHeader = ({ post, morePosts }: Props) => {
handleMathJax(true);
}, 500);
}
const [isCoAuthorModalVisible, setIsCoAuthorModalVisible] = useState(false);
const closeCoAuthorModal = () => {
setIsCoAuthorModalVisible(false);
};
const openCoAuthorModal = () => {
setIsCoAuthorModalVisible(true);
};
useEffect(() => {
if (screen.width <= 425) {
setMobMount(true);
Expand All @@ -101,7 +109,7 @@ export const PostHeader = ({ post, morePosts }: Props) => {
setCanLoadEmbeds(true);
})();
}, []);

const authorsArray = [post.author, ...(post.coAuthors || [])];
return (
<Fragment>
<article>
Expand Down Expand Up @@ -157,15 +165,53 @@ export const PostHeader = ({ post, morePosts }: Props) => {

<div className="relative z-20 mb-8 flex flex-row flex-wrap items-center justify-center px-4 md:-mt-7 md:mb-14 md:text-lg last:md:mb-10">
<div className="mb-5 flex w-full flex-row items-center justify-center md:mb-0 md:w-auto md:justify-start">
<div className="mr-2 h-10 w-10 overflow-hidden rounded-full bg-slate-200 dark:bg-white/20 md:mr-3 md:h-12 md:w-12">
<ProfileImage user={post.author} width="200" height="200" hoverDisabled={true} />
</div>
<a
{authorsArray.map((coAuthor, index) => (
<div
key={coAuthor.id?.toString()}
style={{ zIndex: index + 1 }}
className={twJoin(
'overflow-hidden rounded-full bg-slate-200 dark:bg-white/20 md:mr-3',
index > 0 ? 'hidden md:block' : '',
authorsArray.length === 1
? 'h-10 w-10 md:h-12 md:w-12'
: 'h-8 w-8 border-2 border-slate-100 dark:border-slate-800 md:h-9 md:w-9 [&:not(:first-of-type)]:-ml-3 md:[&:not(:first-of-type)]:-ml-6 ',
)}
>
<ProfileImage user={coAuthor} width="200" height="200" hoverDisabled={true} />
</div>
))}
{post.coAuthors && post.coAuthors.length > 0 && (
<button
onClick={openCoAuthorModal}
style={{ zIndex: post.coAuthors?.length }}
className="relative -ml-3 flex h-8 w-8 items-center justify-center overflow-hidden rounded-full border-1-1/2 border-slate-100 bg-slate-100 px-1 group-hover:border-slate-200 dark:border-slate-800 dark:bg-slate-600 dark:text-white group-hover:dark:border-slate-700 md:hidden"
>
<p className="truncate text-xs font-normal">+{post.coAuthors.length}</p>
</button>
)}
{!post.coAuthors?.length && (
<a
href={`https://hashnode.com/@${post.author.username}`}
className="font-medium text-slate-900 dark:text-white"
>
className="ml-2 font-semibold text-slate-600 dark:text-white md:ml-0"
>
<span>{post.author.name}</span>
</a>
</a>
)}
{post?.coAuthors?.length && post.coAuthors.length > 0 && (
<button
onClick={openCoAuthorModal}
className="ml-2 text-left font-semibold text-slate-600 hover:underline dark:text-white"
>
<span>{post.author.name}</span>
{post.coAuthors && (
<span className="font-normal">
{' '}
<br className="block sm:hidden" />
with {post.coAuthors.length} co-author{post.coAuthors.length === 1 ? '' : 's'}
</span>
)}
</button>
)}
</div>
<div className="mb-5 flex w-full flex-row items-center justify-center md:mb-0 md:w-auto md:justify-start">
<span className="mx-3 hidden font-bold text-slate-500 md:block">&middot;</span>
Expand Down Expand Up @@ -280,7 +326,10 @@ export const PostHeader = ({ post, morePosts }: Props) => {
selectedFilter={selectedFilter}
post={post}
/>
)}
)}
{isCoAuthorModalVisible && (
<CoAuthorsModal closeModal={closeCoAuthorModal} />
)}
</Fragment>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import { resizeImage } from '@starter-kit/utils/image';

import { DEFAULT_AVATAR } from '../utils/const/images';
import { twMerge } from 'tailwind-merge';

/**
* Progressive Image Component which loads low resolution version image before loading original
* @param {string} options.src Image source
* @param {string} options.alt Image alt text
* @param {string} options.className Classname string
* @param {...[type]} options.restOfProps Rest of the props passed to the child
*/
class ProgressiveImage extends React.Component<{
resize: any;
src: string;
alt: string;
className: string;
css: string;
}> {
image: HTMLImageElement | null = null;

componentDidMount() {
if (!(window as any).lazySizes && this.image) {
this.image.setAttribute('src', this.image.getAttribute('data-src') || '');
}
}

// TODO: Improve type
replaceBadImage = (e: any) => {
// eslint-disable-next-line react/destructuring-assignment
if (this.props.resize && this.props.resize.c !== 'face') {
return;
}
e.target.onerror = null;
e.target.src = DEFAULT_AVATAR;
};

render() {
const { src, alt, className, resize = {}, ...restOfProps } = this.props;

if (!src || src.trim().length === 0) return null;

const resizedImage = resizeImage(src, resize);

return (
<img
data-sizes="auto"
loading="lazy"
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
// eslint-disable-next-line no-return-assign
ref={(c) => (this.image = c || null)}
data-src={resizedImage}
width={resize.w}
height={resize.h}
onError={this.replaceBadImage}
alt={alt}
className={twMerge('lazyload block w-full', className)}
{...restOfProps}
/>
);
}
}

export default ProgressiveImage;

export { ProgressiveImage };
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ProgressiveImage } from './progressive-image';

function ResizableImage(props) {
const { src, alt, resize, className, ...restOfTheProps } = props;

return (
<ProgressiveImage alt={alt} src={src || props.default} resize={resize} className={className} {...restOfTheProps} />
);
}

export default ResizableImage;
export { ResizableImage };

0 comments on commit af9db15

Please sign in to comment.