mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-14 10:33:30 +01:00
feat(guide): sidebar
This commit is contained in:
@@ -17,21 +17,21 @@ export const Content = defineDocumentType(() => ({
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
summary: {
|
category: {
|
||||||
type: 'string',
|
|
||||||
},
|
|
||||||
image: {
|
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
required: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
computedFields: {
|
computedFields: {
|
||||||
slug: {
|
slug: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
resolve: (doc) => doc._raw.flattenedPath,
|
// eslint-disable-next-line unicorn/prefer-string-replace-all
|
||||||
|
resolve: (doc) => doc._raw.flattenedPath.replace(/\d+-/g, ''),
|
||||||
},
|
},
|
||||||
url: {
|
url: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
resolve: (post) => `/guide/${post._raw.flattenedPath}`,
|
// eslint-disable-next-line unicorn/prefer-string-replace-all
|
||||||
|
resolve: (doc) => `/guide/${doc._raw.flattenedPath.replace(/\d+-/g, '')}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { allContents } from 'contentlayer/generated';
|
import { allContents } from 'contentlayer/generated';
|
||||||
import { notFound } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
import { Mdx } from '~/components/Mdx';
|
import { Mdx } from '~/components/Mdx';
|
||||||
|
|
||||||
export async function generateStaticParams() {
|
export async function generateStaticParams() {
|
||||||
@@ -10,7 +10,7 @@ export default function Page({ params }: { params: { slug: string[] } }) {
|
|||||||
const content = allContents.find((content) => content.slug === params.slug?.join('/'));
|
const content = allContents.find((content) => content.slug === params.slug?.join('/'));
|
||||||
|
|
||||||
if (!content) {
|
if (!content) {
|
||||||
notFound();
|
redirect('/guide/introduction');
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
16
apps/guide/src/components/Section.tsx
Normal file
16
apps/guide/src/components/Section.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Section as DJSSection, type SectionOptions } from '@discordjs/ui';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
|
import { useMedia } from 'react-use';
|
||||||
|
|
||||||
|
// This is wrapper around the Section component from @discordjs/ui,
|
||||||
|
// it simply automatically sets the dense prop to true if the screen
|
||||||
|
// width is less than 768px. This is done to separate client-side logic
|
||||||
|
// from server-side rendering.
|
||||||
|
export function Section(options: PropsWithChildren<SectionOptions>) {
|
||||||
|
const matches = useMedia('(max-width: 768px)', true);
|
||||||
|
const modifiedOptions = { ...options, dense: matches };
|
||||||
|
|
||||||
|
return <DJSSection {...modifiedOptions} />;
|
||||||
|
}
|
||||||
@@ -1,8 +1,62 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
export function Sidebar() {
|
import { allContents } from 'contentlayer/generated';
|
||||||
// const pathname = usePathname();
|
import Link from 'next/link';
|
||||||
// const { setOpened } = useNav();
|
import { usePathname } from 'next/navigation';
|
||||||
|
import { Section } from './Section';
|
||||||
|
import { useNav } from '~/contexts/nav';
|
||||||
|
|
||||||
return null;
|
function transformItemsByCategory(allContents: any[]) {
|
||||||
|
return allContents.reduce((accumulator: any, content) => {
|
||||||
|
if (!accumulator[content.category]) {
|
||||||
|
accumulator[content.category] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
accumulator[content.category].push(content);
|
||||||
|
return accumulator;
|
||||||
|
}, {});
|
||||||
|
}
|
||||||
|
|
||||||
|
const items = allContents.map((content) => ({
|
||||||
|
title: content.title,
|
||||||
|
category: content.category,
|
||||||
|
slug: content.slug,
|
||||||
|
href: content.url,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const itemsByCategory = transformItemsByCategory(items);
|
||||||
|
|
||||||
|
export function Sidebar() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const { setOpened } = useNav();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-3 p-3">
|
||||||
|
{Object.keys(itemsByCategory).map((category, idx) => (
|
||||||
|
<Section
|
||||||
|
buttonClassName="bg-light-600 hover:bg-light-700 active:bg-light-800 dark:bg-dark-400 dark:hover:bg-dark-300 dark:active:bg-dark-400 focus:ring-width-2 focus:ring-blurple rounded p-3 outline-0 focus:ring"
|
||||||
|
key={`${category}-${idx}`}
|
||||||
|
title={category}
|
||||||
|
>
|
||||||
|
{itemsByCategory[category].map((member, index) => (
|
||||||
|
<Link
|
||||||
|
className={`dark:border-dark-100 border-light-800 focus:ring-width-2 focus:ring-blurple ml-5 flex flex-col border-l p-[5px] pl-6 outline-0 focus:rounded focus:border-0 focus:ring ${
|
||||||
|
decodeURIComponent(pathname ?? '') === member.href
|
||||||
|
? 'bg-blurple text-white'
|
||||||
|
: 'dark:hover:bg-dark-200 dark:active:bg-dark-100 hover:bg-light-700 active:bg-light-800'
|
||||||
|
}`}
|
||||||
|
href={member.href}
|
||||||
|
key={`${member.title}-${index}`}
|
||||||
|
onClick={() => setOpened(false)}
|
||||||
|
title={member.title}
|
||||||
|
>
|
||||||
|
<div className="flex flex-row place-items-center gap-2 lg:text-sm">
|
||||||
|
<span className="truncate">{member.title}</span>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user