1import React, {type ReactNode} from 'react';
2import clsx from 'clsx';
3import Link, {type Props as LinkProps} from '@docusaurus/Link';
4import AuthorSocials from '@theme/Blog/Components/Author/Socials';
5import type {Props} from '@theme/Blog/Components/Author';
6import Heading from '@theme/Heading';
7import styles from './styles.module.css';
8
9function MaybeLink(props: LinkProps): ReactNode {
10 if (props.href) {
11 return <Link {...props} />;
12 }
13 return <>{props.children}</>;
14}
15
16function AuthorTitle({title}: {title: string}) {
17 return (
18 <small className={styles.authorTitle} title={title}>
19 {title}
20 </small>
21 );
22}
23
24function AuthorName({name, as}: {name: string; as: Props['as']}) {
25 if (!as) {
26 return <span className={styles.authorName}>{name}</span>;
27 } else {
28 return (
29 <Heading as={as} className={styles.authorName}>
30 {name}
31 </Heading>
32 );
33 }
34}
35
36function AuthorBlogPostCount({count}: {count: number}) {
37 return <span className={clsx(styles.authorBlogPostCount)}>{count}</span>;
38}
39
40// Note: in the future we might want to have multiple "BlogAuthor" components
41// Creating different display modes with the "as" prop may not be the best idea
42// Explainer: https://kyleshevlin.com/prefer-multiple-compositions/
43// For now, we almost use the same design for all cases, so it's good enough
44export default function BlogAuthor({
45 as,
46 author,
47 className,
48 count,
49}: Props): ReactNode {
50 const {name, title, url, imageURL, email, page} = author;
51 const link =
52 page?.permalink || url || (email && `mailto:${email}`) || undefined;
53
54 return (
55 <div
56 className={clsx(
57 'avatar margin-bottom--lg margin-top--sm',
58 className,
59 styles[`author-as-${as}`],
60 )}>
61 {imageURL && (
62 <MaybeLink href={link} className="avatar__photo-link">
63 <img
64 className={clsx('avatar__photo', styles.authorImage)}
65 src={imageURL}
66 alt={name}
67 />
68 </MaybeLink>
69 )}
70
71 {(name || title) && (
72 <div className={clsx('avatar__intro', styles.authorDetails)}>
73 <div className="avatar__name">
74 {name && (
75 <MaybeLink href={link}>
76 <AuthorName name={name} as={as} />
77 </MaybeLink>
78 )}
79 {count !== undefined && <AuthorBlogPostCount count={count} />}
80 </div>
81 {!!title && <AuthorTitle title={title} />}
82
83 {/*
84 We always render AuthorSocials even if there's none
85 This keeps other things aligned with flexbox layout
86 */}
87 <AuthorSocials author={author} />
88 </div>
89 )}
90 </div>
91 );
92}
93
1import React, {type ReactNode} from 'react';
2import clsx from 'clsx';
3import Link, {type Props as LinkProps} from '@docusaurus/Link';
4import AuthorSocials from '@theme/Blog/Components/Author/Socials';
5import type {Props} from '@theme/Blog/Components/Author';
6import Heading from '@theme/Heading';
7import styles from './styles.module.css';
8
9function MaybeLink(props: LinkProps): ReactNode {
10 if (props.href) {
11 return <Link {...props} />;
12 }
13 return <>{props.children}</>;
14}
15
16function AuthorTitle({title}: {title: string}) {
17 return (
18 <small className={styles.authorTitle} title={title}>
19 {title}
20 </small>
21 );
22}
23
24function AuthorName({name, as}: {name: string; as: Props['as']}) {
25 if (!as) {
26 return <span className={styles.authorName}>{name}</span>;
27 } else {
28 return (
29 <Heading as={as} className={styles.authorName}>
30 {name}
31 </Heading>
32 );
33 }
34}
35
36function AuthorBlogPostCount({count}: {count: number}) {
37 return <span className={clsx(styles.authorBlogPostCount)}>{count}</span>;
38}
39
40// Note: in the future we might want to have multiple "BlogAuthor" components
41// Creating different display modes with the "as" prop may not be the best idea
42// Explainer: https://kyleshevlin.com/prefer-multiple-compositions/
43// For now, we almost use the same design for all cases, so it's good enough
44export default function BlogAuthor({
45 as,
46 author,
47 className,
48 count,
49}: Props): ReactNode {
50 const {name, title, url, imageURL, email, page} = author;
51 const link =
52 page?.permalink || url || (email && `mailto:${email}`) || undefined;
53
54 return (
55 <div
56 className={clsx(
57 'avatar margin-bottom--lg margin-top--sm',
58 className,
59 styles[`author-as-${as}`],
60 )}>
61 {imageURL && (
62 <MaybeLink href={link} className="avatar__photo-link">
63 <img
64 className={clsx('avatar__photo', styles.authorImage)}
65 src={imageURL}
66 alt={name}
67 />
68 </MaybeLink>
69 )}
70
71 {(name || title) && (
72 <div className={clsx('avatar__intro', styles.authorDetails)}>
73 <div className="avatar__name">
74 {name && (
75 <MaybeLink href={link}>
76 <AuthorName name={name} as={as} />
77 </MaybeLink>
78 )}
79 {count !== undefined && <AuthorBlogPostCount count={count} />}
80 </div>
81 {!!title && <AuthorTitle title={title} />}
82
83 {/*
84 We always render AuthorSocials even if there's none
85 This keeps other things aligned with flexbox layout
86 */}
87 <AuthorSocials author={author} />
88 </div>
89 )}
90 </div>
91 );
92}
93