diff --git a/content/@theme/components/Navbar/Navbar.tsx b/content/@theme/components/Navbar/Navbar.tsx
new file mode 100644
index 0000000000..6fa7debeed
--- /dev/null
+++ b/content/@theme/components/Navbar/Navbar.tsx
@@ -0,0 +1,313 @@
+import * as React from 'react';
+import styled from 'styled-components';
+import { useThemeConfig } from '@theme/hooks/useThemeConfig';
+import { LanguagePicker } from '@theme/i18n/LanguagePicker';
+import { useI18n } from '@portal/hooks';
+
+import { slugify } from '../../helpers';
+
+import { Link } from '@portal/Link';
+import { ColorModeSwitcher } from '@theme/components/ColorModeSwitcher/ColorModeSwitcher';
+import { Search } from '@theme/components/Search/Search';
+
+// @ts-ignore
+// import navbar from '../../../top-nav.yaml';
+
+// const alertBanner = {
+// show: true,
+// message: 'This is the draft Redocly version of the site!',
+// button: 'Cool',
+// link: 'https://github.com/ripple/xrpl-org-dev-portal',
+// };
+
+export function Navbar(props) {
+ // const [isOpen, setIsOpen] = useMobileMenu(false);
+ const themeConfig = useThemeConfig();
+ const { changeLanguage } = useI18n();
+
+ const menu = themeConfig.navbar?.items;
+ const logo = themeConfig.logo;
+
+ const { href, altText, items } = props;
+ const pathPrefix = '';
+
+ const navItems = menu.map((item, index) => {
+ if (item.type === 'group') {
+ return ;
+ } else {
+ return (
+
+
+ {item.label}
+
+
+ );
+ }
+ });
+
+ React.useEffect(() => {
+ // Turns out jQuery is necessary for firing events on Bootstrap v4
+ // dropdowns. These events set classes so that the search bar and other
+ // submenus collapse on mobile when you expand one submenu.
+ const dds = $('#topnav-pages .dropdown');
+ const top_main_nav = document.querySelector('#top-main-nav');
+ dds.on('show.bs.dropdown', evt => {
+ top_main_nav.classList.add('submenu-expanded');
+ });
+ dds.on('hidden.bs.dropdown', evt => {
+ top_main_nav.classList.remove('submenu-expanded');
+ });
+ });
+
+ return (
+ <>
+ {/* */}
+
+
+
+
+
+
+
+ {navItems}
+
+
+
+
+
+
+ >
+ );
+}
+
+const StyledColorModeSwitcher = styled(ColorModeSwitcher)`
+ padding: 10px;
+`;
+
+export function AlertBanner(props) {
+ const { show, message, button, link } = props;
+
+ return (
+
+ );
+}
+
+export function TopNavCollapsible(props) {
+ return (
+
+ {props.children}
+
+ );
+}
+
+export function NavDropdown(props) {
+ const { label, items, pathPrefix } = props;
+
+ const dropdownGroups = items.map((item, index) => {
+ if (item.items) {
+ const groupLinks = item.items.map((item2, index2) => {
+ const cls2 = item2.external ? 'dropdown-item external-link' : 'dropdown-item';
+ let item2_href = item2.link;
+ if (item2_href && !item2_href.match(/^https?:/)) {
+ item2_href = pathPrefix + item2_href;
+ }
+ return (
+
+ {item2.label}
+
+ );
+ });
+
+ const clnm = 'navcol col-for-' + slugify(item.label);
+
+ return (
+
+
{item.label}
+ {groupLinks}
+
+ );
+ } else if (item.hero) {
+ const hero_id = 'dropdown-hero-for-' + slugify(label);
+ const img_alt = item.label + ' icon';
+
+ let hero_href = item.link;
+ if (hero_href && !hero_href.match(/^https?:/)) {
+ hero_href = pathPrefix + hero_href;
+ }
+
+ return (
+
+
+
+
{item.label}
+
{item.description}
+
+
+ );
+ } else {
+ const cls = item.external ? 'dropdown-item ungrouped external-link' : 'dropdown-item ungrouped';
+ let item_href = item.link;
+ if (item_href && !item_href.match(/^https?:/)) {
+ item_href = pathPrefix + item_href;
+ }
+ return (
+
+ {item.label}
+
+ );
+ }
+ });
+
+ const toggler_id = 'topnav_' + slugify(label);
+ const dd_id = 'topnav_dd_' + slugify(label) + 'html';
+
+ return (
+
+
+ {label}
+
+
+ {dropdownGroups}
+
+
+ );
+}
+
+export function NavWrapper(props) {
+ return (
+
+ );
+}
+
+export function NavControls(props) {
+ return (
+
+ );
+}
+
+export function MobileMenuIcon() {
+ return (
+
+
+
+ );
+}
+
+export function NavItems(props) {
+ return (
+
+ );
+}
+
+export function NavItem(props) {
+ return {props.children};
+}
+
+export function LogoBlock(props) {
+ const { to, img, altText } = props;
+ return (
+
+
+
+ );
+}
+
+export class ThemeToggle extends React.Component {
+ auto_update_theme() {
+ const upc = window.localStorage.getItem('user-prefers-color');
+ let theme = 'dark'; // Default to dark theme
+ if (!upc) {
+ // User hasn't saved a preference specifically for this site; check
+ // the browser-level preferences.
+ if (window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches) {
+ theme = 'light';
+ }
+ } else {
+ // Follow user's saved setting.
+ theme = upc == 'light' ? 'light' : 'dark';
+ }
+ const disable_theme = theme == 'dark' ? 'light' : 'dark';
+ document.documentElement.classList.add(theme);
+ document.documentElement.classList.remove(disable_theme);
+ }
+
+ user_choose_theme() {
+ const new_theme = document.documentElement.classList.contains('dark') ? 'light' : 'dark';
+ window.localStorage.setItem('user-prefers-color', new_theme);
+ document.body.style.transition = 'background-color .2s ease';
+ const disable_theme = new_theme == 'dark' ? 'light' : 'dark';
+ document.documentElement.classList.add(new_theme);
+ document.documentElement.classList.remove(disable_theme);
+ }
+
+ render() {
+ return (
+
+ );
+ }
+
+ componentDidMount() {
+ this.auto_update_theme();
+ }
+}
diff --git a/content/@theme/helpers.ts b/content/@theme/helpers.ts
new file mode 100644
index 0000000000..7bf7f4ab06
--- /dev/null
+++ b/content/@theme/helpers.ts
@@ -0,0 +1,14 @@
+/**
+ * Slugify function, has to match the formula used in interactive-tutorial.js
+ */
+export function slugify(s) {
+ const unacceptable_chars = /[^A-Za-z0-9._ ]+/g;
+ const whitespace_regex = /\s+/g;
+ s = s.replace(unacceptable_chars, '');
+ s = s.replace(whitespace_regex, '_');
+ s = s.toLowerCase();
+ if (!s) {
+ s = '_';
+ }
+ return s;
+}
diff --git a/content/@theme/styles.css b/content/@theme/styles.css
index 59eeb17724..31af675cab 100644
--- a/content/@theme/styles.css
+++ b/content/@theme/styles.css
@@ -1,3 +1,19 @@
html.light pre code {
color: var(--code-block-controls-text-color) !important;
}
+
+ul.nav.navbar-nav {
+ align-items: center;
+ width: 100%;
+}
+
+@media (min-width: 992px) and (min-width: 1200px) {
+ .top-nav .topnav-search {
+ margin-left: 3.5rem;
+ margin-right: 0.5rem;
+ }
+}
+
+.top-nav .topnav-search {
+ flex-grow: 1;
+}