import classNames from 'classnames' import { NavLink, useLocation } from 'react-router-dom' import { useOrgParams } from '~/utils/organization' import ControlPanel from './ControlPanel' import ModeSwitch from './ModeSwitch' import { useTopNavState } from '~/utils/useDashboardStructure' import { ActionGroupWithPossibleMetadata } from '~/utils/types' import { useEffect, useRef, useState } from 'react' import IconCaretDown from '~/icons/compiled/CaretDown' import { Menu, MenuButton, useMenuState } from 'ariakit' import Transition from '../Transition' import { dropdownMenuClassNames } from '~/components/DropdownMenu' import { useDebouncedCallback } from 'use-debounce' import MobileNav from './MobileNav' import IconMenu from '~/icons/compiled/Menu' import useControlPanelNav from './useControlPanelNav' import OrgSwitcher from './OrgSwitcher' import useDashboard from '../DashboardContext' import { ENV_COLOR_OPTIONS } from '~/utils/color' import { VisualState, useKBar } from 'kbar' import SearchIcon from '~/icons/compiled/Search' export const NAV_ITEM_HEIGHT = 54 export const NAVBAR_HEIGHT = NAV_ITEM_HEIGHT + 1 // 1px border const MORE_ITEM_WIDTH = 84 function getFittingNavItems( container: HTMLElement, topLevelGroups: ActionGroupWithPossibleMetadata[] ): ActionGroupWithPossibleMetadata[] { const spaceTarget = container.offsetWidth const items = Array.from(container.children) const sizes = items.map(li => li.getBoundingClientRect()) let spaceUsed = 0 const canFitAllItems = sizes.reduce((acc, size) => acc + size.width, 0) < spaceTarget if (canFitAllItems) return topLevelGroups return sizes.reduce((acc, size, index) => { if (spaceUsed + size.width + MORE_ITEM_WIDTH < spaceTarget) { spaceUsed += size.width // don't push the first item, which is 'Dashboard' and is not in topLevelGroups if (index > 0) { acc.push(topLevelGroups[index - 1]) } } return acc }, [] as ActionGroupWithPossibleMetadata[]) } export function getTopLevelGroups(data?: ActionGroupWithPossibleMetadata[]) { if (!data) return [] const topLevelGroups = data.filter(g => !g.slug.includes('/') && !g.unlisted) return topLevelGroups } export function useIsSettingsPage() { const { pathname } = useLocation() const { orgNav, userNav, actionsNav } = useControlPanelNav() const paths = [ ...orgNav.map(g => g.path), ...userNav.map(g => g.path), ...actionsNav.map(g => g.path), ] return paths.some(p => pathname.startsWith(p)) } function Navbar({ groups, isSettingsPage, }: { groups?: ActionGroupWithPossibleMetadata[] isSettingsPage: boolean }) { const { basePath } = useOrgParams() // To avoid getting stuck in a display-adjust-display loop, we use two elements, a "ghost nav" // and a visible nav. The ghost nav calculates what we can fit on screen, and the visible nav renders it. const ghostNavRef = useRef(null) const [visibleItems, setVisibleItems] = useState< ActionGroupWithPossibleMetadata[] | undefined >(undefined) const menu = useMenuState({ animated: 250, gutter: -6 }) const topLevelGroups = getTopLevelGroups(groups) const navLinkClassName = ({ isActive, className, }: { isActive: boolean className?: string }) => { return classNames( 'text-sm cursor-pointer flex items-center focus:outline-none font-medium px-4 rounded-md', className, { 'bg-opacity-50 text-primary-400 border-none': isActive, 'text-gray-900 hover:text-opacity-60 border-none': !isActive, } ) } const calculate = useDebouncedCallback(() => { if (!ghostNavRef.current) return if (!topLevelGroups.length) { setVisibleItems([]) } else { setVisibleItems(getFittingNavItems(ghostNavRef.current, topLevelGroups)) } }, 100) useEffect(() => { calculate() window.addEventListener('resize', calculate) return () => window.removeEventListener('resize', calculate) }, [calculate, groups]) return ( ) } function CommandBarToggle() { const { query, visible } = useKBar(state => ({ visible: state.visualState !== VisualState.hidden, })) return ( ) } export default function DashboardNav() { const { isDevMode } = useOrgParams() const { organizationEnvironment } = useDashboard() const [isSidebarOpen, setIsSidebarOpen] = useState(false) const groups = useTopNavState({ mode: isDevMode ? 'console' : 'live', }) const isSettingsPage = useIsSettingsPage() const hasEnvColor = !!organizationEnvironment?.color return ( <> setIsSidebarOpen(false)} groups={groups} mode={isDevMode ? 'console' : 'live'} />
{organizationEnvironment?.color && !isSettingsPage && (
{organizationEnvironment?.name}
)}
) }