{"version":3,"file":"MainNav.mjs","sources":["../../../../src/lib/ui/navigation/MainNav.tsx"],"sourcesContent":["/**\n * @file Main Navigation Component\n * @description Main navigation that respects auth + feature flags\n * Performance optimized: static styles extracted, dynamic styles memoized\n * Uses theme tokens for consistent styling\n */\n\nimport React, { useMemo, memo, useCallback, type CSSProperties } from 'react';\nimport { useAuth } from '../../auth/useAuth';\nimport { useFeatureFlag } from '../../flags/useFeatureFlag';\nimport { AppLink } from '../../routing/linking';\nimport { type Role } from '../../auth/types';\nimport { colorTokens, tokens } from '../../theme/tokens';\n\n/**\n * Navigation route definition\n */\nexport interface NavRoute {\n  id: string;\n  label: string;\n  path: string;\n  icon?: React.ReactNode;\n  requiredRoles?: Role[];\n  featureFlag?: string;\n  children?: NavRoute[];\n  badge?: string | number;\n  external?: boolean;\n}\n\n/**\n * Main nav props\n */\nexport interface MainNavProps {\n  /** Navigation routes */\n  routes: NavRoute[];\n\n  /** Current path for active state */\n  currentPath?: string;\n\n  /** Layout direction */\n  direction?: 'horizontal' | 'vertical';\n\n  /** Custom class name */\n  className?: string;\n}\n\n// ============================================================================\n// STATIC STYLES - Extracted outside component to prevent re-creation\n// ============================================================================\n\nconst iconContainerStyle: CSSProperties = {\n  flexShrink: 0,\n};\n\nconst badgeStyle: CSSProperties = {\n  marginLeft: 'auto',\n  padding: '0.125rem 0.5rem',\n  fontSize: tokens.fontSize.xs,\n  backgroundColor: colorTokens.neutral[200],\n  borderRadius: tokens.radius.full,\n};\n\nconst externalIconStyle: CSSProperties = {\n  marginLeft: '0.25rem',\n};\n\n// Screen reader only style for hidden text\nconst srOnlyStyle: CSSProperties = {\n  position: 'absolute',\n  width: '1px',\n  height: '1px',\n  padding: 0,\n  margin: '-1px',\n  overflow: 'hidden',\n  clip: 'rect(0, 0, 0, 0)',\n  whiteSpace: 'nowrap',\n  border: 0,\n};\n\n// ============================================================================\n// HELPER FUNCTIONS\n// ============================================================================\n\n/**\n * Check if user has access to a route\n */\nfunction hasRouteAccess(route: NavRoute, roles: Role[]): boolean {\n  if (!route.requiredRoles || route.requiredRoles.length === 0) {\n    return true;\n  }\n  return route.requiredRoles.some((role) => roles.includes(role));\n}\n\n// ============================================================================\n// SUB-COMPONENTS\n// ============================================================================\n\n/**\n * Props for NavItem component\n */\ninterface NavItemProps {\n  route: NavRoute;\n  isActive: boolean;\n  direction: 'horizontal' | 'vertical';\n  roles: Role[];\n}\n\n/**\n * Navigation item component - memoized for performance\n */\nconst NavItem = memo(\n  ({ route, isActive, direction, roles }: NavItemProps): React.ReactElement | null => {\n    // Check feature flag - always call with consistent pattern to avoid Rules of Hooks violation\n    // Use sentinel value '__ALWAYS_ENABLED__' when no feature flag is specified\n    const flagEnabled = useFeatureFlag(route.featureFlag ?? '__ALWAYS_ENABLED__');\n\n    const isHorizontal = direction === 'horizontal';\n\n    // Memoize link style based on direction and active state - MUST be before early returns\n    const linkStyle = useMemo(\n      (): CSSProperties => ({\n        display: 'flex',\n        alignItems: 'center',\n        gap: tokens.spacing.sm,\n        padding: isHorizontal\n          ? `${tokens.spacing.sm} ${tokens.spacing.md}`\n          : `${tokens.spacing.md} ${tokens.spacing.md}`,\n        color: isActive ? colorTokens.primary.default : colorTokens.text.secondary,\n        textDecoration: 'none',\n        borderRadius: tokens.radius.md,\n        backgroundColor: isActive ? colorTokens.interactive.selected : 'transparent',\n        fontWeight: isActive ? tokens.fontWeight.medium : tokens.fontWeight.normal,\n        fontSize: tokens.fontSize.sm,\n        whiteSpace: 'nowrap',\n      }),\n      [isHorizontal, isActive]\n    );\n\n    // Check visibility AFTER all hooks\n    if (route.featureFlag !== undefined && route.featureFlag !== '' && !flagEnabled) {\n      return null;\n    }\n\n    if (!hasRouteAccess(route, roles)) {\n      return null;\n    }\n\n    const content = (\n      <>\n        {route.icon !== undefined && <span style={iconContainerStyle}>{route.icon}</span>}\n        <span>{route.label}</span>\n        {route.badge !== undefined && route.badge !== '' && (\n          <span style={badgeStyle}>{route.badge}</span>\n        )}\n      </>\n    );\n\n    if (route.external === true) {\n      return (\n        <a\n          href={route.path}\n          target=\"_blank\"\n          rel=\"noopener noreferrer\"\n          style={linkStyle}\n          aria-label={`${route.label} (opens in new tab)`}\n        >\n          {content}\n          <svg\n            width=\"12\"\n            height=\"12\"\n            viewBox=\"0 0 20 20\"\n            fill=\"currentColor\"\n            style={externalIconStyle}\n            aria-hidden=\"true\"\n            focusable=\"false\"\n          >\n            <path d=\"M11 3a1 1 0 100 2h2.586l-6.293 6.293a1 1 0 101.414 1.414L15 6.414V9a1 1 0 102 0V4a1 1 0 00-1-1h-5z\" />\n            <path d=\"M5 5a2 2 0 00-2 2v8a2 2 0 002 2h8a2 2 0 002-2v-3a1 1 0 10-2 0v3H5V7h3a1 1 0 000-2H5z\" />\n          </svg>\n          <span style={srOnlyStyle}>(opens in new tab)</span>\n        </a>\n      );\n    }\n\n    return (\n      <AppLink to={route.path} style={linkStyle}>\n        {content}\n      </AppLink>\n    );\n  }\n);\n\nNavItem.displayName = 'NavItem';\n\n// ============================================================================\n// MAIN COMPONENT\n// ============================================================================\n\n/**\n * Main navigation component\n */\nexport function MainNav({\n  routes,\n  currentPath,\n  direction = 'horizontal',\n  className,\n}: MainNavProps): React.ReactElement {\n  const { roles, isAuthenticated } = useAuth();\n\n  // Filter routes based on auth and flags - memoized\n  const visibleRoutes = useMemo(() => {\n    return routes.filter((route) => {\n      // Public routes are always visible\n      if (!route.requiredRoles || route.requiredRoles.length === 0) {\n        return true;\n      }\n      // Require authentication for protected routes\n      if (!isAuthenticated) {\n        return false;\n      }\n      return hasRouteAccess(route, roles);\n    });\n  }, [routes, roles, isAuthenticated]);\n\n  const isHorizontal = direction === 'horizontal';\n\n  // Memoize nav style\n  const navStyle = useMemo(\n    (): CSSProperties => ({\n      display: 'flex',\n      flexDirection: isHorizontal ? 'row' : 'column',\n      gap: isHorizontal ? '0.25rem' : '0.125rem',\n    }),\n    [isHorizontal]\n  );\n\n  // Memoize active state calculator\n  const getIsActive = useCallback(\n    (routePath: string): boolean => {\n      return currentPath === routePath || (currentPath?.startsWith(`${routePath}/`) ?? false);\n    },\n    [currentPath]\n  );\n\n  return (\n    <nav\n      aria-label={isHorizontal ? 'Main navigation' : 'Sidebar navigation'}\n      className={className}\n      style={navStyle}\n    >\n      {visibleRoutes.map((route) => (\n        <NavItem\n          key={route.id}\n          route={route}\n          isActive={getIsActive(route.path)}\n          direction={direction}\n          roles={roles}\n        />\n      ))}\n    </nav>\n  );\n}\n\nMainNav.displayName = 'MainNav';\n"],"names":["iconContainerStyle","badgeStyle","tokens","colorTokens","externalIconStyle","srOnlyStyle","hasRouteAccess","route","roles","role","NavItem","memo","isActive","direction","flagEnabled","useFeatureFlag","isHorizontal","linkStyle","useMemo","content","jsxs","Fragment","jsx","AppLink","MainNav","routes","currentPath","className","isAuthenticated","useAuth","visibleRoutes","navStyle","getIsActive","useCallback","routePath"],"mappings":"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"}