@primer/doctocat-nextjs 0.5.0-rc.d795ae7 → 0.5.1-rc.6f568d0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # @primer/doctocat-nextjs
2
2
 
3
+ ## 0.5.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#35](https://github.com/primer/doctocat-nextjs/pull/35) [`d0c5bd4`](https://github.com/primer/doctocat-nextjs/commit/d0c5bd40829c417b610682049e4b5bd59b3f87f0) Thanks [@rezrah](https://github.com/rezrah)! - Enable support for `trailingSlash: true` in `next.config.js`
8
+
3
9
  ## 0.5.0
4
10
 
5
11
  ### Minor Changes
@@ -12,12 +12,21 @@ type IndexCardsProps = {
12
12
 
13
13
  export function IndexCards({route, folderData}: IndexCardsProps) {
14
14
  // We don't want to show children of these pages. E.g. tabbed pages
15
- const onlyDirectChildren = folderData.filter(
16
- item => item.route.includes(`${route}/`) && item.route.split('/').length === 3,
17
- )
15
+ const onlyDirectChildren = folderData.filter(item => {
16
+ // Normalize paths regardless of trailing slash enablement
17
+ const normalizedRoute = route.endsWith('/') ? route : `${route}/`
18
+ const normalizedItemRoute = item.route.endsWith('/') ? item.route : `${item.route}/`
19
+
20
+ const isChild = normalizedItemRoute.startsWith(normalizedRoute)
21
+ if (!isChild) return false
22
+
23
+ const routeSegments = normalizedRoute.split('/').filter(Boolean).length
24
+ const itemSegments = normalizedItemRoute.split('/').filter(Boolean).length
18
25
 
19
- const filteredData = onlyDirectChildren.filter(item => item.type === 'doc' && item.route.includes(`${route}/`))
26
+ return itemSegments === routeSegments + 1
27
+ })
20
28
 
29
+ const filteredData = onlyDirectChildren.filter(item => item.type === 'doc')
21
30
  return (
22
31
  <Stack direction="vertical" padding="none" gap="spacious">
23
32
  {filteredData.map((item: DocsItem) => {
@@ -53,16 +53,13 @@ export type ThemeProps = PropsWithChildren<{
53
53
 
54
54
  export function Theme({pageMap, children}: ThemeProps) {
55
55
  const pathname = usePathname()
56
+ const pathHasTrailingSlash = pathname.endsWith('/')
56
57
 
57
58
  const normalizedPages = normalizePages({
58
59
  list: pageMap,
59
60
  route: pathname,
60
61
  })
61
62
 
62
- const {activeMetadata} = normalizedPages
63
-
64
- const {filePath = '', title = ''} = activeMetadata || {}
65
-
66
63
  const route = usePathname()
67
64
 
68
65
  const fsPath = useFSRoute()
@@ -79,33 +76,42 @@ export function Theme({pageMap, children}: ThemeProps) {
79
76
  // eslint-disable-next-line i18n-text/no-en
80
77
  const siteTitle = process.env.NEXT_PUBLIC_SITE_TITLE || 'Example Site'
81
78
  const isHomePage = route === '/'
82
- const isIndexPage =
83
- /index\.mdx?$/.test(filePath) && !isHomePage && activeMetadata && activeMetadata['show-tabs'] === undefined
79
+
80
+ const activeFile = isHomePage
81
+ ? undefined
82
+ : (normalizedPages.flatDocsDirectories.find(
83
+ item => `${item.route}${pathHasTrailingSlash ? '/' : ''}` === pathname,
84
+ ) as MdxFile)
85
+
86
+ const activeMetadata = activeFile?.frontMatter || {}
87
+ const filePath = activeMetadata.filePath || ''
88
+ const title = activeMetadata.title || ''
89
+
90
+ const isIndexPage = /index\.mdx?$/.test(filePath) && !isHomePage && activeMetadata['show-tabs'] === undefined
84
91
  const data = !isHomePage && activePath[activePath.length - 2]
85
92
  const filteredTabData: MdxFile[] = data && hasChildren(data) ? ((data as Folder).children as MdxFile[]) : []
86
93
 
87
94
  const relatedLinks = getRelatedPages(route, activeMetadata, flatDocsDirectories)
88
- const disablePageAnimation = activeMetadata?.options?.disablePageAnimation || false
95
+ const disablePageAnimation = activeMetadata.options?.disablePageAnimation || false
96
+
89
97
  return (
90
98
  <>
91
99
  <BrandThemeProvider dir="ltr" colorMode={colorMode}>
92
100
  <ThemeProvider colorMode={colorMode}>
93
101
  <BaseStyles>
94
- {activeMetadata && (
95
- <Head>
96
- <title>{title}</title>
97
- {activeMetadata.description && <meta name="description" content={activeMetadata.description} />}
98
- <meta property="og:type" content="website" />
99
- <meta property="og:title" content={title} />
100
- {activeMetadata.description && <meta property="og:description" content={activeMetadata.description} />}
101
- <meta property="og:image" content={activeMetadata.image || '/og-image.png'} />
102
- {/* X (Twitter) OG */}
103
- <meta name="twitter:card" content="summary_large_image" />
104
- <meta name="twitter:title" content={title} />
105
- {activeMetadata.description && <meta name="twitter:description" content={activeMetadata.description} />}
106
- <meta name="twitter:image" content={activeMetadata.image || '/og-image.png'} />
107
- </Head>
108
- )}
102
+ <Head>
103
+ <title>{title}</title>
104
+ {activeMetadata.description && <meta name="description" content={activeMetadata.description} />}
105
+ <meta property="og:type" content="website" />
106
+ <meta property="og:title" content={title} />
107
+ {activeMetadata.description && <meta property="og:description" content={activeMetadata.description} />}
108
+ <meta property="og:image" content={activeMetadata.image || '/og-image.png'} />
109
+ {/* X (Twitter) OG */}
110
+ <meta name="twitter:card" content="summary_large_image" />
111
+ <meta name="twitter:title" content={title} />
112
+ {activeMetadata.description && <meta name="twitter:description" content={activeMetadata.description} />}
113
+ <meta name="twitter:image" content={activeMetadata.image || '/og-image.png'} />
114
+ </Head>
109
115
 
110
116
  <ContentWrapper disableAnimations={disablePageAnimation}>
111
117
  <PRCBox
@@ -159,22 +165,22 @@ export function Theme({pageMap, children}: ThemeProps) {
159
165
 
160
166
  <Box>
161
167
  <Stack direction="vertical" padding="none" gap={12} alignItems="flex-start">
162
- {activeMetadata?.title && (
168
+ {activeMetadata.title && (
163
169
  <Heading as="h1" size="3">
164
170
  {activeMetadata.title}
165
171
  </Heading>
166
172
  )}
167
- {activeMetadata?.description && (
173
+ {activeMetadata.description && (
168
174
  <Text as="p" variant="muted" size="300">
169
175
  {activeMetadata.description}
170
176
  </Text>
171
177
  )}
172
- {activeMetadata?.image && (
178
+ {activeMetadata.image && (
173
179
  <Box paddingBlockStart={16} style={{width: '100%'}}>
174
180
  <Hero.Image src={activeMetadata.image} alt={activeMetadata['image-alt']} />
175
181
  </Box>
176
182
  )}
177
- {activeMetadata && activeMetadata['action-1-text'] && (
183
+ {activeMetadata['action-1-text'] && (
178
184
  <Box paddingBlockStart={16}>
179
185
  <ButtonGroup>
180
186
  <Button as="a" href={activeMetadata['action-1-link']}>
@@ -190,9 +196,7 @@ export function Theme({pageMap, children}: ThemeProps) {
190
196
  )}
191
197
  </Stack>
192
198
  </Box>
193
- {activeMetadata && activeMetadata['show-tabs'] && (
194
- <UnderlineNav tabData={filteredTabData} />
195
- )}
199
+ {activeMetadata['show-tabs'] && <UnderlineNav tabData={filteredTabData} />}
196
200
  </>
197
201
  )}
198
202
  <article>
@@ -63,6 +63,11 @@ export function Sidebar({pageMap}: SidebarProps) {
63
63
  )
64
64
  }
65
65
 
66
+ const normalizePath = (path: string) => {
67
+ // Remove trailing slash unless it's the root path
68
+ return path === '/' ? path : path.replace(/\/+$/, '')
69
+ }
70
+
66
71
  return (
67
72
  <NavList.Group title={subNavName} key={item.name} sx={{mb: 24}}>
68
73
  {item.children
@@ -73,12 +78,13 @@ export function Sidebar({pageMap}: SidebarProps) {
73
78
  if (!hasChildren(child)) {
74
79
  const {name, route} = child as MdxFile
75
80
 
81
+ const cleanPathname = normalizePath(pathname)
76
82
  return (
77
83
  <NavList.Item
78
84
  as={NextLink}
79
85
  key={name}
80
86
  href={route}
81
- aria-current={route === pathname ? 'page' : undefined}
87
+ aria-current={route === cleanPathname ? 'page' : undefined}
82
88
  >
83
89
  {(child as MdxFile).frontMatter?.title || name}
84
90
  </NavList.Item>
@@ -88,17 +94,24 @@ export function Sidebar({pageMap}: SidebarProps) {
88
94
  if (hasChildren(child)) {
89
95
  const landingPageItem = (child as Folder).children.find(
90
96
  innerChild => (innerChild as DocsItem).name === 'index',
91
- )
97
+ ) as MdxFile
98
+
99
+ // Then inside your component where you need to compare paths:
100
+ const cleanPathname = normalizePath(pathname)
101
+ const cleanRoute = normalizePath(landingPageItem.route)
102
+
103
+ // For checking if current path is this route or a direct child:
104
+ const isCurrentOrChild = cleanPathname === cleanRoute || cleanPathname.startsWith(`${cleanRoute}/`)
92
105
 
93
106
  return (
94
107
  <NavList.Item
95
108
  as={NextLink}
96
- key={(landingPageItem as MdxFile).route}
97
- href={(landingPageItem as MdxFile).route}
109
+ key={landingPageItem.route}
110
+ href={landingPageItem.route}
98
111
  sx={{textTransform: 'capitalize'}}
99
- aria-current={(landingPageItem as MdxFile).route === pathname ? 'page' : undefined}
112
+ aria-current={isCurrentOrChild ? 'page' : undefined}
100
113
  >
101
- {(landingPageItem as MdxFile).frontMatter?.title || item.name}
114
+ {landingPageItem.frontMatter?.title || item.name}
102
115
  </NavList.Item>
103
116
  )
104
117
  }
@@ -12,8 +12,7 @@ type UnderlineNavProps = {
12
12
  export function UnderlineNav({tabData}: UnderlineNavProps) {
13
13
  const [isClient, setIsClient] = useState(false)
14
14
  const pathname = usePathname()
15
-
16
- const currentRoute = pathname
15
+ const pathHasTrailingSlash = pathname.endsWith('/')
17
16
 
18
17
  // Reorders tabData so the tab with name === index is always first
19
18
  if (tabData.length > 1) {
@@ -34,12 +33,14 @@ export function UnderlineNav({tabData}: UnderlineNavProps) {
34
33
  <PrimerUnderlineNav aria-label="Sibling pages">
35
34
  {tabData.length > 1 &&
36
35
  tabData.reverse().map(item => {
36
+ const cleanPathname = pathname === '/' ? '/' : pathHasTrailingSlash ? pathname.slice(0, -1) : pathname
37
+
37
38
  return (
38
39
  <PrimerUnderlineNav.Item
39
40
  as={Link}
40
41
  key={item.name}
41
42
  href={`${item.route}`}
42
- aria-current={currentRoute === item.route ? 'page' : undefined}
43
+ aria-current={cleanPathname === item.route ? 'page' : undefined}
43
44
  >
44
45
  {(item.frontMatter && (item.frontMatter['tab-label'] || item.frontMatter.title)) || item.name}
45
46
  </PrimerUnderlineNav.Item>
@@ -150,6 +150,7 @@
150
150
  .Prose ul:not(:global(.custom-component) ul) {
151
151
  list-style-type: image;
152
152
  list-style-image: var(--brand-Prose-unorderedList-imageUrl);
153
+ margin-block-end: var(--base-size-24) !important;
153
154
  }
154
155
 
155
156
  .Prose li:not(:global(.custom-component) li) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@primer/doctocat-nextjs",
3
- "version": "0.5.0-rc.d795ae7",
3
+ "version": "0.5.1-rc.6f568d0",
4
4
  "description": "A Next.js theme for building Primer documentation sites",
5
5
  "main": "index.js",
6
6
  "type": "module",