diff --git a/docs/introduction.md b/docs/introduction.md index 5b1dae60b5..8f7445a302 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -3,6 +3,10 @@ title: OpenRewrite by Moderne | Large Scale Automated Refactoring keywords: [OpenRewrite, Moderne, refactoring, Java, Spring Boot Migration, auto-remediation, SAST, SCA] description: Large-scale automated source code refactoring slug: / +# The entry point reads as a landing page rather than a doc, so it shows no +# sidebar. Detaching here rather than hiding the column in CSS also releases the +# width Docusaurus reserves for it. +displayed_sidebar: null --- import ReactPlayer from 'react-player' diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 0f1d06d77e..5560972a35 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -41,26 +41,16 @@ const config: Config = { }, innerHTML: JSON.stringify(structuredData), }, - // Runs before hydration so neither the bar nor the navbar offset flashes. - // Hides the notice bar for visitors who already dismissed it, and records whether - // a bar is showing on this route so custom.css can offset the sticky navbar. - // Which bar applies depends on the path, but the dismissal flags are global, hence - // the route check. Keys and attributes must match src/theme/AnnouncementBar/index.tsx. + // Runs before hydration so the notice bar does not flash for visitors who + // already dismissed it. The key and attribute must match + // src/theme/AnnouncementBar/index.tsx. { tagName: 'script', attributes: {}, innerHTML: `try { - var noticeDismissed = window.localStorage.getItem('code-genome-project-announcement-dismissed') === 'true'; - if (noticeDismissed) { + if (window.localStorage.getItem('code-genome-project-announcement-dismissed') === 'true') { document.documentElement.setAttribute('data-notice-bar-dismissed', 'true'); } - var onRecipes = window.location.pathname.indexOf('/recipes') === 0; - var dismissed = onRecipes - ? window.localStorage.getItem('docusaurus.announcement.dismiss') === 'true' - : noticeDismissed; - if (!dismissed) { - document.documentElement.setAttribute('data-bar-visible', 'true'); - } } catch (e) {}`, }, // @@ -84,7 +74,7 @@ const config: Config = { tagName: 'link', attributes: { rel: 'stylesheet', - href: 'https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&display=swap', + href: 'https://fonts.googleapis.com/css2?family=Geist:ital,wght@0,300..700;1,300..700&family=Geist+Mono:ital,wght@0,400;0,500;1,400&display=swap', }, }, @@ -136,7 +126,13 @@ const config: Config = { trackingID: "G-Y67JVX3WB7", }, theme: { - customCss: "./src/css/custom.css", + // Order matters: the design-system tokens define every --mod-* the + // site references, and custom.css is the Infima bridge, so it must + // load second for its --ifm-* mappings to win. + customCss: [ + require.resolve("@moderneinc/design-system-tokens/moderne.css"), + "./src/css/custom.css", + ], }, } satisfies Preset.Options, ], @@ -244,9 +240,8 @@ const config: Config = { }, ], }, - footer: { - copyright: `© Moderne, ${new Date().getFullYear()}`, - }, + // The footer is swizzled (src/theme/Footer), which owns its own links and + // copyright, so there is nothing to configure here. prism: { theme: prismThemes.vsDark, darkTheme: prismThemes.vsDark, diff --git a/package.json b/package.json index a644373024..5f03570c32 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@docusaurus/theme-mermaid": "^3.9.2", "@mdx-js/react": "^3.1.0", "@mermaid-js/layout-elk": "^0.1.9", + "@moderneinc/design-system-tokens": "7.4.0", "clsx": "^2.0.0", "prism-react-renderer": "^2.3.0", "react": "^18.0.0", diff --git a/sidebars.ts b/sidebars.ts index 4810305669..92f3dd2565 100644 --- a/sidebars.ts +++ b/sidebars.ts @@ -87,6 +87,7 @@ const sidebars: SidebarsConfig = { description: 'How to create and test recipes with OpenRewrite and Moderne.', href: 'https://docs.moderne.io/hands-on-learning/fundamentals/workshop-overview', }, + 'training', ], }, { @@ -132,7 +133,6 @@ const sidebars: SidebarsConfig = { 'reference/building-openrewrite-from-source', ], }, - 'training', { type: 'category', label: 'Lists', diff --git a/src/components/SecondaryNav/SecondaryNav.module.css b/src/components/SecondaryNav/SecondaryNav.module.css new file mode 100644 index 0000000000..78270663ee --- /dev/null +++ b/src/components/SecondaryNav/SecondaryNav.module.css @@ -0,0 +1,110 @@ +/** + * Section navigation, below the primary navbar. Desktop only: on mobile the + * sidebar still lists every section, so this bar would only repeat it. + */ + +.secondaryNav { + display: none; + height: var(--ifm-secondary-nav-height); + padding: 0 var(--mod-spacing_4); + align-items: center; + justify-content: space-between; + /* Hairline that defines the header's lower edge. */ + border-bottom: 1px solid var(--mod-color-border-primary); +} + +@media (min-width: 997px) { + .secondaryNav { + display: flex; + } +} + +.sections { + display: flex; + align-items: center; + gap: var(--mod-spacing_3); + min-width: 0; +} + +.section { + font-size: var(--mod-font-size-default); + font-weight: var(--mod-font-weight-medium); + color: var(--mod-color-text-primary); + text-decoration: none; + white-space: nowrap; + transition: color 0.2s ease; +} + +.section:hover { + color: var(--mod-color-action-default); + text-decoration: none; +} + +/* Both active states take the same colour; custom.css owns the light/dark pair. */ +.sectionActive, +.sectionActive:hover, +.dropdownItemActive, +.dropdownItemActive:hover { + color: var(--docs-color-active); +} + +.menu { + position: relative; + flex-shrink: 0; +} + +.menuButton { + display: flex; + align-items: center; + gap: var(--mod-spacing_1_2); + padding: var(--mod-spacing_1) 0; + background: transparent; + border: none; + cursor: pointer; +} + +/* One definition of this glyph, shared with the sidebar caret in custom.css. + background-color + mask means it follows currentColor like an inline SVG. */ +.chevron { + width: 10px; + height: 6px; + flex-shrink: 0; + background-color: currentColor; + mask-image: var(--docs-chevron-down); + mask-repeat: no-repeat; + mask-size: contain; + mask-position: center; + transition: transform 0.2s ease; +} + +.chevronOpen { + transform: rotate(180deg); +} + +.dropdown { + position: absolute; + top: 100%; + right: 0; + min-width: 200px; + padding: var(--mod-spacing_1) 0; + background-color: var(--mod-color-surface-raised); + border: 1px solid var(--mod-color-border-primary); + border-radius: var(--mod-border-radius-input); + box-shadow: var(--docs-shadow-dropdown); + z-index: var(--ifm-z-index-dropdown); +} + +.dropdownItem { + display: block; + padding: var(--mod-spacing_1) var(--mod-spacing_2); + font-size: var(--mod-font-size-default); + color: var(--mod-color-text-primary); + text-decoration: none; + white-space: nowrap; +} + +.dropdownItem:hover { + background-color: var(--mod-color-surface-row-hover); + color: var(--mod-color-text-primary); + text-decoration: none; +} diff --git a/src/components/SecondaryNav/SecondaryNav.tsx b/src/components/SecondaryNav/SecondaryNav.tsx new file mode 100644 index 0000000000..8153544477 --- /dev/null +++ b/src/components/SecondaryNav/SecondaryNav.tsx @@ -0,0 +1,126 @@ +import Link from '@docusaurus/Link'; +import { useLocation } from '@docusaurus/router'; +import clsx from 'clsx'; +import { type FunctionComponent, useCallback, useEffect, useRef, useState } from 'react'; +import type { NavSection } from '@site/src/config/navSections'; +import { findSection } from '@site/src/theme/DocSidebarItems/filterUtils'; +import styles from './SecondaryNav.module.css'; + +export type SecondaryNavProps = { + /** Sections rendered as direct links, left-aligned. */ + sections: NavSection[]; + /** Label for the right-aligned dropdown. */ + menuLabel: string; + /** Items inside that dropdown. */ + menuItems: NavSection[]; +}; + +/** Chevron for the dropdown. Masked from --docs-chevron-down rather than + * inlined, so this glyph has one definition shared with the sidebar caret. The + * mask takes its colour from currentColor, as an inline SVG would. */ +const Chevron: FunctionComponent<{ open: boolean }> = ({ open }) => ( +