import { useRef, useState } from 'react' import { useUpdateAnimation } from './hooks/use-update-animation' import { useMeasureWidth } from './hooks/use-measure-width' import { Cross } from '../../icons/cross' import { Warning } from '../../icons/warning' import { css } from '../../utils/css' import { useDevOverlayContext } from '../../../dev-overlay.browser' import { useRenderErrorContext } from '../../dev-overlay' import { useDelayedRender } from '../../hooks/use-delayed-render' import { ACTION_ERROR_OVERLAY_CLOSE, ACTION_ERROR_OVERLAY_OPEN, } from '../../shared' import { usePanelRouterContext } from '../../menu/context' import { BASE_LOGO_SIZE } from '../../utils/indicator-metrics' import { StatusIndicator, Status, getCurrentStatus } from './status-indicator' const SHORT_DURATION_MS = 150 export function NextLogo({ onTriggerClick, ...buttonProps }: { onTriggerClick: () => void } & React.ComponentProps<'button'>) { const { state, dispatch } = useDevOverlayContext() const { totalErrorCount } = useRenderErrorContext() const SIZE = BASE_LOGO_SIZE / state.scale const { panel, triggerRef, setPanel } = usePanelRouterContext() const isMenuOpen = panel === 'panel-selector' const hasError = totalErrorCount > 0 const [isErrorExpanded, setIsErrorExpanded] = useState(hasError) const [previousHasError, setPreviousHasError] = useState(hasError) if (previousHasError !== hasError) { setPreviousHasError(hasError) // Reset the expanded state when the error state changes setIsErrorExpanded(hasError) } const [dismissed, setDismissed] = useState(false) const newErrorDetected = useUpdateAnimation( totalErrorCount, SHORT_DURATION_MS ) // Cache indicator state management const isCacheFilling = state.cacheIndicator === 'filling' const isCacheBypassing = state.cacheIndicator === 'bypass' // Determine if we should show any status (excluding cache bypass, which renders like error badge) const shouldShowStatus = state.buildingIndicator || state.renderingIndicator || isCacheFilling // Delay showing for 400ms to catch fast operations, // and keep visible for minimum time (longer for warnings) const { rendered: showStatusIndicator } = useDelayedRender(shouldShowStatus, { enterDelay: 400, exitDelay: 500, }) const ref = useRef(null) const measuredWidth = useMeasureWidth(ref) // Get the current status from the state const currentStatus = getCurrentStatus( state.buildingIndicator, state.renderingIndicator, state.cacheIndicator ) const displayStatus = showStatusIndicator ? currentStatus : Status.None const isExpanded = isErrorExpanded || isCacheBypassing || showStatusIndicator || state.disableDevIndicator const width = measuredWidth === 0 ? 'auto' : measuredWidth return (
{/* Styles */}
{/* Children */} {!state.disableDevIndicator && ( )} {isExpanded && ( <> {/* Error badge has priority over cache indicator */} {(isErrorExpanded || state.disableDevIndicator) && (
{!state.buildError && ( )}
)} {/* Cache bypass badge shown when cache is being bypassed */} {isCacheBypassing && !hasError && !state.disableDevIndicator && ( )} {/* Status indicator shown when no errors and no cache bypass */} {showStatusIndicator && !hasError && !isCacheBypassing && !state.disableDevIndicator && ( )} )}
) } function AnimateCount({ children: count, animate = true, ...props }: { children: number animate: boolean }) { return (
{count - 1}
{count}
) } function CacheBypassBadge({ onTriggerClick, triggerRef, }: { onTriggerClick: () => void triggerRef: React.RefObject }) { const [dismissed, setDismissed] = useState(false) if (dismissed) { return null } return (
) } function NextMark() { return ( ) }