Skip to content

Commit 1cb6d5f

Browse files
committed
feature(changelog): changelog page
1 parent 70419cb commit 1cb6d5f

File tree

10 files changed

+156
-47
lines changed

10 files changed

+156
-47
lines changed

contentlayer.config.ts

+2
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ const Changelog = defineDocumentType(() => ({
171171
description: { type: 'string', required: true },
172172
slug: { type: 'string' },
173173
version: { type: 'string' },
174+
releaseUrl: { type: 'string' },
175+
releaseDate: { type: 'string' },
174176
},
175177
computedFields: {
176178
frontMatter: {

i18n/ui.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,8 @@
204204
"sponsor": "Sponsor"
205205
},
206206
"table-of-content": {
207-
"on-this-page": "On this page"
207+
"on-this-page": "On this page",
208+
"versions": "Versions"
208209
},
209210
"vercel-callout": {
210211
"deployed-by": "Deployed by"
@@ -219,4 +220,4 @@
219220
"message": "A collection of beautiful websites that are built in Chakra UI",
220221
"submit-project-button-title": "Submit your project"
221222
}
222-
}
223+
}

layouts/changelog.tsx

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import PageContainer from 'components/page-container'
2+
import Sidebar from 'components/sidebar/sidebar'
3+
import componentsSidebar from 'configs/components.sidebar.json'
4+
import gettingStartedSidebar from 'configs/getting-started.sidebar.json'
5+
import hooksSidebar from 'configs/hooks.sidebar.json'
6+
import styledSystemSidebar from 'configs/styled-system.sidebar.json'
7+
import tutorialSidebar from 'configs/tutorial.sidebar.json'
8+
import communitySidebar from 'configs/community.sidebar.json'
9+
import semverRSort from 'semver/functions/rsort'
10+
import { ReactNode } from 'react'
11+
import { RouteItem } from 'utils/get-route-context'
12+
import { Frontmatter } from 'src/types/frontmatter'
13+
import { List, ListItem } from '@chakra-ui/react'
14+
import SidebarLink from 'components/sidebar/sidebar-link'
15+
import { allChangelogs } from 'contentlayer/generated'
16+
import TocNav from 'components/toc-nav'
17+
import { t } from 'utils/i18n'
18+
19+
export function getRoutes(slug: string): RouteItem[] {
20+
// for home page, use docs sidebar
21+
if (slug === '/') {
22+
return gettingStartedSidebar.routes as RouteItem[]
23+
}
24+
25+
const configMap = {
26+
'/getting-started': gettingStartedSidebar,
27+
'/docs/styled-system': styledSystemSidebar,
28+
'/docs/hooks': hooksSidebar,
29+
'/docs/components': componentsSidebar,
30+
'/tutorial': tutorialSidebar,
31+
'/community': communitySidebar,
32+
}
33+
34+
const [, sidebar] =
35+
Object.entries(configMap).find(([path]) => slug.startsWith(path)) ?? []
36+
37+
const routes = sidebar?.routes ?? []
38+
return routes as RouteItem[]
39+
}
40+
41+
export function getVersions(): RouteItem[] {
42+
return semverRSort(
43+
allChangelogs
44+
.filter(({ version }) => version.startsWith('2.'))
45+
.map(({ version }) => version),
46+
).map((version) => ({
47+
title: `v${version}`,
48+
path: `/changelog/${version}`,
49+
}))
50+
}
51+
52+
interface MDXLayoutProps {
53+
frontmatter: Frontmatter
54+
children: ReactNode
55+
hideToc?: boolean
56+
maxWidth?: string
57+
}
58+
59+
export default function MDXLayout(props: MDXLayoutProps) {
60+
const { frontmatter, children, maxWidth } = props
61+
62+
const routes = getRoutes(frontmatter.slug)
63+
const versions = getVersions()
64+
65+
return (
66+
<PageContainer
67+
hideToc={true}
68+
maxWidth={maxWidth}
69+
frontmatter={frontmatter}
70+
leftSidebar={<Sidebar routes={routes} />}
71+
rightSidebar={
72+
<TocNav title={t('component.table-of-content.versions')}>
73+
<List mt={2}>
74+
{versions.map(({ title, path }) => (
75+
<ListItem key={path}>
76+
<SidebarLink href={path}>{title}</SidebarLink>
77+
</ListItem>
78+
))}
79+
</List>
80+
</TocNav>
81+
}
82+
>
83+
{children}
84+
</PageContainer>
85+
)
86+
}

layouts/mdx.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ export default function MDXLayout(props: MDXLayoutProps) {
5353
hideToc={hideToc}
5454
maxWidth={maxWidth}
5555
frontmatter={frontmatter}
56-
sidebar={<Sidebar routes={routes} />}
56+
leftSidebar={<Sidebar routes={routes} />}
5757
pagination={
5858
<Pagination
5959
next={routeContext.nextRoute}

pages/changelog/[version].tsx

+10-3
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@ import semverMaxSatisfying from 'semver/ranges/max-satisfying'
44
import { useMDXComponent } from 'next-contentlayer/hooks'
55
import React from 'react'
66
import { MDXComponents } from 'components/mdx-components'
7-
import MDXLayout from 'layouts/mdx'
7+
import ChangelogLayout from 'layouts/changelog'
88

99
export default function Page({
1010
doc,
1111
}: InferGetStaticPropsType<typeof getStaticProps>) {
1212
const Component = useMDXComponent(doc.body.code)
1313
return (
14-
<MDXLayout frontmatter={doc.frontMatter}>
14+
<ChangelogLayout hideToc frontmatter={doc.frontMatter}>
1515
<Component components={MDXComponents} />
16-
</MDXLayout>
16+
</ChangelogLayout>
1717
)
1818
}
1919

@@ -39,6 +39,13 @@ export const getStaticProps = async (ctx) => {
3939
allChangelogs.map(({ version }) => version),
4040
'*',
4141
)
42+
43+
return {
44+
redirect: {
45+
destination: `/changelog/${versionParam}`,
46+
permanent: true,
47+
},
48+
}
4249
}
4350

4451
const doc = allChangelogs.find(({ version }) => version === versionParam)

pages/resources.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ function Resources() {
4646

4747
return (
4848
<PageContainer
49-
sidebar={<Sidebar routes={routes} />}
49+
leftSidebar={<Sidebar routes={routes} />}
5050
frontmatter={{
5151
title: t('resources.title'),
5252
description: t('resources.description'),

src/components/page-container.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,17 @@ interface PageContainerProps {
3939
hideToc?: boolean
4040
maxWidth?: string
4141
children: React.ReactNode
42-
sidebar?: React.ReactElement
42+
leftSidebar?: React.ReactElement
43+
rightSidebar?: React.ReactElement
4344
pagination?: React.ReactElement
4445
}
4546

4647
function PageContainer(props: PageContainerProps) {
4748
const {
4849
frontmatter,
4950
children,
50-
sidebar,
51+
leftSidebar,
52+
rightSidebar,
5153
pagination,
5254
hideToc,
5355
maxWidth = '48rem',
@@ -69,7 +71,7 @@ function PageContainer(props: PageContainerProps) {
6971
<Header />
7072
<Box as='main' className='main-content' w='full' maxW='8xl' mx='auto'>
7173
<Box display={{ md: 'flex' }}>
72-
{sidebar || null}
74+
{leftSidebar || null}
7375
<Box flex='1' minW='0'>
7476
<SkipNavContent />
7577
<Box id='content' px={5} mx='auto' minH='76vh'>
@@ -105,6 +107,7 @@ function PageContainer(props: PageContainerProps) {
105107
headings={headings}
106108
/>
107109
)}
110+
{rightSidebar}
108111
</Flex>
109112
</Box>
110113
</Box>

src/components/sidebar/sidebar.tsx

+4-6
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,10 @@ import SidebarLink from './sidebar-link'
2727

2828
const sortRoutes = (routes: RouteItem[]) => {
2929
return routes.sort(({ title: titleA }, { title: titleB }) => {
30-
if (titleA < titleB)
31-
return -1;
32-
if (titleA > titleB)
33-
return 1;
34-
return 0;
35-
});
30+
if (titleA < titleB) return -1
31+
if (titleA > titleB) return 1
32+
return 0
33+
})
3634
}
3735

3836
export type SidebarContentProps = Routes & {

src/components/table-of-content.tsx

+3-31
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import * as React from 'react'
1111
import { useScrollSpy } from 'hooks/use-scrollspy'
1212
import { t } from 'utils/i18n'
1313
import type { FrontmatterHeading } from 'src/types/frontmatter'
14+
import TocNav from './toc-nav'
1415

1516
interface TableOfContentProps extends BoxProps {
1617
headings: FrontmatterHeading[]
@@ -27,36 +28,7 @@ function TableOfContent(props: TableOfContentProps) {
2728
const linkColor = useColorModeValue('gray.600', 'gray.400')
2829
const linkHoverColor = useColorModeValue('gray.900', 'gray.600')
2930
return (
30-
<Box
31-
as='nav'
32-
aria-labelledby='toc-title'
33-
width='16rem'
34-
flexShrink={0}
35-
display={{ base: 'none', xl: 'block' }}
36-
position='sticky'
37-
py='10'
38-
pr='4'
39-
top='6rem'
40-
right='0'
41-
fontSize='sm'
42-
alignSelf='start'
43-
maxHeight='calc(100vh - 8rem)'
44-
overflowY='auto'
45-
sx={{ overscrollBehavior: 'contain' }}
46-
{...rest}
47-
>
48-
<Text
49-
as='h2'
50-
id='toc-title'
51-
textTransform='uppercase'
52-
fontWeight='bold'
53-
fontSize='xs'
54-
color='gray.700'
55-
_dark={{ color: 'gray.400' }}
56-
letterSpacing='wide'
57-
>
58-
{t('component.table-of-content.on-this-page')}
59-
</Text>
31+
<TocNav title={t('component.table-of-content.on-this-page')} {...rest}>
6032
<OrderedList spacing={1} ml='0' mt='4' styleType='none'>
6133
{headings.map(({ id, text, level }) => (
6234
<ListItem key={id} title={text} ml={level === 'h3' ? '4' : undefined}>
@@ -76,7 +48,7 @@ function TableOfContent(props: TableOfContentProps) {
7648
</ListItem>
7749
))}
7850
</OrderedList>
79-
</Box>
51+
</TocNav>
8052
)
8153
}
8254

src/components/toc-nav.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Box, BoxProps, Text } from '@chakra-ui/react'
2+
3+
export default function TocNav({ children, title, ...rest }: BoxProps) {
4+
return (
5+
<Box
6+
as='nav'
7+
aria-labelledby='toc-title'
8+
width='16rem'
9+
flexShrink={0}
10+
display={{ base: 'none', xl: 'block' }}
11+
position='sticky'
12+
py='10'
13+
pr='4'
14+
top='6rem'
15+
right='0'
16+
fontSize='sm'
17+
alignSelf='start'
18+
maxHeight='calc(100vh - 8rem)'
19+
overflowY='auto'
20+
sx={{ overscrollBehavior: 'contain' }}
21+
{...rest}
22+
>
23+
{title && (
24+
<Text
25+
as='h2'
26+
id='toc-title'
27+
textTransform='uppercase'
28+
fontWeight='bold'
29+
fontSize='xs'
30+
color='gray.700'
31+
_dark={{ color: 'gray.400' }}
32+
letterSpacing='wide'
33+
>
34+
{title}
35+
</Text>
36+
)}
37+
{children}
38+
</Box>
39+
)
40+
}

0 commit comments

Comments
 (0)