Skip to content

Commit 1166219

Browse files
authored
Multi authors (#6)
1 parent 880adf7 commit 1166219

21 files changed

+744
-22
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { PostFullFragment } from '../generated/graphql';
2+
import { useAppContext } from './contexts/appContext';
3+
import PostAuthorInfo from './post-author-info';
4+
5+
function AboutAuthor() {
6+
const { post: _post } = useAppContext();
7+
const post = _post as unknown as PostFullFragment;
8+
const { publication, author } = post;
9+
let coAuthors = post.coAuthors || [];
10+
11+
const allAuthors = publication?.isTeam ? [author, ...coAuthors] : [author];
12+
13+
return (
14+
<div className="mx-auto mb-5 mt-10 flex w-full flex-col gap-16 px-5 md:max-w-screen-md">
15+
<div className="flex-1 px-2">
16+
<div className="flex flex-col flex-wrap items-start md:flex-nowrap">
17+
<h3 className="border-b-1-1/2 mb-4 w-full pb-2 text-base font-medium tracking-wider text-slate-500 dark:border-slate-800 dark:text-slate-400 ">
18+
Written by
19+
</h3>
20+
<div className="flex w-full flex-col gap-12">
21+
{allAuthors.map((_author) => {
22+
return <PostAuthorInfo key={_author.id.toString()} author={_author} />;
23+
})}
24+
</div>
25+
</div>
26+
</div>
27+
</div>
28+
);
29+
}
30+
31+
export default AboutAuthor;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import * as DialogPrimitive from '@radix-ui/react-dialog';
2+
import { PostFullFragment } from '../generated/graphql';
3+
import { DEFAULT_AVATAR } from '../utils/const';
4+
import { Button } from './button';
5+
import { useAppContext } from './contexts/appContext';
6+
import CloseSVG from './icons/svgs/CloseSVG';
7+
import { ResizableImage } from './resizable-image';
8+
import CustomScrollArea from './scroll-area';
9+
10+
type CoAuthorsModalProps = {
11+
closeModal: () => void;
12+
};
13+
14+
const AuthorCard = ({ author }: { author: PostFullFragment['author'] }) => {
15+
return (
16+
<div className="flex flex-row items-center justify-between" key={author.id.toString()}>
17+
<div className="flex w-full flex-wrap items-center justify-between overflow-hidden px-0 py-2.5">
18+
<div className="flex flex-wrap items-center overflow-hidden">
19+
<a
20+
href={`https://hashnode.com/@${author.username}`}
21+
title={author.name}
22+
className="mr-2 w-8"
23+
>
24+
<ResizableImage
25+
src={author.profilePicture || DEFAULT_AVATAR}
26+
resize={{ w: 200, h: 200, c: 'face' }}
27+
className="mr-3 h-8 w-8 rounded-full"
28+
/>
29+
</a>
30+
<div className="flex flex-row items-center text-clip">
31+
<a
32+
href={`https://hashnode.com/@${author.username}`}
33+
title={author.name}
34+
className="truncate font-sans text-sm font-medium text-slate-700 dark:text-slate-200"
35+
>
36+
{author.name}
37+
</a>
38+
</div>
39+
</div>
40+
</div>
41+
</div>
42+
);
43+
};
44+
45+
export default function CoAuthorsModal({ closeModal }: CoAuthorsModalProps) {
46+
const { post } = useAppContext();
47+
const authors = [post?.author, ...(post?.coAuthors || [])];
48+
49+
return (
50+
<DialogPrimitive.Root open>
51+
<DialogPrimitive.Portal>
52+
<DialogPrimitive.Overlay
53+
onClick={closeModal}
54+
className="fixed inset-0 z-50 bg-slate-900 opacity-50 transition-opacity duration-300 ease-out dark:bg-slate-600"
55+
/>
56+
<DialogPrimitive.Content
57+
onEscapeKeyDown={closeModal}
58+
className="md:bottom-50 md:left-50 lg:w-108 xl:w-116 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:w-96 md:-translate-x-1/2 md:translate-y-1/2 md:rounded-xl"
59+
>
60+
<DialogPrimitive.DialogTitle className="w-full px-6 py-4 text-lg font-semibold">
61+
Authors in this article
62+
</DialogPrimitive.DialogTitle>
63+
<DialogPrimitive.Close
64+
className="absolute right-2 top-4 text-slate-900 dark:text-slate-50"
65+
asChild
66+
>
67+
<Button
68+
type="outline"
69+
label=""
70+
icon={<CloseSVG className="h-5 w-5 fill-current" />}
71+
className="rounded-xl !border-transparent !px-3 !py-2 hover:bg-neutral-800 dark:text-white"
72+
onClick={closeModal}
73+
/>
74+
</DialogPrimitive.Close>
75+
<CustomScrollArea>
76+
<div className="px-6 pb-8">
77+
{authors.map((author) => {
78+
if (!author) {
79+
return null;
80+
}
81+
return <AuthorCard author={author} key={author.id.toString()} />;
82+
})}
83+
</div>
84+
</CustomScrollArea>
85+
</DialogPrimitive.Content>
86+
</DialogPrimitive.Portal>
87+
</DialogPrimitive.Root>
88+
);
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { ImgHTMLAttributes } from 'react';
2+
3+
import Image, { ImageProps } from 'next/legacy/image';
4+
5+
type Props = {
6+
src: any; // can be string or StaticImport of next/image
7+
alt: string;
8+
originalSrc: string;
9+
} & ImgHTMLAttributes<any> &
10+
ImageProps;
11+
12+
/**
13+
* Conditionally renders native img for gifs and next/image for other types
14+
* @param props
15+
* @returns <img /> or <Image/>
16+
*/
17+
function CustomImage(props: Props) {
18+
const { originalSrc, ...originalRestOfTheProps } = props;
19+
const {
20+
alt = '',
21+
loader,
22+
quality,
23+
priority,
24+
loading,
25+
unoptimized,
26+
objectFit,
27+
objectPosition,
28+
src,
29+
width,
30+
height,
31+
layout,
32+
placeholder,
33+
blurDataURL,
34+
...restOfTheProps
35+
} = originalRestOfTheProps; // Destructured next/image props on purpose, so that unwanted props don't end up in <img />
36+
37+
if (!originalSrc) {
38+
return null;
39+
}
40+
41+
const isGif = originalSrc.substr(-4) === '.gif';
42+
const isHashnodeCDNImage = src.indexOf('cdn.hashnode.com') > -1;
43+
44+
if (isGif || !isHashnodeCDNImage) {
45+
// restOfTheProps will contain all props excluding the next/image props
46+
return <img {...restOfTheProps} alt={alt} src={src || originalSrc} />;
47+
}
48+
49+
// Notes we are passing whole props object here
50+
return <Image {...originalRestOfTheProps} src={src || originalSrc} />;
51+
}
52+
53+
export default CustomImage;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
export default class BookOpenSVG extends React.Component {
4+
render() {
5+
return (
6+
<svg className={this.props.className} viewBox="0 0 576 512">
7+
<path d="M540.9 56.77c-45.95-16.66-90.23-24.09-129.1-24.75-60.7.94-102.7 17.45-123.8 27.72-21.1-10.27-64.1-26.8-123.2-27.74-40-.05-84.4 8.35-129.7 24.77C14.18 64.33 0 84.41 0 106.7v302.9c0 14.66 6.875 28.06 18.89 36.8 11.81 8.531 26.64 10.98 40.73 6.781 118.9-36.34 209.3 19.05 214.3 22.19C277.8 477.6 281.2 480 287.1 480c6.52 0 10.12-2.373 14.07-4.578 10.78-6.688 98.3-57.66 214.3-22.27 14.11 4.25 28.86 1.75 40.75-6.812C569.1 437.6 576 424.2 576 409.6V106.7c0-22.28-14.2-42.35-35.1-49.93zM272 438.1c-24.95-12.03-71.01-29.37-130.5-29.37-27.83 0-58.5 3.812-91.19 13.77-4.406 1.344-9 .594-12.69-2.047C34.02 417.8 32 413.1 32 409.6V106.7c0-8.859 5.562-16.83 13.86-19.83C87.66 71.7 127.9 63.95 164.5 64c51.8.81 89.7 15.26 107.5 23.66V438.1zm272-28.5c0 4.375-2.016 8.234-5.594 10.84-3.766 2.703-8.297 3.422-12.69 2.125C424.1 391.6 341.3 420.4 304 438.3V87.66c17.8-8.4 55.7-22.85 107.4-23.66 35.31-.063 76.34 7.484 118.8 22.88 8.2 3 13.8 10.96 13.8 19.82v302.9z" />
8+
</svg>
9+
);
10+
}
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
3+
export default class CloseSVG extends React.Component {
4+
render() {
5+
return (
6+
<svg className={this.props.className} viewBox="0 0 320 512">
7+
<path d="M193.94 256L296.5 153.44l21.15-21.15c3.12-3.12 3.12-8.19 0-11.31l-22.63-22.63c-3.12-3.12-8.19-3.12-11.31 0L160 222.06 36.29 98.34c-3.12-3.12-8.19-3.12-11.31 0L2.34 120.97c-3.12 3.12-3.12 8.19 0 11.31L126.06 256 2.34 379.71c-3.12 3.12-3.12 8.19 0 11.31l22.63 22.63c3.12 3.12 8.19 3.12 11.31 0L160 289.94 262.56 392.5l21.15 21.15c3.12 3.12 8.19 3.12 11.31 0l22.63-22.63c3.12-3.12 3.12-8.19 0-11.31L193.94 256z" />
8+
</svg>
9+
);
10+
}
11+
}

packages/blog-starter-kit/themes/enterprise/components/icons/svgs/index.js

+4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import ArticleSVG from './ArticleSVG';
2+
import BookOpenSVG from './BookOpenSVG';
23
import ChevronDownSVG from './ChevronDownSVG';
4+
import CloseSVG from './CloseSVG';
35
import ExternalArrowSVG from './ExternalArrowSVG';
46
import GithubSVG from './GithubSVG';
57
import HashnodeSVG from './HashnodeSVG';
@@ -31,9 +33,11 @@ export {
3133
AboutUsNavSVG,
3234
ArticleSVG,
3335
BlogNavSGV,
36+
BookOpenSVG,
3437
CareersSVG,
3538
CaseStudiesSVG,
3639
ChevronDownSVG,
40+
CloseSVG,
3741
CloudNavBarSVG,
3842
CommunitySVG,
3943
ContactSVG,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { twJoin } from 'tailwind-merge';
2+
3+
import { getBlurHash, resizeImage } from '@starter-kit/utils/image';
4+
import CustomImage from './custom-image';
5+
6+
function PostAuthorInfo(props: any) {
7+
const { author } = props;
8+
9+
return (
10+
<div className="flex w-full flex-1 flex-col md:flex-row">
11+
<div className="mb-4 flex w-full flex-1 flex-row md:mb-0 ">
12+
<div className="mr-4 flex flex-row md:mb-0">
13+
<a
14+
href={`https://hashnode.com/@${author.username}`}
15+
className="block h-10 w-10 overflow-hidden rounded-full border dark:border-slate-800 md:h-14 md:w-14"
16+
>
17+
<CustomImage
18+
className="block"
19+
placeholder="blur"
20+
originalSrc={author.profilePicture}
21+
src={resizeImage(author.profilePicture, {
22+
w: 256,
23+
h: 256,
24+
c: 'thumb',
25+
})}
26+
blurDataURL={getBlurHash(
27+
resizeImage(author.profilePicture, {
28+
w: 256,
29+
h: 256,
30+
c: 'thumb',
31+
}),
32+
)}
33+
width={256}
34+
height={256}
35+
alt={author.name}
36+
/>
37+
</a>
38+
</div>
39+
<div
40+
className={twJoin(
41+
'flex flex-1 flex-col justify-center md:justify-start',
42+
!author.bio?.html ? 'md:justify-center' : '',
43+
)}
44+
>
45+
<div className="flex flex-row items-center md:mb-1">
46+
<h1 className="font-sans text-lg font-semibold text-slate-800 dark:text-slate-100">
47+
<a href={`https://hashnode.com/@${author.username}`}>{author.name}</a>
48+
</h1>
49+
</div>
50+
{author.bio?.html && (
51+
<div className="hidden pr-2 md:block">
52+
<div
53+
className="prose dark:prose-dark text-slate-600 dark:text-slate-300"
54+
dangerouslySetInnerHTML={{ __html: author.bio?.html }}
55+
/>
56+
</div>
57+
)}
58+
</div>
59+
</div>
60+
{author.bio?.html && (
61+
<div className="mb-4 block md:hidden">
62+
<div
63+
className="prose dark:prose-dark text-slate-600 "
64+
dangerouslySetInnerHTML={{ __html: author.bio?.html }}
65+
/>
66+
</div>
67+
)}
68+
</div>
69+
);
70+
}
71+
72+
export default PostAuthorInfo;

0 commit comments

Comments
 (0)