feat: related guides sidebar

feat/sidebar
Arik Chakma 2 weeks ago
parent ee83070507
commit 9231f78bfd
  1. 1
      .gitignore
  2. 11
      src/components/Guide/GuideContent.astro
  3. 70
      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
.temp
.astro
# build output
dist/

@ -3,6 +3,7 @@ import { getGuideTableOfContent, type GuideFileType } from '../../lib/guide';
import MarkdownFile from '../MarkdownFile.astro';
import { TableOfContent } from '../TableOfContent/TableOfContent';
import { replaceVariables } from '../../lib/markdown';
import { RelatedGuides } from './RelatedGuides';
interface Props {
guide: GuideFileType;
@ -14,13 +15,21 @@ const allHeadings = guide.getHeadings();
const tableOfContent = getGuideTableOfContent(allHeadings);
const showTableOfContent = tableOfContent.length > 0;
const showRelatedGuides =
guide?.frontmatter?.relatedGuides &&
Object.keys(guide?.frontmatter?.relatedGuides).length > 0;
const { frontmatter: guideFrontmatter, author } = guide;
---
<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'>
<RelatedGuides
relatedTitle={guideFrontmatter?.relatedTitle}
relatedGuides={guideFrontmatter?.relatedGuides || {}}
client:load
/>
<TableOfContent toc={tableOfContent} client:load />
</div>
)

@ -0,0 +1,70 @@
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,
}),
);
return (
<div
className={cn(
'relative min-w-[250px] px-5 pt-0 max-lg:mb-0.5 max-lg:min-w-full max-lg:max-w-full max-lg:border-none max-lg:px-0 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;
changefreq: 'daily' | 'weekly' | 'monthly' | 'yearly';
};
relatedTitle?: string;
relatedGuides?: Record<string, string>;
tags: string[];
}
@ -116,5 +118,11 @@ export function getGuideTableOfContent(headings: HeadingType[]) {
}
});
if (tableOfContents.length > 5) {
tableOfContents.forEach((group) => {
group.children = [];
});
}
return tableOfContents;
}

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

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

Loading…
Cancel
Save