Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions src/theme/BlogPostItem/Footer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React from "react";
import Link from "@docusaurus/Link";
import { useBlogPost } from "@docusaurus/plugin-content-blog/client";
import BlogPostItemFooterOriginal from "@theme-original/BlogPostItem/Footer";
import type BlogPostItemFooterType from "@theme/BlogPostItem/Footer";
import type { WrapperProps } from "@docusaurus/types";
import { getAuthorProfile } from "@site/src/utils/authors";

import styles from "./styles.module.css";

type Props = WrapperProps<typeof BlogPostItemFooterType>;

function getGitHubUrl(author: {
key?: string;
url?: string;
name?: string;
}): string | undefined {
if (author.key) {
return getAuthorProfile(author.key).githubUrl;
}

if (author.url && /github\.com\//i.test(author.url)) {
return author.url.startsWith("http") ? author.url : `https://${author.url}`;
}

return undefined;
}

export default function BlogPostItemFooterWrapper(props: Props): JSX.Element {
const { metadata, isBlogPostPage } = useBlogPost();
const primaryAuthor = metadata.authors?.[0];

const profile = primaryAuthor?.key
? getAuthorProfile(primaryAuthor.key)
: undefined;

const authorName = primaryAuthor?.name || profile?.name;
const authorAvatar = primaryAuthor?.imageURL || profile?.imageUrl;
const githubUrl = primaryAuthor
? getGitHubUrl({
key: primaryAuthor.key,
name: primaryAuthor.name,
url: primaryAuthor.url,
})
: undefined;

const roundedReadTime = Math.max(1, Math.ceil(metadata.readingTime || 0));
const readTimeText = `${roundedReadTime} min read`;
const authorSummary =
primaryAuthor?.description ||
profile?.description ||
primaryAuthor?.title ||
profile?.title;
const blogDate =
metadata.formattedDate ||
(metadata.date
? new Date(metadata.date).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
})
: "");

const showAuthorCard = Boolean(isBlogPostPage && primaryAuthor && authorName);

return (
<>
<BlogPostItemFooterOriginal {...props} />
{showAuthorCard && (
<section className={styles.authorCard} aria-label="Post author details">
<div className={styles.authorLeft}>
{authorAvatar ? (
<img
className={styles.authorAvatar}
src={authorAvatar}
alt={`${authorName} profile picture`}
loading="lazy"
/>
) : (
<div className={styles.authorAvatarFallback} aria-hidden="true">
{authorName?.charAt(0).toUpperCase()}
</div>
)}
Comment on lines +72 to +83
<div className={styles.authorIdentity}>
<p className={styles.authorName}>{authorName}</p>
{blogDate ? <p className={styles.authorDate}>{blogDate}</p> : null}
{authorSummary ? (
<p className={styles.authorSummary}>{authorSummary}</p>
) : null}
</div>
</div>

<div className={styles.authorRight}>
<p className={styles.readTime}>{readTimeText}</p>
{githubUrl ? (
<Link
className={styles.githubButton}
to={githubUrl}
target="_blank"
rel="noopener noreferrer"
>
GitHub Profile
</Link>
Comment on lines +96 to +103
) : null}
</div>
</section>
)}
</>
);
}
120 changes: 120 additions & 0 deletions src/theme/BlogPostItem/Footer/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
.authorCard {
position: relative;
width: 100%;
margin-top: 1.25rem;
padding: 1rem;
padding-bottom: 3.75rem;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 12px;
display: flex;
align-items: stretch;
justify-content: space-between;
gap: 1rem;
background: var(--ifm-background-surface-color);
box-sizing: border-box;
}

.authorLeft {
display: flex;
align-items: center;
gap: 0.75rem;
min-width: 0;
}

.authorAvatar,
.authorAvatarFallback {
width: 52px;
height: 52px;
border-radius: 50%;
flex-shrink: 0;
}

.authorAvatar {
object-fit: cover;
border: 1px solid var(--ifm-color-emphasis-300);
}

.authorAvatarFallback {
display: grid;
place-items: center;
font-weight: 700;
color: var(--ifm-color-primary-darkest);
background: var(--ifm-color-emphasis-200);
}

.authorIdentity {
min-width: 0;
}

.authorName {
margin: 0;
font-weight: 700;
line-height: 1.2;
}

.authorDate {
margin: 0.25rem 0 0;
color: var(--ifm-color-emphasis-700);
font-size: 0.9rem;
}

.authorSummary {
margin: 0.45rem 0 0;
color: var(--ifm-color-emphasis-800);
font-size: 0.92rem;
line-height: 1.4;
max-width: 56ch;
}

.authorRight {
margin-left: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
justify-content: flex-start;
gap: 0.75rem;
text-align: right;
}

.readTime {
margin: 0;
color: var(--ifm-color-emphasis-700);
font-weight: 600;
}

.githubButton {
position: absolute;
right: 1rem;
bottom: 1rem;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 0.45rem 0.8rem;
border-radius: 8px;
border: 1px solid var(--ifm-color-primary);
font-weight: 600;
text-decoration: none;
}

.githubButton:hover {
text-decoration: none;
}

@media (max-width: 640px) {
.authorCard {
padding-bottom: 1rem;
flex-direction: column;
align-items: flex-start;
}

.authorRight {
width: 100%;
margin-left: 0;
align-items: flex-start;
text-align: left;
}

.githubButton {
position: static;
}
}
Loading