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 ( +
+
+ +

{message}

+
+ + + {button} + + +
+
+ ); +} + +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 ( + + {img_alt} +
+

{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 ( +
  • + +
    + {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 ( + + {altText} + + ); +} + +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; +}