diff --git a/.gitignore b/.gitignore index 168167c..e212b7f 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ out/ npm-debug.log* yarn-debug.log* yarn-error.log* +*.tgz \ No newline at end of file diff --git a/packages/theme/components/context/color-modes/ColorModeProvider.tsx b/packages/theme/components/context/color-modes/ColorModeProvider.tsx index ea3b313..ae32ba9 100644 --- a/packages/theme/components/context/color-modes/ColorModeProvider.tsx +++ b/packages/theme/components/context/color-modes/ColorModeProvider.tsx @@ -9,6 +9,8 @@ const ColorModeProvider = ({children}) => { const savedMode = window.localStorage.getItem('doctocat-active-color-mode') if (savedMode && (savedMode === 'light' || savedMode === 'dark')) { setColorMode(savedMode) + } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { + setColorMode('dark') } } }, []) diff --git a/packages/theme/components/layout/header/Header.module.css b/packages/theme/components/layout/header/Header.module.css index 0791d29..698a49c 100644 --- a/packages/theme/components/layout/header/Header.module.css +++ b/packages/theme/components/layout/header/Header.module.css @@ -54,6 +54,10 @@ } @media (min-width: 768px) { + .Header { + padding: var(--base-size-20) var(--base-size-12) var(--base-size-20) var(--base-size-24); + } + .Header__searchArea { width: 100%; max-width: 350px; diff --git a/packages/theme/components/layout/header/Header.tsx b/packages/theme/components/layout/header/Header.tsx index cc30717..14d0f7c 100644 --- a/packages/theme/components/layout/header/Header.tsx +++ b/packages/theme/components/layout/header/Header.tsx @@ -5,6 +5,7 @@ import clsx from 'clsx' import {MdxFile, PageMapItem} from 'nextra' import type {PageItem} from 'nextra/normalize-pages' import React, {useEffect, useMemo} from 'react' +import {debounce} from 'lodash' import Link from 'next/link' import styles from './Header.module.css' @@ -67,6 +68,8 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) { document.documentElement.setAttribute('data-color-mode', colorMode) }, [colorMode]) + const setSearchResultsDebounced = debounce(data => setSearchResults(data), 1000) + const searchData = useMemo( () => pageMap @@ -102,34 +105,34 @@ export function Header({pageMap, docsDirectories, siteTitle}: HeaderProps) { } if (inputRef.current.value.length > 2) { const curSearchTerm = inputRef.current.value.toLowerCase() - const results: SearchResults[] = searchData + + // filters the frontMatter title and descriptions against the search term + const filteredData = searchData .filter((data): data is SearchResults => data !== null) .filter(data => { if (!data.title) return false const title = data.title.toLowerCase() const description = data.description.toLowerCase() - let searchIndex = 0 - for (let i = 0; i < title.length; i++) { - if (title[i] === curSearchTerm[searchIndex]) { - searchIndex++ - if (searchIndex === curSearchTerm.length) { - return true - } - } - } - searchIndex = 0 - for (let i = 0; i < description.length; i++) { - if (description[i] === curSearchTerm[searchIndex]) { - searchIndex++ - if (searchIndex === curSearchTerm.length) { - return true - } - } - } - return false + return title.includes(curSearchTerm) || description.includes(curSearchTerm) }) - .filter(Boolean) - setTimeout(() => setSearchResults(results), 1000) + + // sorts the data to show hits in title first, description second + const sortedData = filteredData.sort((a, b) => { + const aTitle = a.title.toLowerCase() + const bTitle = b.title.toLowerCase() + const aIncludes = aTitle.includes(curSearchTerm) + const bIncludes = bTitle.includes(curSearchTerm) + + if (aIncludes && !bIncludes) { + return -1 + } else if (!aIncludes && bIncludes) { + return 1 + } else { + return 0 + } + }) + + setSearchResultsDebounced(sortedData) setSearchTerm(inputRef.current.value) setIsSearchResultOpen(true) diff --git a/packages/theme/components/layout/index-cards/IndexCards.module.css b/packages/theme/components/layout/index-cards/IndexCards.module.css index 562f139..3075d6b 100644 --- a/packages/theme/components/layout/index-cards/IndexCards.module.css +++ b/packages/theme/components/layout/index-cards/IndexCards.module.css @@ -1,3 +1,5 @@ .IndexCards { + --brand-Grid-spacing-margin: 0; --brand-Card-maxWidth: 100%; + --brand-Grid-spacing-row: var(--brand-Grid-spacing-column-gap); } diff --git a/packages/theme/components/layout/root-layout/Theme.tsx b/packages/theme/components/layout/root-layout/Theme.tsx index 4507437..f312eeb 100644 --- a/packages/theme/components/layout/root-layout/Theme.tsx +++ b/packages/theme/components/layout/root-layout/Theme.tsx @@ -52,8 +52,7 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) { const {siteTitle} = publicRuntimeConfig const isHomePage = route === '/' - const isIndexPage = filePath.endsWith('index.mdx') && !isHomePage && !frontMatter['show-tabs'] - + const isIndexPage = /index\.mdx?$/.test(filePath) && !isHomePage && !frontMatter['show-tabs'] const data = !isHomePage && activePath[activePath.length - 2] const filteredTabData: MdxFile[] = data && data.kind === 'Folder' @@ -143,9 +142,11 @@ export function Theme({children, pageOpts}: NextraThemeLayoutProps) { {frontMatter['action-1-text'] && ( - + {frontMatter['action-2-text'] && ( - )} diff --git a/packages/theme/components/layout/sidebar/Sidebar.module.css b/packages/theme/components/layout/sidebar/Sidebar.module.css index c70a1ea..596386b 100644 --- a/packages/theme/components/layout/sidebar/Sidebar.module.css +++ b/packages/theme/components/layout/sidebar/Sidebar.module.css @@ -1,3 +1,9 @@ +@media (min-width: 768px) { + .Sidebar { + padding-inline-start: var(--base-size-8); + } +} + .NavList h3 { font-weight: normal; text-transform: uppercase; diff --git a/packages/theme/components/layout/sidebar/Sidebar.tsx b/packages/theme/components/layout/sidebar/Sidebar.tsx index f5051ee..528c642 100644 --- a/packages/theme/components/layout/sidebar/Sidebar.tsx +++ b/packages/theme/components/layout/sidebar/Sidebar.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useMemo} from 'react' import {NavList} from '@primer/react' import {Folder, MdxFile, PageMapItem} from 'nextra' import {useRouter} from 'next/router' @@ -64,102 +64,126 @@ export function Sidebar({pageMap}: SidebarProps) { const {sidebarLinks}: ThemeConfig = publicRuntimeConfig - return ( - - {pageMap.map(item => { - if (item.kind === 'MdxPage' && item.route === '/') return null - - if (item.kind === 'MdxPage') { - return ( - - {item.frontMatter?.title ?? item.name} - - ) + /** + * Sorts the incoming data so that folders with a menu-position frontmatter value + * are sorted to the top of the list. If a folder does not have a menu-position + * value, it is sorted to the bottom of the list. + */ + const reorderedPageMap = useMemo( + () => + [...pageMap].sort((a, b) => { + if (a.kind === 'Folder' && a.children) { + const aIndex = a.children.find(child => child.name === 'index' && child.kind === 'MdxPage') + const aPosition = (aIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity + if (b.kind === 'Folder' && b.children) { + const bIndex = b.children.find(child => child.name === 'index' && child.kind === 'MdxPage') + const bPosition = (bIndex as MdxFile | undefined)?.frontMatter?.['menu-position'] ?? Infinity + return aPosition - bPosition + } } + return 0 + }), + [pageMap], + ) + + return ( +
+ + {reorderedPageMap.map(item => { + if (item.kind === 'MdxPage' && item.route === '/') return null - // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition - if (item.kind === 'Folder') { - const indexPage = (item as Folder).children.find( - child => child.kind === 'MdxPage' && child.name === 'index', - ) as MdxFile - const subNavName = indexPage.frontMatter?.title ?? '' - const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false - if (shouldShowTabs) { + if (item.kind === 'MdxPage') { return ( - - {(indexPage as MdxFile).frontMatter?.title || item.name} + + {item.frontMatter?.title ?? item.name} ) } - return ( - - {item.children && - item.children - .sort((a, b) => (a.name === 'index' ? -1 : b.name === 'index' ? 1 : 0)) // puts index page first - .filter( - child => - (child as DocsItem).name !== 'index' || - ((child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs']) ?? false), - ) // only show index page if it has show-tabs - .map((child: DocsItem) => { - if (child.kind === 'MdxPage') { - return ( - - {(child as MdxFile).frontMatter?.title || item.name} - - ) - } - - if ((child as DocsItem).kind === 'Folder') { - const landingPageItem: PageMapItem | undefined = (child as Folder).children.find( - innerChild => innerChild.kind === 'MdxPage' && innerChild.name === 'index', - ) - return ( - - {(landingPageItem as MdxFile).frontMatter?.title || item.name} - - ) - } - - return null - })} - - ) - } - - return null - })} - {sidebarLinks && sidebarLinks.length > 0 && ( - - {sidebarLinks.map(link => { - const {leadingIcon} = link - const isExternalUrl = link.href.startsWith('http') - const LeadingIcon = getOcticonForType(leadingIcon) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (item.kind === 'Folder') { + const indexPage = (item as Folder).children.find( + child => child.kind === 'MdxPage' && child.name === 'index', + ) as MdxFile + const subNavName = indexPage.frontMatter?.title ?? '' + const shouldShowTabs = indexPage.frontMatter?.['show-tabs'] ?? false + if (shouldShowTabs) { + return ( + + {(indexPage as MdxFile).frontMatter?.title || item.name} + + ) + } return ( - - {LeadingIcon && } - {link.title} - {isExternalUrl && ( - - - - )} - + + {item.children && + item.children + .sort((a, b) => (a.name === 'index' ? -1 : b.name === 'index' ? 1 : 0)) // puts index page first + .filter( + child => + (child as DocsItem).name !== 'index' || + ((child.name === 'index' && (child as MdxFile).frontMatter?.['show-tabs']) ?? false), + ) // only show index page if it has show-tabs + .map((child: DocsItem) => { + if (child.kind === 'MdxPage') { + return ( + + {(child as MdxFile).frontMatter?.title || item.name} + + ) + } + + if ((child as DocsItem).kind === 'Folder') { + const landingPageItem: PageMapItem | undefined = (child as Folder).children.find( + innerChild => innerChild.kind === 'MdxPage' && innerChild.name === 'index', + ) + return ( + + {(landingPageItem as MdxFile).frontMatter?.title || item.name} + + ) + } + + return null + })} + ) - })} - - )} - + } + + return null + })} + {sidebarLinks && sidebarLinks.length > 0 && ( + + {sidebarLinks.map(link => { + const {leadingIcon} = link + const isExternalUrl = link.href.startsWith('http') + const LeadingIcon = getOcticonForType(leadingIcon) + + return ( + + {LeadingIcon && } + {link.title} + {isExternalUrl && ( + + + + )} + + ) + })} + + )} + +
) }