import { forwardRef, useCallback, useEffect, useId, useMemo, useState, type ReactNode, } from 'react' import styled from 'styled-components' import Block from '../../Primitives/Block/Block' import { useResponsiveTokens } from '../../../hooks/useResponsiveTokens' import type { SidebarV2MobileNavigationProps } from './types' import type { MobileNavigationV2TokenType } from './mobile.tokens' import { getMobileNavigationFillerCount, getMobileNavigationLayout, getMobileNavigationSecondaryRows, parseUnitValue, splitPrimaryItems, } from './utils' import { useItemSelection, useMobileNavigationViewportHeight, useOrderedItems, } from './hooks' import MobileNavigationItem from './MobileNavigationItem' import PrimaryActionButton from './PrimaryActionButton' import MoreButton from './MoreButton' type FloatingNavBoxProps = { $tokens: MobileNavigationV2TokenType $backgroundColor: string $background?: string } const FloatingNavContainer = styled(Block)` position: fixed; bottom: 0; left: 0; right: 0; z-index: ${({ $tokens }) => String($tokens?.container?.zIndex ?? 1100)}; backdrop-filter: ${({ $tokens }) => String($tokens?.container?.backdropFilter ?? 'blur(20px)')}; -webkit-backdrop-filter: ${({ $tokens }) => String($tokens?.container?.backdropFilter ?? 'blur(20px)')}; background-color: ${({ $backgroundColor }) => $backgroundColor}; border: ${({ $tokens }) => String($tokens?.container?.border ?? 'none')}; border-radius: ${({ $tokens }) => String($tokens?.container?.borderRadius ?? '24px')}; transition: ${({ $tokens }) => String($tokens?.container?.transition ?? 'all 0.3s ease')}; overflow: hidden; will-change: transform, max-height; display: flex; flex-direction: column; @supports ( (-webkit-backdrop-filter: blur(20px)) or (backdrop-filter: blur(20px)) ) { ${({ $background }) => $background ? `background: ${$background};` : ''} } ` const ScrollableContent = styled(Block)` overflow-y: auto; overflow-x: hidden; flex: 1; -webkit-overflow-scrolling: touch; min-height: 0; &::-webkit-scrollbar { display: none; } -ms-overflow-style: none; scrollbar-width: none; ` const PRIMARY_VISIBLE_LIMIT = 5 const VIEWPORT_HEIGHT_MULTIPLIER = 0.85 const SidebarV2MobileNavigation = forwardRef< HTMLDivElement, SidebarV2MobileNavigationProps >( ( { items, onHeightChange, showMobilePrimaryActionButton = false, mobilePrimaryActionButtonProps, }, ref ) => { const baseId = useId() const secondaryNavigationRegionId = `${baseId}-secondary-mobile-nav` const tokens = useResponsiveTokens( 'MOBILE_NAVIGATION_V2' ) const viewportHeight = useMobileNavigationViewportHeight() const [orderedItems, setOrderedItems] = useOrderedItems(items) const layout = useMemo( () => getMobileNavigationLayout( orderedItems, viewportHeight, tokens, PRIMARY_VISIBLE_LIMIT, VIEWPORT_HEIGHT_MULTIPLIER, { primaryReservedSlots: showMobilePrimaryActionButton ? 1 : 0, } ), [ orderedItems, showMobilePrimaryActionButton, viewportHeight, tokens, ] ) const [isExpanded, setIsExpanded] = useState(false) const toggleExpansion = useCallback(() => setIsExpanded((c) => !c), []) const collapse = useCallback(() => setIsExpanded(false), []) const floatingPadding = tokens.layout.floatingPadding const safeAreaOffset = parseUnitValue(tokens.layout.safeAreaOffset) const { collapsedHeight, expandedHeight } = useMemo(() => { const collapsed = parseUnitValue(layout.snapPoints[0] as string) const expanded = layout.snapPoints[1] ? parseUnitValue(layout.snapPoints[1] as string) : collapsed return { collapsedHeight: collapsed, expandedHeight: expanded } }, [layout.snapPoints]) const navigationHeight = useMemo( () => `${isExpanded ? expandedHeight : collapsedHeight}px`, [isExpanded, expandedHeight, collapsedHeight] ) useEffect(() => { onHeightChange?.(navigationHeight) }, [navigationHeight, onHeightChange]) useEffect(() => () => onHeightChange?.('0px'), [onHeightChange]) const { leftItems, rightItems } = useMemo( () => splitPrimaryItems( layout.primaryItems, showMobilePrimaryActionButton ), [layout.primaryItems, showMobilePrimaryActionButton] ) const handleItemSelect = useItemSelection( orderedItems, setOrderedItems, layout.primaryItems, layout.hasSecondaryItems, collapse ) const secondaryRows = useMemo( () => getMobileNavigationSecondaryRows( layout.secondaryItems, PRIMARY_VISIBLE_LIMIT ), [layout.secondaryItems] ) const primaryActionMargin = String(tokens.layout.primaryActionMarginX) const primaryRowElements = useMemo(() => { const elements: ReactNode[] = [] leftItems.forEach((item, index) => { elements.push( ) }) if (showMobilePrimaryActionButton) { elements.push( ) } rightItems.forEach((item, index) => { const absoluteIndex = index + leftItems.length elements.push( ) }) if (layout.hasSecondaryItems) { elements.push( ) } return elements }, [ leftItems, rightItems, showMobilePrimaryActionButton, layout.hasSecondaryItems, primaryActionMargin, tokens, mobilePrimaryActionButtonProps, handleItemSelect, toggleExpansion, isExpanded, secondaryNavigationRegionId, ]) return ( {primaryRowElements} {layout.hasSecondaryItems && ( {isExpanded && secondaryRows.map((row, rowIndex) => { const fillerCount = getMobileNavigationFillerCount( row.length, PRIMARY_VISIBLE_LIMIT ) const rowElements: ReactNode[] = [ ...row.map((item, index) => ( handleItemSelect( selectedItem, true ) } /> )), ...Array.from({ length: fillerCount, }).map((_, fillerIndex) => ( )} ) } ) SidebarV2MobileNavigation.displayName = 'SidebarV2MobileNavigation' export default SidebarV2MobileNavigation