feat: related guides sidebar (#7682)

* feat: related guides sidebar

* fix: hide related guides on mobile
pull/7694/head
Arik Chakma 3 weeks ago committed by GitHub
parent a0addd1408
commit f47bf798d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      .gitignore
  2. 11
      src/components/Guide/GuideContent.astro
  3. 74
      src/components/Guide/RelatedGuides.tsx
  4. 8
      src/lib/guide.ts
  5. 2
      src/lib/markdown.ts
  6. 8
      src/pages/frontend/job-description.astro

1
.gitignore vendored

@ -1,5 +1,6 @@
.idea .idea
.temp .temp
.astro
# build output # build output
dist/ dist/

@ -3,6 +3,7 @@ import { getGuideTableOfContent, type GuideFileType } from '../../lib/guide';
import MarkdownFile from '../MarkdownFile.astro'; import MarkdownFile from '../MarkdownFile.astro';
import { TableOfContent } from '../TableOfContent/TableOfContent'; import { TableOfContent } from '../TableOfContent/TableOfContent';
import { replaceVariables } from '../../lib/markdown'; import { replaceVariables } from '../../lib/markdown';
import { RelatedGuides } from './RelatedGuides';
interface Props { interface Props {
guide: GuideFileType; guide: GuideFileType;
@ -14,13 +15,21 @@ const allHeadings = guide.getHeadings();
const tableOfContent = getGuideTableOfContent(allHeadings); const tableOfContent = getGuideTableOfContent(allHeadings);
const showTableOfContent = tableOfContent.length > 0; const showTableOfContent = tableOfContent.length > 0;
const showRelatedGuides =
guide?.frontmatter?.relatedGuides &&
Object.keys(guide?.frontmatter?.relatedGuides).length > 0;
const { frontmatter: guideFrontmatter, author } = guide; const { frontmatter: guideFrontmatter, author } = guide;
--- ---
<article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'> <article class='lg:grid lg:max-w-full lg:grid-cols-[1fr_minmax(0,700px)_1fr]'>
{ {
showTableOfContent && ( (showTableOfContent || showRelatedGuides) && (
<div class='bg-gradient-to-r from-gray-50 py-0 lg:col-start-3 lg:col-end-4 lg:row-start-1'> <div class='bg-gradient-to-r from-gray-50 py-0 lg:col-start-3 lg:col-end-4 lg:row-start-1'>
<RelatedGuides
relatedTitle={guideFrontmatter?.relatedTitle}
relatedGuides={guideFrontmatter?.relatedGuides || {}}
client:load
/>
<TableOfContent toc={tableOfContent} client:load /> <TableOfContent toc={tableOfContent} client:load />
</div> </div>
) )

@ -0,0 +1,74 @@
import { useState } from 'react';
import { cn } from '../../lib/classname';
import { ChevronDown } from 'lucide-react';
type RelatedGuidesProps = {
relatedTitle?: string;
relatedGuides: Record<string, string>;
};
export function RelatedGuides(props: RelatedGuidesProps) {
const { relatedTitle = 'Other Guides', relatedGuides } = props;
const [isOpen, setIsOpen] = useState(false);
const relatedGuidesArray = Object.entries(relatedGuides).map(
([title, url]) => ({
title,
url,
}),
);
if (relatedGuidesArray.length === 0) {
return null;
}
return (
<div
className={cn(
'relative -mb-5 min-w-[250px] px-5 pt-0 max-lg:hidden lg:pt-10',
)}
>
<h4 className="text-lg font-medium max-lg:hidden">{relatedTitle}</h4>
<button
className="flex w-full items-center justify-between gap-2 bg-gray-300 px-3 py-2 text-sm font-medium lg:hidden"
onClick={() => setIsOpen(!isOpen)}
>
{relatedTitle}
<ChevronDown
size={16}
className={cn(
'transform transition-transform',
isOpen && 'rotate-180',
)}
/>
</button>
<ol
className={cn(
'mt-0.5 space-y-0 max-lg:absolute max-lg:top-full max-lg:z-10 max-lg:mt-0 max-lg:w-full max-lg:bg-white max-lg:shadow',
!isOpen && 'hidden lg:block',
isOpen && 'block',
)}
>
{relatedGuidesArray.map((relatedGuide) => (
<li key={relatedGuide.url}>
<a
href={relatedGuide.url}
className="text-sm text-gray-500 no-underline hover:text-black max-lg:block max-lg:border-b max-lg:px-3 max-lg:py-1"
onClick={() => {
if (!isOpen) {
return;
}
setIsOpen(false);
}}
>
{relatedGuide.title}
</a>
</li>
))}
</ol>
</div>
);
}

@ -20,6 +20,8 @@ export interface GuideFrontmatter {
priority: number; priority: number;
changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly'; changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly';
}; };
relatedTitle?: string;
relatedGuides?: Record<string, string>;
tags: string[]; tags: string[];
} }
@ -116,5 +118,11 @@ export function getGuideTableOfContent(headings: HeadingType[]) {
} }
}); });
if (tableOfContents.length > 5) {
tableOfContents.forEach((group) => {
group.children = [];
});
}
return tableOfContents; return tableOfContents;
} }

@ -11,7 +11,7 @@ export function replaceVariables(
currentYear: new Date().getFullYear().toString(), currentYear: new Date().getFullYear().toString(),
}; };
return markdown.replace(/@([^@]+)@/g, (match, p1) => { return markdown?.replace(/@([^@]+)@/g, (match, p1) => {
return allVariables[p1] || match; return allVariables[p1] || match;
}); });
} }

@ -12,7 +12,7 @@ const guide = await getGuideById(guideId);
const { frontmatter: guideData } = guide!; const { frontmatter: guideData } = guide!;
const ogImageUrl = const ogImageUrl =
guideData.seo.ogImageUrl || guideData.seo?.ogImageUrl ||
getOpenGraphImageUrl({ getOpenGraphImageUrl({
group: 'guide', group: 'guide',
resourceId: guideId, resourceId: guideId,
@ -20,9 +20,9 @@ const ogImageUrl =
--- ---
<BaseLayout <BaseLayout
title={replaceVariables(guideData.seo.title)} title={replaceVariables(guideData?.seo?.title)}
description={replaceVariables(guideData.seo.description)} description={replaceVariables(guideData?.seo?.description)}
permalink={guide.frontmatter.excludedBySlug} permalink={guideData.excludedBySlug}
canonicalUrl={guideData.canonicalUrl} canonicalUrl={guideData.canonicalUrl}
ogImageUrl={ogImageUrl} ogImageUrl={ogImageUrl}
> >

Loading…
Cancel
Save