import {Link, config, i18n, useTranslation} from '~/utils/i18n'; import React, {FunctionComponent, useEffect, useMemo, useState} from 'react'; import { backgroundFocusedColor, border, borderRadius, navbarBackgroundColor, navbarHighlightColor, navbarHoverBackgroundColor, primaryColor, rem, size, textColor, textInvertColor, transitionProps } from '~/utils/style'; import useNavItems, {NavItem as BaseNavItem} from '~/hooks/useNavItems'; import Icon from '~/components/Icon'; import {InitConfig} from '@visualdl/i18n'; import Language from '~/components/Language'; import Tippy from '@tippyjs/react'; import ee from '~/utils/event'; import {getApiToken} from '~/utils/fetch'; import queryString from 'query-string'; import styled from 'styled-components'; import {useRouter} from 'next/router'; const API_TOKEN_KEY = process.env.API_TOKEN_KEY; const PUBLIC_PATH = process.env.PUBLIC_PATH; const Nav = styled.nav` background-color: ${navbarBackgroundColor}; color: ${textInvertColor}; ${size('100%')} padding: 0 ${rem(20)}; display: flex; justify-content: space-between; align-items: stretch; > .left { display: flex; justify-content: flex-start; align-items: center; } > .right { display: flex; justify-content: flex-end; align-items: center; margin-right: -${rem(20)}; } `; const Logo = styled.a` font-size: ${rem(20)}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-weight: 600; margin-right: ${rem(40)}; > img { ${size(rem(31), rem(98))} vertical-align: middle; margin-right: ${rem(8)}; } > span { vertical-align: middle; } `; const NavItem = styled.a<{active?: boolean}>` padding: 0 ${rem(20)}; height: 100%; display: inline-flex; justify-content: center; align-items: center; background-color: ${navbarBackgroundColor}; cursor: pointer; ${transitionProps('background-color')} &:hover { background-color: ${navbarHoverBackgroundColor}; } > .nav-text { padding: ${rem(10)} 0 ${rem(7)}; ${props => border('bottom', rem(3), 'solid', props.active ? navbarHighlightColor : 'transparent')} ${transitionProps('border-bottom')} text-transform: uppercase; } `; const SubNav = styled.div` overflow: hidden; border-radius: ${borderRadius}; `; const NavItemChild = styled.a<{active?: boolean}>` display: block; padding: 0 ${rem(20)}; line-height: 3em; &, &:visited { color: ${props => (props.active ? primaryColor : textColor)}; } &:hover { background-color: ${backgroundFocusedColor}; } `; interface NavItem extends BaseNavItem { cid?: string; active: boolean; children?: ({active: boolean} & NonNullable[number])[]; } const changeLanguage = () => { const {language} = i18n; const {allLanguages} = config; const index = allLanguages.indexOf(language); const nextLanguage = index < 0 || index >= allLanguages.length - 1 ? allLanguages[0] : allLanguages[index + 1]; i18n.changeLanguage(nextLanguage); }; const NavbarItem = React.forwardRef(({path, id, cid, active}, ref) => { const {t} = useTranslation('common'); const name = useMemo(() => (cid ? `${t(id)} - ${t(cid)}` : t(id)), [t, id, cid]); if (path) { // https://nextjs.org/docs/api-reference/next/link#if-the-child-is-a-custom-component-that-wraps-an-a-tag return ( {name} ); } return ( {name} ); }); NavbarItem.displayName = 'NavbarItem'; const Navbar: FunctionComponent = () => { const {t, i18n} = useTranslation('common'); const {pathname, basePath} = useRouter(); const currentPath = useMemo(() => pathname.replace(basePath, ''), [pathname, basePath]); const navItems = useNavItems(); const [items, setItems] = useState([]); useEffect(() => { setItems(oldItems => navItems.map(item => { const children = item.children?.map(child => ({ ...child, active: child.path === currentPath })); if (item.children && !item.path) { const child = item.children.find(child => child.path === currentPath); if (child) { return { ...item, cid: child.id, path: currentPath, active: true, children }; } else { const oldItem = oldItems.find(oldItem => oldItem.id === item.id); if (oldItem) { return { ...item, ...oldItem, active: false, children }; } } } return { ...item, active: currentPath === item.path, children }; }) ); }, [navItems, currentPath]); const indexUrl = useMemo(() => { // TODO: fix type const subpath = (i18n.options as InitConfig).localeSubpaths?.[i18n.language]; let path = PUBLIC_PATH ?? ''; if (subpath) { path += `/${subpath}`; } path += '/index'; if (API_TOKEN_KEY) { const id = getApiToken(); if (id) { path += `?${queryString.stringify({ [API_TOKEN_KEY]: id })}`; } } return path; }, [i18n.options, i18n.language]); return ( ); }; export default Navbar;