import { Badge, Box, Button, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuGroup, DropdownMenuItem, DropdownMenuPortal, DropdownMenuTrigger, elevationVariants, Flex, Link, NavigationLink, SidePanel, styled, Text, Tooltip, VisuallyHidden, } from '@traefiklabs/faency' import { useContext, useEffect, useMemo, useState } from 'react' import { BsChevronDoubleRight, BsChevronDoubleLeft } from 'react-icons/bs' import { FiBookOpen, FiGithub, FiHelpCircle } from 'react-icons/fi' import { matchPath, useHref } from 'react-router' import { useLocation } from 'react-router-dom' import { useWindowSize } from 'usehooks-ts' import Container from './Container' import { DARK_PRIMARY_COLOR, LIGHT_PRIMARY_COLOR } from './Page' import IconButton from 'components/buttons/IconButton' import Logo from 'components/icons/Logo' import { PluginsIcon } from 'components/icons/PluginsIcon' import ThemeSwitcher from 'components/ThemeSwitcher' import TooltipText from 'components/TooltipText' import { VersionContext } from 'contexts/version' import useTotals from 'hooks/use-overview-totals' import { useIsDarkMode } from 'hooks/use-theme' import { Route, ROUTES } from 'routes' export const LAPTOP_BP = 1025 const NavigationDrawer = styled(Flex, { width: '100%', maxWidth: '100%', height: 64, p: 0, variants: { elevation: elevationVariants, }, defaultVariants: { elevation: 1, }, }) const BasicNavigationItem = ({ route, count, isSmallScreen, isExpanded, }: { route: Route count?: number isSmallScreen: boolean isExpanded: boolean }) => { const { pathname } = useLocation() const href = useHref(route.path) const isActiveRoute = useMemo(() => { const mainPath = matchPath(route.path, pathname) if (mainPath) return true if (route.activeMatches) { return route.activeMatches.some((path) => matchPath(path, pathname)) } }, [pathname, route.activeMatches, route.path]) if (isSmallScreen && !isExpanded) { return ( {route.label}} side="right"> ) } return ( {route.label} {!!count && ( {count} )} ) } export const SideBarPanel = ({ isOpen, onOpenChange, }: { isOpen: boolean onOpenChange: (isOpen: boolean) => void }) => { const windowSize = useWindowSize() return ( side navigation onOpenChange(false)} /> ) } export const SideNav = ({ isExpanded, onSidePanelToggle, isResponsive = false, }: { isExpanded: boolean onSidePanelToggle: () => void isResponsive?: boolean }) => { const windowSize = useWindowSize() const { version } = useContext(VersionContext) const { http, tcp, udp } = useTotals() const [isSmallScreen, setIsSmallScreen] = useState(false) useEffect(() => { setIsSmallScreen(isResponsive && windowSize.width < LAPTOP_BP) }, [isExpanded, isResponsive, windowSize.width]) const totalValueByPath = useMemo<{ [key: string]: number }>( () => ({ '/http/routers': http?.routers, '/http/services': http?.services, '/http/middlewares': http?.middlewares as number, '/tcp/routers': tcp?.routers, '/tcp/services': tcp?.services, '/tcp/middlewares': tcp?.middlewares as number, '/udp/routers': udp?.routers, '/udp/services': udp?.services, }), [http, tcp, udp], ) return ( div:nth-child(1)': { marginLeft: 0, paddingRight: 0, }, } : undefined, transition: '150ms cubic-bezier(0.22, 1, 0.36, 1)', '&[data-collapsed="true"]': { marginLeft: -32, }, }} > : } onClick={onSidePanelToggle} css={{ display: 'none', position: 'absolute', top: 3, right: isExpanded ? 12 : 4, color: '$hiContrast', [`@media (max-width:${LAPTOP_BP}px)`]: { display: 'inherit' }, p: '$1', '&:before, &:after': { borderRadius: '10px' }, height: 16, }} /> {!!version && !isSmallScreen && ( )} {ROUTES.map((section, index) => ( {section.sectionLabel && ( {section.sectionLabel} )} {section.items.map((item, idx) => ( ))} ))} } css={{ mt: '$3', whiteSpace: 'nowrap', }} href="https://plugins.traefik.io/" target="_blank" > {!isSmallScreen || isExpanded ? 'Plugins' : ''} ) } export const TopNav = () => { const [hasHubButtonComponent, setHasHubButtonComponent] = useState(false) const { showHubButton, version } = useContext(VersionContext) const isDarkMode = useIsDarkMode() const parsedVersion = useMemo(() => { if (!version) { return 'master' } if (version === 'dev') { return 'master' } const matches = version.match(/^(v?\d+\.\d+)/) return matches ? 'v' + matches[1] : 'master' }, [version]) useEffect(() => { if (!showHubButton) { setHasHubButtonComponent(false) return } if (customElements.get('hub-button-app')) { setHasHubButtonComponent(true) return } const scripts: HTMLScriptElement[] = [] const createScript = (scriptSrc: string): HTMLScriptElement => { const script = document.createElement('script') script.src = scriptSrc script.async = true script.onload = () => { setHasHubButtonComponent(customElements.get('hub-button-app') !== undefined) } scripts.push(script) return script } // Source: https://github.com/traefik/traefiklabs-hub-button-app document.head.appendChild(createScript('traefiklabs-hub-button-app/main-v1.js')) return () => { // Remove the scripts on unmount. scripts.forEach((script) => { if (script.parentNode) { script.parentNode.removeChild(script) } }) } }, [showHubButton]) return ( {hasHubButtonComponent && ( )} Documentation Github Repository ) }