Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions docs/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
33 changes: 14 additions & 19 deletions docusaurus.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}`,
},
// <link rel="preconnect" href="https://fonts.googleapis.com">
Expand All @@ -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',
},
},

Expand Down Expand Up @@ -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,
],
Expand Down Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion sidebars.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
},
{
Expand Down Expand Up @@ -132,7 +133,6 @@ const sidebars: SidebarsConfig = {
'reference/building-openrewrite-from-source',
],
},
'training',
{
type: 'category',
label: 'Lists',
Expand Down
110 changes: 110 additions & 0 deletions src/components/SecondaryNav/SecondaryNav.module.css
Original file line number Diff line number Diff line change
@@ -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) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit i think we have a breakpoint design token?

.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;
}
126 changes: 126 additions & 0 deletions src/components/SecondaryNav/SecondaryNav.tsx
Original file line number Diff line number Diff line change
@@ -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 }) => (
<span className={clsx(styles.chevron, open && styles.chevronOpen)} aria-hidden="true" />
);
Comment on lines +21 to +23

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wasn't the chevron also inlined as a data+svg in custom.css?


/**
* Section navigation, shown below the primary navbar on desktop.
*
* Picks the section; the sidebar then shows that section's contents (see
* src/theme/DocSidebarItems, which scopes on desktop only). Hidden below 997px,
* where the drawer keeps every section and this bar would duplicate it.
*/
export const SecondaryNav: FunctionComponent<SecondaryNavProps> = ({
sections,
menuLabel,
menuItems,
}) => {
const [open, setOpen] = useState(false);
const menuRef = useRef<HTMLDivElement>(null);
const location = useLocation();

// Same matcher the sidebar filter uses, so the highlighted section and the
// scoped sidebar can never disagree.
const activeHref = findSection(location.pathname)?.href;
const isMenuActive = menuItems.some((item) => item.href === activeHref);

const close = useCallback(() => setOpen(false), []);

// Close on outside click and on Escape, so the menu never outlives its context.
useEffect(() => {
if (!open) {
return;
}
// mousedown + touchstart + focusin is the set theme-classic's own
// DropdownNavbarItem listens on; mousedown alone leaves the menu open on a
// touch tap and when focus moves away by keyboard.
const onOutside = (event: MouseEvent | TouchEvent | FocusEvent) => {
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
close();
}
};
const onKeyDown = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
close();
}
};
const events = ['mousedown', 'touchstart', 'focusin'] as const;
events.forEach((name) => document.addEventListener(name, onOutside));
document.addEventListener('keydown', onKeyDown);
return () => {
events.forEach((name) => document.removeEventListener(name, onOutside));
document.removeEventListener('keydown', onKeyDown);
};
}, [open, close]);

// A route change means the menu's job is done.
useEffect(close, [location.pathname, close]);

return (
<nav className={styles.secondaryNav} aria-label="Documentation sections">
<div className={styles.sections}>
{sections.map((section) => {
const active = section.href === activeHref;
return (
<Link
key={section.href}
to={section.href}
className={clsx(styles.section, active && styles.sectionActive)}
aria-current={active ? 'page' : undefined}
>
{section.name}
</Link>
);
})}
</div>

<div className={styles.menu} ref={menuRef}>
<button
type="button"
className={clsx(styles.section, styles.menuButton, isMenuActive && styles.sectionActive)}
onClick={() => setOpen((prev) => !prev)}
aria-expanded={open}
aria-haspopup="true"
>
{menuLabel}
<Chevron open={open} />
</button>
{open && (
<div className={styles.dropdown} role="menu">
{menuItems.map((item) => (
<Link
key={item.href}
to={item.href}
className={clsx(styles.dropdownItem, item.href === activeHref && styles.dropdownItemActive)}
role="menuitem"
>
{item.name}
</Link>
))}
</div>
)}
</div>
</nav>
);
};

SecondaryNav.displayName = 'SecondaryNav';
2 changes: 2 additions & 0 deletions src/components/SecondaryNav/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { SecondaryNav } from './SecondaryNav';
export type { SecondaryNavProps } from './SecondaryNav';
Loading