From 7758592a653aa6e8033300f97e72d53c279a9277 Mon Sep 17 00:00:00 2001 From: zeke <40004347+KAJdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:22:26 -0800 Subject: [PATCH 1/4] feat: navbar redesign --- docusaurus.config.js | 13 +++- package.json | 1 + src/css/custom.css | 50 +++++++++++--- src/theme/Layout.js | 21 ++++++ src/theme/Logo/index.js | 65 ++++++++++++++++++ src/theme/Navbar/Content/index.js | 77 ++++++++++++++++++++++ src/theme/Navbar/Content/styles.module.css | 8 +++ src/theme/Navbar/Search/index.js | 10 +++ src/theme/Navbar/Search/styles.module.css | 18 +++++ src/theme/NavbarItem/SearchNavbarItem.js | 13 ++++ 10 files changed, 264 insertions(+), 12 deletions(-) create mode 100644 src/theme/Layout.js create mode 100644 src/theme/Logo/index.js create mode 100644 src/theme/Navbar/Content/index.js create mode 100644 src/theme/Navbar/Content/styles.module.css create mode 100644 src/theme/Navbar/Search/index.js create mode 100644 src/theme/Navbar/Search/styles.module.css create mode 100644 src/theme/NavbarItem/SearchNavbarItem.js diff --git a/docusaurus.config.js b/docusaurus.config.js index b3e088a..d70d2e3 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -14,7 +14,8 @@ import { themes as prismThemes } from "prism-react-renderer"; /** @type {import('@docusaurus/types').Config} */ const config = { title: "RunPod Documentation", - tagline: "Globally distributed GPU cloud built for production. Develop, train, and scale AI applications.", + tagline: + "Globally distributed GPU cloud built for production. Develop, train, and scale AI applications.", favicon: "img/favicon.ico", url: "https://docs.runpod.io", baseUrl: "/", @@ -105,6 +106,7 @@ const config = { logo: { alt: "RunPod Logo", src: "img/logo.svg", + target: "_self", }, items: [ { @@ -123,7 +125,11 @@ const config = { }, { href: "https://blog.runpod.io", label: "Blog", position: "left" }, - { href: "https://www.runpod.io/console/signup", label: "Sign up", position: "left" }, + { + href: "https://www.runpod.io/console/signup", + label: "Sign up", + position: "left", + }, { href: "https://github.com/runpod", label: "GitHub", @@ -214,7 +220,8 @@ const config = { "data-website-id": "d8e25089-cadd-4c1c-9010-7e83cd99a2a5", "data-project-name": "RunPod", "data-project-color": "#070D27", - "data-project-logo": "https://avatars.githubusercontent.com/u/95939477?s=200&v=4", + "data-project-logo": + "https://avatars.githubusercontent.com/u/95939477?s=200&v=4", async: true, }, { diff --git a/package.json b/package.json index 92ff599..349aba2 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", + "dev": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", diff --git a/src/css/custom.css b/src/css/custom.css index 00d6161..77e419a 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -15,16 +15,48 @@ --ifm-color-primary-lightest: #a87deb; /* Lightest tint */ --ifm-code-font-size: 95%; /* Adjusted code font size */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); /* Highlighted code line background in light mode */ + --ifm-font-family-base: 'Inter', sans-serif; /* Base font family */ + font-family: var(--ifm-font-family-base) !important; } /* For readability concerns, you should choose a lighter palette in dark mode. */ -[data-theme='dark'] { - --ifm-color-primary: #a382ec; /* Primary color for dark mode */ - --ifm-color-primary-dark: #9775e3; /* Dark shade for dark mode */ - --ifm-color-primary-darker: #8c68db; /* Darker shade for dark mode */ - --ifm-color-primary-darkest: #7a59c9; /* Darkest shade for dark mode */ - --ifm-color-primary-light: #ab8eed; /* Light tint for dark mode */ - --ifm-color-primary-lighter: #b39bf0; /* Lighter tint for dark mode */ - --ifm-color-primary-lightest: #beb0f3; /* Lightest tint for dark mode */ - --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); /* Highlighted code line background in dark mode */ +/* dark mode */ +[data-theme='dark']:root { + --ifm-color-primary: #a382ec; /* Primary color for dark mode */ + --ifm-color-primary-dark: #9775e3; /* Dark shade for dark mode */ + --ifm-color-primary-darker: #8c68db; /* Darker shade for dark mode */ + --ifm-color-primary-darkest: #7a59c9; /* Darkest shade for dark mode */ + --ifm-color-primary-light: #ab8eed; /* Light tint for dark mode */ + --ifm-color-primary-lighter: #b39bf0; /* Lighter tint for dark mode */ + --ifm-color-primary-lightest: #beb0f3; /* Lightest tint for dark mode */ + --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); /* Highlighted code line background in dark mode */ + --ifm-navbar-background-color: #000; } + +body { + font-family: var(--ifm-font-family-base) !important; +} + +.navbar--fixed-top { + padding-left: 36px; + padding-right: 36px; + border-bottom: 1px solid var(--ifm-toc-border-color); + box-shadow: none; + font-size: 14px; + font-family: "Inter", sans-serif; +} + +.navbar__brand:has(.navbar__title) { + margin-left: 30px; +} + +.navbar__items { + --ifm-navbar-item-padding-horizontal: 20px; +} + +@media (max-width: 768px) { + .navbar--fixed-top { + padding-left: 25px; + padding-right: 20px; + } +} \ No newline at end of file diff --git a/src/theme/Layout.js b/src/theme/Layout.js new file mode 100644 index 0000000..cd22fa8 --- /dev/null +++ b/src/theme/Layout.js @@ -0,0 +1,21 @@ +import React from "react"; +import OriginalLayout from "@theme-original/Layout"; +import Head from "@docusaurus/Head"; +import { useLocation } from "@docusaurus/router"; + +export default function Layout(props) { + const location = useLocation(); + return ( + <> + + + + + + + + ); +} diff --git a/src/theme/Logo/index.js b/src/theme/Logo/index.js new file mode 100644 index 0000000..631c210 --- /dev/null +++ b/src/theme/Logo/index.js @@ -0,0 +1,65 @@ +import React from "react"; +import Link from "@docusaurus/Link"; +import useBaseUrl from "@docusaurus/useBaseUrl"; +import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; +import { useThemeConfig } from "@docusaurus/theme-common"; +import ThemedImage from "@theme/ThemedImage"; +function LogoThemedImage({ logo, alt, imageClassName }) { + const sources = { + light: useBaseUrl(logo.src), + dark: useBaseUrl(logo.srcDark || logo.src), + }; + const themedImage = ( + + ); + // Is this extra div really necessary? + // introduced in https://github.com/facebook/docusaurus/pull/5666 + return imageClassName ? ( +
{themedImage}
+ ) : ( + themedImage + ); +} +export default function Logo(props) { + const { + siteConfig: { title }, + } = useDocusaurusContext(); + const { + navbar: { title: navbarTitle, logo }, + } = useThemeConfig(); + const { imageClassName, titleClassName, ...propsRest } = props; + const logoLink = useBaseUrl(logo?.href || "/"); + // If visible title is shown, fallback alt text should be + // an empty string to mark the logo as decorative. + const fallbackAlt = navbarTitle ? "" : title; + // Use logo alt text if provided (including empty string), + // and provide a sensible fallback otherwise. + const alt = logo?.alt ?? fallbackAlt; + return ( + <> + {logo && ( + + + + )} + + {navbarTitle != null && {navbarTitle}} + + + ); +} diff --git a/src/theme/Navbar/Content/index.js b/src/theme/Navbar/Content/index.js new file mode 100644 index 0000000..122e2fb --- /dev/null +++ b/src/theme/Navbar/Content/index.js @@ -0,0 +1,77 @@ +import React from "react"; +import { useThemeConfig, ErrorCauseBoundary } from "@docusaurus/theme-common"; +import { + splitNavbarItems, + useNavbarMobileSidebar, +} from "@docusaurus/theme-common/internal"; +import NavbarItem from "@theme/NavbarItem"; +import NavbarColorModeToggle from "@theme/Navbar/ColorModeToggle"; +import SearchBar from "@theme/SearchBar"; +import NavbarMobileSidebarToggle from "@theme/Navbar/MobileSidebar/Toggle"; +import NavbarLogo from "@theme/Navbar/Logo"; +import NavbarSearch from "@theme/Navbar/Search"; +import styles from "./styles.module.css"; +function useNavbarItems() { + // TODO temporary casting until ThemeConfig type is improved + return useThemeConfig().navbar.items; +} +function NavbarItems({ items }) { + return ( + <> + {items.map((item, i) => ( + + new Error( + `A theme navbar item failed to render. +Please double-check the following navbar item (themeConfig.navbar.items) of your Docusaurus config: +${JSON.stringify(item, null, 2)}`, + { cause: error } + ) + } + > + + + ))} + + ); +} +function NavbarContentLayout({ left, right }) { + return ( +
+
{left}
+
{right}
+
+ ); +} +export default function NavbarContent() { + const mobileSidebar = useNavbarMobileSidebar(); + const items = useNavbarItems(); + const [leftItems, rightItems] = splitNavbarItems(items); + const searchBarItem = items.find((item) => item.type === "search"); + return ( + + + + + } + right={ + // TODO stop hardcoding items? + // Ask the user to add the respective navbar items => more flexible + <> + + + {!searchBarItem && ( + + + + )} + {!mobileSidebar.disabled && } + + } + /> + ); +} diff --git a/src/theme/Navbar/Content/styles.module.css b/src/theme/Navbar/Content/styles.module.css new file mode 100644 index 0000000..b4bda8c --- /dev/null +++ b/src/theme/Navbar/Content/styles.module.css @@ -0,0 +1,8 @@ +/* +Hide color mode toggle in small viewports + */ +@media (max-width: 768px) { + .colorModeToggle { + display: none; + } +} diff --git a/src/theme/Navbar/Search/index.js b/src/theme/Navbar/Search/index.js new file mode 100644 index 0000000..686f73b --- /dev/null +++ b/src/theme/Navbar/Search/index.js @@ -0,0 +1,10 @@ +import React from 'react'; +import clsx from 'clsx'; +import styles from './styles.module.css'; +export default function NavbarSearch({children, className}) { + return ( +
+ {children} +
+ ); +} diff --git a/src/theme/Navbar/Search/styles.module.css b/src/theme/Navbar/Search/styles.module.css new file mode 100644 index 0000000..f1a731c --- /dev/null +++ b/src/theme/Navbar/Search/styles.module.css @@ -0,0 +1,18 @@ +/* +Workaround to avoid rendering empty search container +See https://github.com/facebook/docusaurus/pull/9385 +*/ +.navbarSearchContainer:empty { + display: none; +} + +@media (max-width: 996px) { + .navbarSearchContainer { + position: relative; + margin-right: 20px; + } +} + +.navbarSearchContainer { + margin-left: 20px; +} \ No newline at end of file diff --git a/src/theme/NavbarItem/SearchNavbarItem.js b/src/theme/NavbarItem/SearchNavbarItem.js new file mode 100644 index 0000000..9cf4462 --- /dev/null +++ b/src/theme/NavbarItem/SearchNavbarItem.js @@ -0,0 +1,13 @@ +import React from 'react'; +import SearchBar from '@theme/SearchBar'; +import NavbarSearch from '@theme/Navbar/Search'; +export default function SearchNavbarItem({mobile, className}) { + if (mobile) { + return null; + } + return ( + + + + ); +} From 04b9006a89b2226e7b84bc6aaf90f81ca566b3bb Mon Sep 17 00:00:00 2001 From: zeke <40004347+KAJdev@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:26:10 -0800 Subject: [PATCH 2/4] fix --- src/theme/Logo/index.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/theme/Logo/index.js b/src/theme/Logo/index.js index 631c210..9507440 100644 --- a/src/theme/Logo/index.js +++ b/src/theme/Logo/index.js @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useLayoutEffect } from "react"; import Link from "@docusaurus/Link"; import useBaseUrl from "@docusaurus/useBaseUrl"; import useDocusaurusContext from "@docusaurus/useDocusaurusContext"; @@ -27,6 +27,7 @@ function LogoThemedImage({ logo, alt, imageClassName }) { themedImage ); } + export default function Logo(props) { const { siteConfig: { title }, @@ -35,18 +36,23 @@ export default function Logo(props) { navbar: { title: navbarTitle, logo }, } = useThemeConfig(); const { imageClassName, titleClassName, ...propsRest } = props; - const logoLink = useBaseUrl(logo?.href || "/"); // If visible title is shown, fallback alt text should be // an empty string to mark the logo as decorative. const fallbackAlt = navbarTitle ? "" : title; // Use logo alt text if provided (including empty string), // and provide a sensible fallback otherwise. const alt = logo?.alt ?? fallbackAlt; + + const [logoLink, setLogoLink] = React.useState(logo?.href || "/"); + useLayoutEffect(() => { + setLogoLink(window.location.origin); + }, []); + return ( <> {logo && ( From 9ca0582137ecc88da3525ffad56d33c6d22b64f7 Mon Sep 17 00:00:00 2001 From: zeke <40004347+KAJdev@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:41:51 -0800 Subject: [PATCH 3/4] update logo, better responsiveness, bigger text --- docusaurus.config.js | 6 +- src/css/custom.css | 18 +- src/pages/index.module.css | 2 +- .../Navbar/MobileSidebar/Toggle/index.tsx | 23 +++ src/theme/NavbarItem/ComponentTypes.tsx | 25 +++ src/theme/NavbarItem/DefaultNavbarItem.tsx | 59 ++++++ src/theme/NavbarItem/DocNavbarItem.tsx | 34 ++++ src/theme/NavbarItem/DocSidebarNavbarItem.tsx | 29 +++ .../DocsVersionDropdownNavbarItem.tsx | 92 +++++++++ .../NavbarItem/DocsVersionNavbarItem.tsx | 20 ++ .../NavbarItem/DropdownNavbarItem/index.tsx | 176 ++++++++++++++++++ .../DropdownNavbarItem/styles.module.css | 3 + src/theme/NavbarItem/HtmlNavbarItem.tsx | 25 +++ .../LocaleDropdownNavbarItem/index.tsx | 76 ++++++++ .../styles.module.css | 4 + src/theme/NavbarItem/NavbarNavLink.tsx | 67 +++++++ src/theme/NavbarItem/SearchNavbarItem.tsx | 19 ++ src/theme/NavbarItem/index.tsx | 21 +++ static/img/icon.svg | 4 + static/img/logoDarkMode.svg | 5 + static/img/logoLightMode.svg | 5 + 21 files changed, 707 insertions(+), 6 deletions(-) create mode 100644 src/theme/Navbar/MobileSidebar/Toggle/index.tsx create mode 100644 src/theme/NavbarItem/ComponentTypes.tsx create mode 100644 src/theme/NavbarItem/DefaultNavbarItem.tsx create mode 100644 src/theme/NavbarItem/DocNavbarItem.tsx create mode 100644 src/theme/NavbarItem/DocSidebarNavbarItem.tsx create mode 100644 src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx create mode 100644 src/theme/NavbarItem/DocsVersionNavbarItem.tsx create mode 100644 src/theme/NavbarItem/DropdownNavbarItem/index.tsx create mode 100644 src/theme/NavbarItem/DropdownNavbarItem/styles.module.css create mode 100644 src/theme/NavbarItem/HtmlNavbarItem.tsx create mode 100644 src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx create mode 100644 src/theme/NavbarItem/LocaleDropdownNavbarItem/styles.module.css create mode 100644 src/theme/NavbarItem/NavbarNavLink.tsx create mode 100644 src/theme/NavbarItem/SearchNavbarItem.tsx create mode 100644 src/theme/NavbarItem/index.tsx create mode 100644 static/img/icon.svg create mode 100644 static/img/logoDarkMode.svg create mode 100644 static/img/logoLightMode.svg diff --git a/docusaurus.config.js b/docusaurus.config.js index d70d2e3..657534c 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -101,12 +101,14 @@ const config = { }, image: "img/docusaurus-social-card.png", navbar: { - title: "RunPod", + title: "Docs Home", logo: { alt: "RunPod Logo", - src: "img/logo.svg", + src: "img/logoLightMode.svg", + srcDark: "img/logoDarkMode.svg", target: "_self", + href: "https://www.runpod.io", }, items: [ { diff --git a/src/css/custom.css b/src/css/custom.css index 77e419a..f0bbc7a 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -42,7 +42,7 @@ body { padding-right: 36px; border-bottom: 1px solid var(--ifm-toc-border-color); box-shadow: none; - font-size: 14px; + font-size: 16px; font-family: "Inter", sans-serif; } @@ -54,9 +54,21 @@ body { --ifm-navbar-item-padding-horizontal: 20px; } -@media (max-width: 768px) { +@media (max-width: 1200px) { .navbar--fixed-top { - padding-left: 25px; + padding-left: 15px; padding-right: 20px; } +} + +@media screen and (max-width: 1199px) { + a[href*="github.com"] { + display: none; + } +} + +@media screen and (min-width: 1200px) { + a[href*="github.com"] { + display: inline; /* or whatever display property you want */ + } } \ No newline at end of file diff --git a/src/pages/index.module.css b/src/pages/index.module.css index fbe0a52..ff20137 100644 --- a/src/pages/index.module.css +++ b/src/pages/index.module.css @@ -1,6 +1,6 @@ /* General Styles */ body { - font-family: 'Arial', sans-serif; + font-family: 'Inter', sans-serif; margin: 0; } diff --git a/src/theme/Navbar/MobileSidebar/Toggle/index.tsx b/src/theme/Navbar/MobileSidebar/Toggle/index.tsx new file mode 100644 index 0000000..9691f10 --- /dev/null +++ b/src/theme/Navbar/MobileSidebar/Toggle/index.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import {useNavbarMobileSidebar} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import IconMenu from '@theme/Icon/Menu'; + +export default function MobileSidebarToggle(): JSX.Element { + const {toggle, shown} = useNavbarMobileSidebar(); + return ( + + ); +} diff --git a/src/theme/NavbarItem/ComponentTypes.tsx b/src/theme/NavbarItem/ComponentTypes.tsx new file mode 100644 index 0000000..df6a5f2 --- /dev/null +++ b/src/theme/NavbarItem/ComponentTypes.tsx @@ -0,0 +1,25 @@ +import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; +import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; +import LocaleDropdownNavbarItem from '@theme/NavbarItem/LocaleDropdownNavbarItem'; +import SearchNavbarItem from '@theme/NavbarItem/SearchNavbarItem'; +import HtmlNavbarItem from '@theme/NavbarItem/HtmlNavbarItem'; +import DocNavbarItem from '@theme/NavbarItem/DocNavbarItem'; +import DocSidebarNavbarItem from '@theme/NavbarItem/DocSidebarNavbarItem'; +import DocsVersionNavbarItem from '@theme/NavbarItem/DocsVersionNavbarItem'; +import DocsVersionDropdownNavbarItem from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; + +import type {ComponentTypesObject} from '@theme/NavbarItem/ComponentTypes'; + +const ComponentTypes: ComponentTypesObject = { + default: DefaultNavbarItem, + localeDropdown: LocaleDropdownNavbarItem, + search: SearchNavbarItem, + dropdown: DropdownNavbarItem, + html: HtmlNavbarItem, + doc: DocNavbarItem, + docSidebar: DocSidebarNavbarItem, + docsVersion: DocsVersionNavbarItem, + docsVersionDropdown: DocsVersionDropdownNavbarItem, +}; + +export default ComponentTypes; diff --git a/src/theme/NavbarItem/DefaultNavbarItem.tsx b/src/theme/NavbarItem/DefaultNavbarItem.tsx new file mode 100644 index 0000000..b63d971 --- /dev/null +++ b/src/theme/NavbarItem/DefaultNavbarItem.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import clsx from 'clsx'; +import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'; +import type { + DesktopOrMobileNavBarItemProps, + Props, +} from '@theme/NavbarItem/DefaultNavbarItem'; + +function DefaultNavbarItemDesktop({ + className, + isDropdownItem = false, + ...props +}: DesktopOrMobileNavBarItemProps) { + const element = ( + + ); + + if (isDropdownItem) { + return
  • {element}
  • ; + } + + return element; +} + +function DefaultNavbarItemMobile({ + className, + isDropdownItem, + ...props +}: DesktopOrMobileNavBarItemProps) { + return ( +
  • + +
  • + ); +} + +export default function DefaultNavbarItem({ + mobile = false, + position, // Need to destructure position from props so that it doesn't get passed on. + ...props +}: Props): JSX.Element { + const Comp = mobile ? DefaultNavbarItemMobile : DefaultNavbarItemDesktop; + return ( + + ); +} diff --git a/src/theme/NavbarItem/DocNavbarItem.tsx b/src/theme/NavbarItem/DocNavbarItem.tsx new file mode 100644 index 0000000..f391a21 --- /dev/null +++ b/src/theme/NavbarItem/DocNavbarItem.tsx @@ -0,0 +1,34 @@ +import React from 'react'; +import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client'; +import {useLayoutDoc} from '@docusaurus/theme-common/internal'; +import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; +import type {Props} from '@theme/NavbarItem/DocNavbarItem'; + +export default function DocNavbarItem({ + docId, + label: staticLabel, + docsPluginId, + ...props +}: Props): JSX.Element | null { + const {activeDoc} = useActiveDocContext(docsPluginId); + const doc = useLayoutDoc(docId, docsPluginId); + const pageActive = activeDoc?.path === doc?.path; + + // Draft and unlisted items are not displayed in the navbar. + if (doc === null || (doc.unlisted && !pageActive)) { + return null; + } + + return ( + + pageActive || + (!!activeDoc?.sidebar && activeDoc.sidebar === doc.sidebar) + } + label={staticLabel ?? doc.id} + to={doc.path} + /> + ); +} diff --git a/src/theme/NavbarItem/DocSidebarNavbarItem.tsx b/src/theme/NavbarItem/DocSidebarNavbarItem.tsx new file mode 100644 index 0000000..4c8245a --- /dev/null +++ b/src/theme/NavbarItem/DocSidebarNavbarItem.tsx @@ -0,0 +1,29 @@ +import React from 'react'; +import {useActiveDocContext} from '@docusaurus/plugin-content-docs/client'; +import {useLayoutDocsSidebar} from '@docusaurus/theme-common/internal'; +import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; +import type {Props} from '@theme/NavbarItem/DocSidebarNavbarItem'; + +export default function DocSidebarNavbarItem({ + sidebarId, + label, + docsPluginId, + ...props +}: Props): JSX.Element { + const {activeDoc} = useActiveDocContext(docsPluginId); + const sidebarLink = useLayoutDocsSidebar(sidebarId, docsPluginId).link; + if (!sidebarLink) { + throw new Error( + `DocSidebarNavbarItem: Sidebar with ID "${sidebarId}" doesn't have anything to be linked to.`, + ); + } + return ( + activeDoc?.sidebar === sidebarId} + label={label ?? sidebarLink.label} + to={sidebarLink.path} + /> + ); +} diff --git a/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx b/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx new file mode 100644 index 0000000..819223c --- /dev/null +++ b/src/theme/NavbarItem/DocsVersionDropdownNavbarItem.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import { + useVersions, + useActiveDocContext, +} from '@docusaurus/plugin-content-docs/client'; +import {useDocsPreferredVersion} from '@docusaurus/theme-common'; +import {useDocsVersionCandidates} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import {useLocation} from '@docusaurus/router'; +import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; +import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; +import type {Props} from '@theme/NavbarItem/DocsVersionDropdownNavbarItem'; +import type {GlobalVersion} from '@docusaurus/plugin-content-docs/client'; + +const getVersionMainDoc = (version: GlobalVersion) => + version.docs.find((doc) => doc.id === version.mainDocId)!; + +export default function DocsVersionDropdownNavbarItem({ + mobile, + docsPluginId, + dropdownActiveClassDisabled, + dropdownItemsBefore, + dropdownItemsAfter, + ...props +}: Props): JSX.Element { + const {search, hash} = useLocation(); + const activeDocContext = useActiveDocContext(docsPluginId); + const versions = useVersions(docsPluginId); + const {savePreferredVersionName} = useDocsPreferredVersion(docsPluginId); + const versionLinks = versions.map((version) => { + // We try to link to the same doc, in another version + // When not possible, fallback to the "main doc" of the version + const versionDoc = + activeDocContext.alternateDocVersions[version.name] ?? + getVersionMainDoc(version); + return { + label: version.label, + // preserve ?search#hash suffix on version switches + to: `${versionDoc.path}${search}${hash}`, + isActive: () => version === activeDocContext.activeVersion, + onClick: () => savePreferredVersionName(version.name), + }; + }); + const items = [ + ...dropdownItemsBefore, + ...versionLinks, + ...dropdownItemsAfter, + ]; + + const dropdownVersion = useDocsVersionCandidates(docsPluginId)[0]; + + // Mobile dropdown is handled a bit differently + const dropdownLabel = + mobile && items.length > 1 + ? translate({ + id: 'theme.navbar.mobileVersionsDropdown.label', + message: 'Versions', + description: + 'The label for the navbar versions dropdown on mobile view', + }) + : dropdownVersion.label; + const dropdownTo = + mobile && items.length > 1 + ? undefined + : getVersionMainDoc(dropdownVersion).path; + + // We don't want to render a version dropdown with 0 or 1 item. If we build + // the site with a single docs version (onlyIncludeVersions: ['1.0.0']), + // We'd rather render a button instead of a dropdown + if (items.length <= 1) { + return ( + false : undefined} + /> + ); + } + + return ( + false : undefined} + /> + ); +} diff --git a/src/theme/NavbarItem/DocsVersionNavbarItem.tsx b/src/theme/NavbarItem/DocsVersionNavbarItem.tsx new file mode 100644 index 0000000..13cd570 --- /dev/null +++ b/src/theme/NavbarItem/DocsVersionNavbarItem.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import {useDocsVersionCandidates} from '@docusaurus/theme-common/internal'; +import DefaultNavbarItem from '@theme/NavbarItem/DefaultNavbarItem'; +import type {Props} from '@theme/NavbarItem/DocsVersionNavbarItem'; +import type {GlobalVersion} from '@docusaurus/plugin-content-docs/client'; + +const getVersionMainDoc = (version: GlobalVersion) => + version.docs.find((doc) => doc.id === version.mainDocId)!; + +export default function DocsVersionNavbarItem({ + label: staticLabel, + to: staticTo, + docsPluginId, + ...props +}: Props): JSX.Element { + const version = useDocsVersionCandidates(docsPluginId)[0]; + const label = staticLabel ?? version.label; + const path = staticTo ?? getVersionMainDoc(version).path; + return ; +} diff --git a/src/theme/NavbarItem/DropdownNavbarItem/index.tsx b/src/theme/NavbarItem/DropdownNavbarItem/index.tsx new file mode 100644 index 0000000..faf2513 --- /dev/null +++ b/src/theme/NavbarItem/DropdownNavbarItem/index.tsx @@ -0,0 +1,176 @@ +import React, {useState, useRef, useEffect} from 'react'; +import clsx from 'clsx'; +import { + isRegexpStringMatch, + useCollapsible, + Collapsible, +} from '@docusaurus/theme-common'; +import {isSamePath, useLocalPathname} from '@docusaurus/theme-common/internal'; +import NavbarNavLink from '@theme/NavbarItem/NavbarNavLink'; +import NavbarItem, {type LinkLikeNavbarItemProps} from '@theme/NavbarItem'; +import type { + DesktopOrMobileNavBarItemProps, + Props, +} from '@theme/NavbarItem/DropdownNavbarItem'; +import styles from './styles.module.css'; + +function isItemActive( + item: LinkLikeNavbarItemProps, + localPathname: string, +): boolean { + if (isSamePath(item.to, localPathname)) { + return true; + } + if (isRegexpStringMatch(item.activeBaseRegex, localPathname)) { + return true; + } + if (item.activeBasePath && localPathname.startsWith(item.activeBasePath)) { + return true; + } + return false; +} + +function containsActiveItems( + items: readonly LinkLikeNavbarItemProps[], + localPathname: string, +): boolean { + return items.some((item) => isItemActive(item, localPathname)); +} + +function DropdownNavbarItemDesktop({ + items, + position, + className, + onClick, + ...props +}: DesktopOrMobileNavBarItemProps) { + const dropdownRef = useRef(null); + const [showDropdown, setShowDropdown] = useState(false); + + useEffect(() => { + const handleClickOutside = ( + event: MouseEvent | TouchEvent | FocusEvent, + ) => { + if ( + !dropdownRef.current || + dropdownRef.current.contains(event.target as Node) + ) { + return; + } + setShowDropdown(false); + }; + + document.addEventListener('mousedown', handleClickOutside); + document.addEventListener('touchstart', handleClickOutside); + document.addEventListener('focusin', handleClickOutside); + + return () => { + document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('touchstart', handleClickOutside); + document.removeEventListener('focusin', handleClickOutside); + }; + }, [dropdownRef]); + + return ( +
    + tag focusable in case no link target + // See https://github.com/facebook/docusaurus/pull/6003 + // There's probably a better solution though... + href={props.to ? undefined : '#'} + className={clsx('navbar__link', className)} + {...props} + onClick={props.to ? undefined : (e) => e.preventDefault()} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + setShowDropdown(!showDropdown); + } + }}> + {props.children ?? props.label} + +
      + {items.map((childItemProps, i) => ( + + ))} +
    +
    + ); +} + +function DropdownNavbarItemMobile({ + items, + className, + position, // Need to destructure position from props so that it doesn't get passed on. + onClick, + ...props +}: DesktopOrMobileNavBarItemProps) { + const localPathname = useLocalPathname(); + const containsActive = containsActiveItems(items, localPathname); + + const {collapsed, toggleCollapsed, setCollapsed} = useCollapsible({ + initialState: () => !containsActive, + }); + + // Expand/collapse if any item active after a navigation + useEffect(() => { + if (containsActive) { + setCollapsed(!containsActive); + } + }, [localPathname, containsActive, setCollapsed]); + + return ( +
  • + { + e.preventDefault(); + toggleCollapsed(); + }}> + {props.children ?? props.label} + + + {items.map((childItemProps, i) => ( + + ))} + +
  • + ); +} + +export default function DropdownNavbarItem({ + mobile = false, + ...props +}: Props): JSX.Element { + const Comp = mobile ? DropdownNavbarItemMobile : DropdownNavbarItemDesktop; + return ; +} diff --git a/src/theme/NavbarItem/DropdownNavbarItem/styles.module.css b/src/theme/NavbarItem/DropdownNavbarItem/styles.module.css new file mode 100644 index 0000000..10ee92f --- /dev/null +++ b/src/theme/NavbarItem/DropdownNavbarItem/styles.module.css @@ -0,0 +1,3 @@ +.dropdownNavbarItemMobile { + cursor: pointer; +} diff --git a/src/theme/NavbarItem/HtmlNavbarItem.tsx b/src/theme/NavbarItem/HtmlNavbarItem.tsx new file mode 100644 index 0000000..0cf8719 --- /dev/null +++ b/src/theme/NavbarItem/HtmlNavbarItem.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import clsx from 'clsx'; + +import type {Props} from '@theme/NavbarItem/HtmlNavbarItem'; + +export default function HtmlNavbarItem({ + value, + className, + mobile = false, + isDropdownItem = false, +}: Props): JSX.Element { + const Comp = isDropdownItem ? 'li' : 'div'; + return ( + + ); +} diff --git a/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx b/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx new file mode 100644 index 0000000..c5f8773 --- /dev/null +++ b/src/theme/NavbarItem/LocaleDropdownNavbarItem/index.tsx @@ -0,0 +1,76 @@ +import React from 'react'; +import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; +import {useAlternatePageUtils} from '@docusaurus/theme-common/internal'; +import {translate} from '@docusaurus/Translate'; +import {useLocation} from '@docusaurus/router'; +import DropdownNavbarItem from '@theme/NavbarItem/DropdownNavbarItem'; +import IconLanguage from '@theme/Icon/Language'; +import type {LinkLikeNavbarItemProps} from '@theme/NavbarItem'; +import type {Props} from '@theme/NavbarItem/LocaleDropdownNavbarItem'; + +import styles from './styles.module.css'; + +export default function LocaleDropdownNavbarItem({ + mobile, + dropdownItemsBefore, + dropdownItemsAfter, + queryString = '', + ...props +}: Props): JSX.Element { + const { + i18n: {currentLocale, locales, localeConfigs}, + } = useDocusaurusContext(); + const alternatePageUtils = useAlternatePageUtils(); + const {search, hash} = useLocation(); + + const localeItems = locales.map((locale): LinkLikeNavbarItemProps => { + const baseTo = `pathname://${alternatePageUtils.createUrl({ + locale, + fullyQualified: false, + })}`; + // preserve ?search#hash suffix on locale switches + const to = `${baseTo}${search}${hash}${queryString}`; + return { + label: localeConfigs[locale]!.label, + lang: localeConfigs[locale]!.htmlLang, + to, + target: '_self', + autoAddBaseUrl: false, + className: + // eslint-disable-next-line no-nested-ternary + locale === currentLocale + ? // Similar idea as DefaultNavbarItem: select the right Infima active + // class name. This cannot be substituted with isActive, because the + // target URLs contain `pathname://` and therefore are not NavLinks! + mobile + ? 'menu__link--active' + : 'dropdown__link--active' + : '', + }; + }); + + const items = [...dropdownItemsBefore, ...localeItems, ...dropdownItemsAfter]; + + // Mobile is handled a bit differently + const dropdownLabel = mobile + ? translate({ + message: 'Languages', + id: 'theme.navbar.mobileLanguageDropdown.label', + description: 'The label for the mobile language switcher dropdown', + }) + : localeConfigs[currentLocale]!.label; + + return ( + + + {dropdownLabel} + + } + items={items} + /> + ); +} diff --git a/src/theme/NavbarItem/LocaleDropdownNavbarItem/styles.module.css b/src/theme/NavbarItem/LocaleDropdownNavbarItem/styles.module.css new file mode 100644 index 0000000..8804a08 --- /dev/null +++ b/src/theme/NavbarItem/LocaleDropdownNavbarItem/styles.module.css @@ -0,0 +1,4 @@ +.iconLanguage { + vertical-align: text-bottom; + margin-right: 5px; +} diff --git a/src/theme/NavbarItem/NavbarNavLink.tsx b/src/theme/NavbarItem/NavbarNavLink.tsx new file mode 100644 index 0000000..a85c8a9 --- /dev/null +++ b/src/theme/NavbarItem/NavbarNavLink.tsx @@ -0,0 +1,67 @@ +import React from 'react'; +import Link from '@docusaurus/Link'; +import useBaseUrl from '@docusaurus/useBaseUrl'; +import isInternalUrl from '@docusaurus/isInternalUrl'; +import {isRegexpStringMatch} from '@docusaurus/theme-common'; +import IconExternalLink from '@theme/Icon/ExternalLink'; +import type {Props} from '@theme/NavbarItem/NavbarNavLink'; + +export default function NavbarNavLink({ + activeBasePath, + activeBaseRegex, + to, + href, + label, + html, + isDropdownLink, + prependBaseUrlToHref, + ...props +}: Props): JSX.Element { + // TODO all this seems hacky + // {to: 'version'} should probably be forbidden, in favor of {to: '/version'} + const toUrl = useBaseUrl(to); + const activeBaseUrl = useBaseUrl(activeBasePath); + const normalizedHref = useBaseUrl(href, {forcePrependBaseUrl: true}); + const isExternalLink = label && href && !isInternalUrl(href); + + // Link content is set through html XOR label + const linkContentProps = html + ? {dangerouslySetInnerHTML: {__html: html}} + : { + children: ( + <> + {label} + {isExternalLink && ( + + )} + + ), + }; + + if (href) { + return ( + + ); + } + + return ( + + activeBaseRegex + ? isRegexpStringMatch(activeBaseRegex, location.pathname) + : location.pathname.startsWith(activeBaseUrl), + })} + {...props} + {...linkContentProps} + /> + ); +} diff --git a/src/theme/NavbarItem/SearchNavbarItem.tsx b/src/theme/NavbarItem/SearchNavbarItem.tsx new file mode 100644 index 0000000..87c05f3 --- /dev/null +++ b/src/theme/NavbarItem/SearchNavbarItem.tsx @@ -0,0 +1,19 @@ +import React from 'react'; +import SearchBar from '@theme/SearchBar'; +import NavbarSearch from '@theme/Navbar/Search'; +import type {Props} from '@theme/NavbarItem/SearchNavbarItem'; + +export default function SearchNavbarItem({ + mobile, + className, +}: Props): JSX.Element | null { + if (mobile) { + return null; + } + + return ( + + + + ); +} diff --git a/src/theme/NavbarItem/index.tsx b/src/theme/NavbarItem/index.tsx new file mode 100644 index 0000000..45bb2bb --- /dev/null +++ b/src/theme/NavbarItem/index.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ComponentTypes from '@theme/NavbarItem/ComponentTypes'; +import type {NavbarItemType, Props} from '@theme/NavbarItem'; + +function normalizeComponentType(type: NavbarItemType, props: object) { + // Backward compatibility: navbar item with no type set + // but containing dropdown items should use the type "dropdown" + if (!type || type === 'default') { + return 'items' in props ? 'dropdown' : 'default'; + } + return type; +} + +export default function NavbarItem({type, ...props}: Props): JSX.Element { + const componentType = normalizeComponentType(type, props); + const NavbarItemComponent = ComponentTypes[componentType]; + if (!NavbarItemComponent) { + throw new Error(`No NavbarItem component found for type "${type}".`); + } + return ; +} diff --git a/static/img/icon.svg b/static/img/icon.svg new file mode 100644 index 0000000..8d0d8a4 --- /dev/null +++ b/static/img/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/img/logoDarkMode.svg b/static/img/logoDarkMode.svg new file mode 100644 index 0000000..2f84f61 --- /dev/null +++ b/static/img/logoDarkMode.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/static/img/logoLightMode.svg b/static/img/logoLightMode.svg new file mode 100644 index 0000000..ae42bd2 --- /dev/null +++ b/static/img/logoLightMode.svg @@ -0,0 +1,5 @@ + + + + + From a6ad986fc6def3076f5a25e8987c6d5e1a81e9c9 Mon Sep 17 00:00:00 2001 From: zeke <40004347+KAJdev@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:45:01 -0800 Subject: [PATCH 4/4] better dark mode search background contrast --- src/css/custom.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/css/custom.css b/src/css/custom.css index f0bbc7a..6bb3f03 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -31,6 +31,7 @@ --ifm-color-primary-lightest: #beb0f3; /* Lightest tint for dark mode */ --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); /* Highlighted code line background in dark mode */ --ifm-navbar-background-color: #000; + --docsearch-searchbox-background: #1d1d1d; } body {