@primer/doctocat-nextjs 0.2.0-rc.eeac884 → 0.3.0-rc.0fe5c68

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,19 @@
1
1
  # @primer/doctocat-nextjs
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#22](https://github.com/primer/doctocat-nextjs/pull/22) [`2b57bc4`](https://github.com/primer/doctocat-nextjs/commit/2b57bc456efc03c99255ca90098ca3e035da1206) Thanks [@rezrah](https://github.com/rezrah)! - Add live code editing and previews for `jsx` code blocks in Markdown. All other code blocks will continue to render as normal.
8
+
9
+ E.g.
10
+
11
+ <pre>
12
+ ```jsx
13
+ <p>Your React code here</p>
14
+ ```
15
+ </pre>
16
+
3
17
  ## 0.2.0
4
18
 
5
19
  ### Minor Changes
@@ -2,5 +2,5 @@ import React, {PropsWithChildren} from 'react'
2
2
  import {Text} from '@primer/react'
3
3
 
4
4
  export function Caption(props: PropsWithChildren) {
5
- return <Text as="p" {...props} sx={{mt: 2, mb: 3, fontSize: 1, color: 'var(--brand-color-text-default)'}} />
5
+ return <Text as="span" {...props} sx={{mt: 2, mb: 3, fontSize: 1, color: 'var(--brand-color-text-default)'}} />
6
6
  }
@@ -1,6 +1,8 @@
1
1
  import {createContext} from 'react'
2
2
 
3
- export type ColorMode = 'light' | 'dark'
3
+ export const colorModes = ['light', 'dark'] as const
4
+
5
+ export type ColorMode = (typeof colorModes)[number]
4
6
 
5
7
  export type ColorModeContextProps = {
6
8
  colorMode: ColorMode
@@ -7,3 +7,5 @@ export * from './library'
7
7
  export {TableOfContents} from './layout/table-of-contents/TableOfContents'
8
8
  export {Article} from './layout/article/Article'
9
9
  export {HeadingLink} from './layout/heading-link/HeadingLinks'
10
+ export {CodeBlock} from './layout/code-block/CodeBlock'
11
+ export {PropTableValues} from './layout/prop-values/PropValues'
@@ -0,0 +1,21 @@
1
+ .reviewedLabel {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: var(--base-size-8);
5
+ background-color: var(--base-color-scale-purple-0);
6
+ border-color: transparent;
7
+ color: var(--brand-color-text-default);
8
+ font-weight: var(--brand-text-weight-300);
9
+ }
10
+
11
+ [data-color-mode='dark'] .reviewedLabel {
12
+ background-color: var(--base-color-scale-purple-8);
13
+ }
14
+
15
+ .icon {
16
+ fill: var(--base-color-scale-purple-5);
17
+ }
18
+
19
+ [data-color-mode='dark'] .icon {
20
+ fill: var(--base-color-scale-purple-1);
21
+ }
@@ -0,0 +1,19 @@
1
+ 'use client'
2
+ import {Label} from '@primer/react'
3
+ import {AccessibilityInsetIcon} from '@primer/octicons-react'
4
+ import React from 'react'
5
+
6
+ import styles from './AccessibilityLabel.module.css'
7
+
8
+ type AccessibilityLabelProps = {
9
+ short?: boolean
10
+ }
11
+
12
+ export function AccessibilityLabel({short}: AccessibilityLabelProps) {
13
+ return (
14
+ <Label size="large" className={styles.reviewedLabel}>
15
+ <AccessibilityInsetIcon className={styles.icon} />
16
+ {short ? 'Reviewed' : 'Reviewed for accessibility'}
17
+ </Label>
18
+ )
19
+ }
@@ -3,6 +3,27 @@
3
3
  }
4
4
 
5
5
  .Article--withToc {
6
- grid-template-columns: 1fr 200px;
7
6
  gap: var(--base-size-48);
8
7
  }
8
+
9
+ .main {
10
+ order: 1;
11
+ }
12
+
13
+ .aside {
14
+ order: 0;
15
+ }
16
+
17
+ @media screen and (min-width: 768px) {
18
+ .main {
19
+ order: 0;
20
+ }
21
+
22
+ .aside {
23
+ order: 1;
24
+ }
25
+
26
+ .Article--withToc {
27
+ grid-template-columns: 1fr 200px;
28
+ }
29
+ }
@@ -1,3 +1,4 @@
1
+ 'use client'
1
2
  import React, {PropsWithChildren} from 'react'
2
3
  import {TableOfContents} from '../table-of-contents/TableOfContents'
3
4
  import styles from './Article.module.css'
@@ -5,20 +6,58 @@ import bodyStyles from '../../../css/prose.module.css'
5
6
  import {Heading as HeadingType} from 'nextra'
6
7
 
7
8
  import clsx from 'clsx'
9
+ import {AccessibilityLabel} from './AccessibilityLabel'
10
+ import {Box, Stack} from '@primer/react-brand'
11
+ import {ReadinessLabel} from './ReadinessLabel'
12
+ import {SourceLink} from './SourceLink'
8
13
 
9
14
  type Props = {
10
15
  toc: HeadingType[]
11
16
  metadata: {
12
17
  layout?: string
18
+ [key: string]: unknown
19
+ figma?: string
20
+ source?: string
21
+ storybook?: string
13
22
  }
14
23
  }
15
24
 
16
25
  export function Article({children, toc, metadata}: PropsWithChildren<Props>) {
17
26
  const hasToc = toc.length > 0
27
+
28
+ const hasMetadata = Boolean(
29
+ metadata.ready || metadata.a11yReviewed || metadata.source || metadata.figma || metadata.storybook,
30
+ )
31
+
18
32
  return (
19
- <div className={clsx(styles.Article, hasToc && styles['Article--withToc'])}>
20
- <div className={clsx(metadata.layout !== 'custom' && bodyStyles.Prose)}>{children}</div>
21
- {hasToc && <TableOfContents headings={toc} />}
22
- </div>
33
+ <>
34
+ <div className={clsx(styles.Article, hasToc && styles['Article--withToc'])}>
35
+ <div className={styles.main}>
36
+ {hasMetadata ? (
37
+ <Box marginBlockEnd={48}>
38
+ <Stack padding="none" direction="horizontal" justifyContent="space-between">
39
+ {metadata.ready || metadata.a11yReviewed ? (
40
+ <Stack direction="horizontal" gap={8} padding="none">
41
+ {metadata.ready === true && <ReadinessLabel />}
42
+ {typeof metadata.a11yReviewed === 'boolean' && metadata.a11yReviewed && <AccessibilityLabel />}
43
+ </Stack>
44
+ ) : null}
45
+ <Stack direction="horizontal" gap={16} padding="none">
46
+ {metadata.source ? <SourceLink type="github" href={metadata.source} /> : null}
47
+ {metadata.figma ? <SourceLink type="figma" href={metadata.figma} /> : null}
48
+ {metadata.storybook ? <SourceLink type="storybook" href={metadata.storybook} /> : null}
49
+ </Stack>
50
+ </Stack>
51
+ </Box>
52
+ ) : null}
53
+ <div className={clsx(metadata.layout !== 'custom' && bodyStyles.Prose)}>{children}</div>
54
+ </div>
55
+ {hasToc && (
56
+ <div className={styles.aside}>
57
+ <TableOfContents headings={toc} />
58
+ </div>
59
+ )}
60
+ </div>
61
+ </>
23
62
  )
24
63
  }
@@ -0,0 +1,21 @@
1
+ .label {
2
+ display: flex;
3
+ align-items: center;
4
+ gap: var(--base-size-8);
5
+ background-color: var(--base-color-scale-green-0);
6
+ border-color: transparent;
7
+ color: var(--brand-color-text-default);
8
+ font-weight: var(--brand-text-weight-300);
9
+ }
10
+
11
+ [data-color-mode='dark'] .label {
12
+ background-color: var(--base-color-scale-green-8);
13
+ }
14
+
15
+ .icon {
16
+ fill: var(--base-color-scale-green-5);
17
+ }
18
+
19
+ [data-color-mode='dark'] .icon {
20
+ fill: var(--base-color-scale-green-1);
21
+ }
@@ -0,0 +1,15 @@
1
+ 'use client'
2
+ import React from 'react'
3
+ import {Label} from '@primer/react'
4
+ import {CheckIcon} from '@primer/octicons-react'
5
+
6
+ import styles from './ReadinessLabel.module.css'
7
+
8
+ export function ReadinessLabel() {
9
+ return (
10
+ <Label size="large" className={styles.label}>
11
+ <CheckIcon className={styles.icon} />
12
+ Ready to use
13
+ </Label>
14
+ )
15
+ }
@@ -0,0 +1,7 @@
1
+ .link:not(:hover):not(:focus):not(:active) {
2
+ text-decoration: none !important;
3
+ }
4
+
5
+ .icon {
6
+ fill: var(--brand-InlineLink-color-rest);
7
+ }
@@ -0,0 +1,64 @@
1
+ import {InlineLink, Stack, Text} from '@primer/react-brand'
2
+ import {BookIcon, MarkGithubIcon} from '@primer/octicons-react'
3
+ import React from 'react'
4
+
5
+ import styles from './SourceLink.module.css'
6
+
7
+ type SourceLinkProps = {
8
+ type: 'github' | 'storybook' | 'figma'
9
+ href: string
10
+ }
11
+
12
+ export function SourceLink({href, type}: SourceLinkProps) {
13
+ return (
14
+ <div className="custom-component">
15
+ <Stack padding="none" direction="horizontal" alignItems="center" gap={4}>
16
+ {type === 'github' ? <MarkGithubIcon className={styles.icon} /> : null}
17
+ {type === 'storybook' ? <BookIcon className={styles.icon} /> : null}
18
+ {type === 'figma' ? <FigmaLogo className={styles.icon} /> : null}
19
+
20
+ {type === 'github' ? (
21
+ <Text size="100">
22
+ <InlineLink href={href} className={styles.link} target="_blank">
23
+ GitHub
24
+ </InlineLink>
25
+ </Text>
26
+ ) : null}
27
+ {type === 'storybook' ? (
28
+ <Text size="100">
29
+ <InlineLink href={href} className={styles.link} target="_blank">
30
+ Storybook
31
+ </InlineLink>
32
+ </Text>
33
+ ) : null}
34
+ {type === 'figma' ? (
35
+ <Text size="100">
36
+ <InlineLink href={href} className={styles.link} target="_blank">
37
+ Figma
38
+ </InlineLink>
39
+ </Text>
40
+ ) : null}
41
+ </Stack>
42
+ </div>
43
+ )
44
+ }
45
+
46
+ function FigmaLogo({className}: {className?: string}) {
47
+ return (
48
+ <svg
49
+ className={className}
50
+ viewBox="0 0 16 16"
51
+ aria-hidden="true"
52
+ width={16}
53
+ height={16}
54
+ fill="currentColor"
55
+ xmlns="http://www.w3.org/2000/svg"
56
+ >
57
+ <path
58
+ fillRule="evenodd"
59
+ clipRule="evenodd"
60
+ d="M5.417 0A3.167 3.167 0 0 0 3.37 5.583 3.16 3.16 0 0 0 2.25 8a3.16 3.16 0 0 0 1.12 2.417 3.167 3.167 0 1 0 5.213 2.417V10.687a3.165 3.165 0 0 0 3.87-4.964A3.167 3.167 0 0 0 10.582 0H5.417Zm4.727 6.333h.21a1.665 1.665 0 1 1-.21 0Zm.25-1.5h.19a1.667 1.667 0 1 0 0-3.333H5.416a1.667 1.667 0 1 0 0 3.333h4.687a3.226 3.226 0 0 1 .29 0ZM3.75 8c0-.92.746-1.667 1.667-1.667h1.666v3.334H5.417C4.497 9.667 3.75 8.92 3.75 8Zm1.667 3.167a1.667 1.667 0 1 0 1.666 1.667v-1.667H5.417Z"
61
+ />
62
+ </svg>
63
+ )
64
+ }
@@ -0,0 +1,18 @@
1
+ 'use client'
2
+ import React, {PropsWithChildren} from 'react'
3
+
4
+ import {ReactCodeBlock} from './ReactCodeBlock'
5
+ import {Pre} from 'nextra/components'
6
+
7
+ type CodeBlockProps = {
8
+ 'data-language': string
9
+ jsxScope: Record<string, unknown>
10
+ } & PropsWithChildren<HTMLElement>
11
+
12
+ export function CodeBlock(props: CodeBlockProps) {
13
+ if (['tsx', 'jsx'].includes(props['data-language'])) {
14
+ return <ReactCodeBlock {...props} />
15
+ }
16
+
17
+ return <Pre>{props.children}</Pre>
18
+ }
@@ -0,0 +1,68 @@
1
+ .CodeBlock {
2
+ margin-block: var(--base-size-24);
3
+ background-color: var(--brand-color-canvas-default);
4
+ border: var(--brand-borderWidth-thin) solid var(--brand-color-border-default);
5
+ border-radius: var(--brand-borderRadius-large);
6
+ display: flex;
7
+ flex-direction: column;
8
+ overflow: hidden;
9
+ width: 100%;
10
+ position: relative;
11
+ }
12
+
13
+ .Preview {
14
+ display: flex;
15
+ width: 100%;
16
+ padding: var(--base-size-16);
17
+ padding-block: var(--base-size-64);
18
+ min-height: 240px;
19
+ flex: 1 1;
20
+ align-items: center;
21
+ justify-content: center;
22
+ }
23
+
24
+ .Editor {
25
+ font-size: var(--brand-text-size-100);
26
+ line-height: var(--brand-text-lineHeight-100);
27
+ font-family: var(--brand-fontStack-monospace);
28
+ background-color: var(--brand-color-canvas-subtle);
29
+ overflow: auto;
30
+ }
31
+
32
+ .Editor pre {
33
+ font-size: inherit;
34
+ border-radius: 0;
35
+ padding: var(--base-size-16) !important;
36
+ background-color: var(--brand-color-canvas-subtle) !important;
37
+ }
38
+
39
+ .Toolbar {
40
+ display: flex;
41
+ gap: var(--base-size-8);
42
+ padding-inline: var(--base-size-16);
43
+ padding-block-start: var(--base-size-16);
44
+ border-top: var(--brand-borderWidth-thin) solid var(--brand-color-border-default);
45
+ background-color: var(--brand-color-canvas-subtle);
46
+ }
47
+
48
+ .Error pre {
49
+ margin-block: 0 !important;
50
+ border-radius: 0;
51
+ font-size: var(--brand-text-size-100);
52
+ line-height: var(--brand-text-lineHeight-100);
53
+ font-family: var(--brand-fontStack-monospace);
54
+ color: var(--brand-color-error-fg);
55
+ }
56
+
57
+ .colorModeMenu {
58
+ display: inline-block;
59
+ position: absolute;
60
+ top: var(--base-size-16);
61
+ right: var(--base-size-2);
62
+ z-index: 19;
63
+ width: 200px;
64
+ }
65
+
66
+ .colorModeButtonActive {
67
+ background-color: var(--brand-color-canvas-subtle);
68
+ }
@@ -0,0 +1,101 @@
1
+ 'use client'
2
+ import React, {PropsWithChildren, useCallback, useState} from 'react'
3
+ import clsx from 'clsx'
4
+ import {LiveProvider, LiveEditor, LiveError, LivePreview} from 'react-live'
5
+ import {useColorMode} from '../../context/color-modes/useColorMode'
6
+ import styles from './ReactCodeBlock.module.css'
7
+ import {ActionBar, Button, ThemeProvider} from '@primer/react'
8
+ import {CopyIcon, MoonIcon, SunIcon, UndoIcon} from '@primer/octicons-react'
9
+ import {colorModes} from '../../context/color-modes/context'
10
+
11
+ import {lightTheme, darkTheme} from './syntax-highlighting-themes'
12
+
13
+ type ReactCodeBlockProps = {
14
+ 'data-language': string
15
+ 'data-filename'?: string
16
+ jsxScope: Record<string, unknown>
17
+ } & PropsWithChildren<HTMLElement>
18
+
19
+ export function ReactCodeBlock(props: ReactCodeBlockProps) {
20
+ const {colorMode, setColorMode} = useColorMode()
21
+ const initialCode = getCodeFromChildren(props.children)
22
+ const [code, setCode] = useState(initialCode)
23
+ const shouldShowPreview = ['tsx', 'jsx'].includes(props['data-language'])
24
+
25
+ const handleReset = useCallback(() => {
26
+ setCode(initialCode)
27
+ }, [initialCode, setCode])
28
+
29
+ const handleCopy = useCallback(() => {
30
+ navigator.clipboard.writeText(code)
31
+ }, [code])
32
+
33
+ const noInline = props['data-filename'] === 'noinline' || false
34
+
35
+ return (
36
+ <>
37
+ <LiveProvider code={code} scope={props.jsxScope} noInline={noInline}>
38
+ <div className={clsx(styles.CodeBlock, 'custom-component')}>
39
+ {shouldShowPreview && (
40
+ <div>
41
+ <div className={styles.colorModeMenu}>
42
+ <ActionBar aria-label="Toolbar">
43
+ {colorModes.map((mode, index) => {
44
+ const Icon = mode === 'light' ? SunIcon : MoonIcon
45
+ return (
46
+ <ActionBar.IconButton
47
+ className={clsx(styles.colorModeButton, colorMode === mode && styles.colorModeButtonActive)}
48
+ key={`color-mode-${mode}-${index}`}
49
+ icon={Icon}
50
+ aria-label={mode}
51
+ onClick={() => setColorMode(mode)}
52
+ />
53
+ )
54
+ })}
55
+ </ActionBar>
56
+ </div>
57
+ <ThemeProvider colorMode={colorMode}>
58
+ <div className="custom-component">
59
+ <LivePreview className={styles.Preview} />
60
+ </div>
61
+ </ThemeProvider>
62
+ </div>
63
+ )}
64
+ <div className={styles.Toolbar}>
65
+ <Button size="small" leadingVisual={CopyIcon} onClick={handleCopy}>
66
+ Copy
67
+ </Button>
68
+ <Button size="small" leadingVisual={UndoIcon} onClick={handleReset}>
69
+ Reset
70
+ </Button>
71
+ </div>
72
+ <div className={styles.Editor}>
73
+ <LiveEditor theme={colorMode === 'light' ? lightTheme : darkTheme} onChange={setCode} />
74
+ </div>
75
+ {shouldShowPreview && (
76
+ <div className={styles.Error}>
77
+ <LiveError />
78
+ </div>
79
+ )}
80
+ </div>
81
+ </LiveProvider>
82
+ </>
83
+ )
84
+ }
85
+
86
+ /**
87
+ * Helper function to turn Nextra <code> children into plain text
88
+ */
89
+ function getCodeFromChildren(children: React.ReactNode) {
90
+ if (!React.isValidElement(children) || !children.props?.children) return ''
91
+
92
+ // Flattens the nested spans and combine their text content if it's a react child
93
+ const extractText = (node: React.ReactNode): string => {
94
+ if (typeof node === 'string') return node
95
+ if (Array.isArray(node)) return node.map(extractText).join('')
96
+ if (React.isValidElement(node) && node.props?.children) return extractText(node.props.children)
97
+ return ''
98
+ }
99
+
100
+ return extractText(children)
101
+ }
@@ -0,0 +1,55 @@
1
+ export const lightTheme = {
2
+ plain: {
3
+ backgroundColor: '#ffffff',
4
+ color: '#24292e',
5
+ },
6
+ styles: [
7
+ {
8
+ types: ['comment'],
9
+ style: {
10
+ color: '#6a737d',
11
+ fontStyle: 'italic' as const,
12
+ },
13
+ },
14
+ {
15
+ types: ['string', 'number', 'builtin', 'variable'],
16
+ style: {
17
+ color: '#032f62',
18
+ },
19
+ },
20
+ {
21
+ types: ['class-name', 'function', 'tag', 'attr-name'],
22
+ style: {
23
+ color: '#005CC5',
24
+ },
25
+ },
26
+ ],
27
+ }
28
+
29
+ export const darkTheme = {
30
+ plain: {
31
+ backgroundColor: '#0d1117',
32
+ color: '#c9d1d9',
33
+ },
34
+ styles: [
35
+ {
36
+ types: ['comment'],
37
+ style: {
38
+ color: '#8b949e',
39
+ fontStyle: 'italic' as const,
40
+ },
41
+ },
42
+ {
43
+ types: ['string', 'number', 'builtin', 'variable'],
44
+ style: {
45
+ color: '#a5d6ff',
46
+ },
47
+ },
48
+ {
49
+ types: ['class-name', 'function', 'tag', 'attr-name'],
50
+ style: {
51
+ color: '#d2a8ff',
52
+ },
53
+ },
54
+ ],
55
+ }
@@ -6,6 +6,7 @@
6
6
  padding: var(--base-size-20) var(--base-size-12) var(--base-size-20) var(--base-size-16);
7
7
  border-bottom: var(--brand-borderWidth-thin) solid var(--brand-color-border-default);
8
8
  background: var(--brand-color-canvas-default);
9
+ z-index: 20;
9
10
  }
10
11
 
11
12
  @media (max-width: 768px) {
@@ -3,7 +3,7 @@ import {MarkGithubIcon, MoonIcon, SearchIcon, SunIcon, ThreeBarsIcon, XIcon} fro
3
3
  import {Box, FormControl, IconButton, TextInput} from '@primer/react'
4
4
  import {Heading, Stack, Text} from '@primer/react-brand'
5
5
  import {clsx} from 'clsx'
6
- import {MdxFile, Folder, PageMapItem} from 'nextra'
6
+ import {MdxFile, PageMapItem} from 'nextra'
7
7
  import {debounce} from 'lodash'
8
8
 
9
9
  import Link from 'next/link'
@@ -11,11 +11,11 @@ import styles from './Header.module.css'
11
11
  import {NavDrawer} from '../nav-drawer/NavDrawer'
12
12
  import {useNavDrawerState} from '../nav-drawer/useNavDrawerState'
13
13
  import {useColorMode} from '../../context/color-modes/useColorMode'
14
- import {hasChildren} from '../../../helpers/hasChildren'
15
14
  import {DocsItem} from '../../../types'
16
15
 
17
16
  type HeaderProps = {
18
17
  pageMap: PageMapItem[]
18
+ flatDocsDirectories: DocsItem[]
19
19
  siteTitle: string
20
20
  }
21
21
 
@@ -25,7 +25,7 @@ type SearchResults = {
25
25
  url: string
26
26
  }
27
27
 
28
- export function Header({pageMap, siteTitle}: HeaderProps) {
28
+ export function Header({pageMap, siteTitle, flatDocsDirectories}: HeaderProps) {
29
29
  const {colorMode, setColorMode} = useColorMode()
30
30
  const inputRef = useRef<HTMLInputElement | null>(null)
31
31
  const searchResultsRef = useRef<HTMLElement | null>(null)
@@ -74,32 +74,30 @@ export function Header({pageMap, siteTitle}: HeaderProps) {
74
74
  }, [colorMode])
75
75
 
76
76
  const setSearchResultsDebounced = debounce((data: SearchResults[] | undefined) => setSearchResults(data), 1000)
77
-
78
77
  const searchData = useMemo(
79
78
  () =>
80
- pageMap
79
+ flatDocsDirectories
81
80
  .map(item => {
82
- if (hasChildren(item)) {
83
- return (item as Folder).children.filter(child => !hasChildren(child))
84
- }
85
- if ((item as DocsItem).type === 'doc') {
86
- return item
87
- }
81
+ if (item.route === '/') return null // remove homepage
82
+ return item
88
83
  })
89
- .flat()
90
84
  .filter(Boolean)
91
85
  .map(item => {
92
86
  const {frontMatter, route} = item as MdxFile
93
-
94
87
  if (!frontMatter) return null
95
88
  const result = {
96
- title: frontMatter.title ? frontMatter.title : '',
89
+ title:
90
+ frontMatter['show-tabs'] && frontMatter['tab-label']
91
+ ? `${frontMatter.title} | ${frontMatter['tab-label']}`
92
+ : frontMatter.title
93
+ ? frontMatter.title
94
+ : '',
97
95
  description: frontMatter.description ? frontMatter.description : '',
98
96
  url: route,
99
97
  }
100
98
  return result
101
99
  }),
102
- [pageMap],
100
+ [flatDocsDirectories],
103
101
  )
104
102
 
105
103
  const handleChange = () => {
@@ -11,7 +11,13 @@ type IndexCardsProps = {
11
11
  }
12
12
 
13
13
  export function IndexCards({route, folderData}: IndexCardsProps) {
14
- const filteredData = folderData.filter(item => item.type === 'doc' && item.route.includes(`${route}/`))
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
+ )
18
+
19
+ const filteredData = onlyDirectChildren.filter(item => item.type === 'doc' && item.route.includes(`${route}/`))
20
+
15
21
  return (
16
22
  <Stack direction="vertical" padding="none" gap="spacious">
17
23
  {filteredData.map((item: DocsItem) => {