{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navbar",
  "title": "Navbar",
  "description": "Government website header with a utility bar, brand, navigation and actions.",
  "dependencies": ["lucide-react"],
  "registryDependencies": [
    "https://indiacn.in/r/theme.json",
    "https://indiacn.in/r/typography.json",
    "https://indiacn.in/r/dropdown.json",
    "https://indiacn.in/r/accessibility-widget.json"
  ],
  "files": [
    {
      "path": "components/ui/navbar.tsx",
      "content": "'use client';\n\nimport {\n  AArrowDown,\n  AArrowUp,\n  ALargeSmall,\n  ChevronDown,\n  ExternalLink,\n  Globe,\n  Menu,\n  Moon,\n  Sun,\n  X,\n} from 'lucide-react';\nimport {\n  ComponentProps,\n  ReactNode,\n  createContext,\n  useCallback,\n  useContext,\n  useId,\n  useMemo,\n  useState,\n  useSyncExternalStore,\n} from 'react';\n\nimport { useAccessibilitySettings } from '@/components/ui/accessibility-widget';\nimport { Dropdown, DropdownContent, DropdownItem, DropdownTrigger } from '@/components/ui/dropdown';\nimport { Label1, Label2, Label3 } from '@/components/ui/typography';\nimport { cn } from '@/lib/utils';\n\n/*\n * UX4G government header: a 41px primary utility bar over a 72px white bar.\n * The utility bar holds the Government of India mark and whatever site-wide\n * controls a site offers; the main bar holds brand, navigation and actions.\n */\ninterface INavbarContext {\n  open: boolean;\n  navId: string;\n  toggle: () => void;\n  close: () => void;\n}\n\nconst NAVBAR_CONTEXT = createContext<INavbarContext | null>(null);\n\n/** Reads the nearest Navbar context, or fails loudly rather than silently. */\nfunction useNavbar() {\n  const context = useContext(NAVBAR_CONTEXT);\n  if (!context) throw new Error('Navbar parts must be rendered inside <Navbar>');\n  return context;\n}\n\n/** Government website header. */\nfunction Navbar({ className, children, ...props }: ComponentProps<'header'>) {\n  const navId = useId();\n  const [open, setOpen] = useState(false);\n  const toggle = useCallback(() => setOpen(o => !o), []);\n  const close = useCallback(() => setOpen(false), []);\n  const value = useMemo(() => ({ open, navId, toggle, close }), [open, navId, toggle, close]);\n\n  return (\n    <NAVBAR_CONTEXT.Provider value={value}>\n      <header className={cn('bg-neutral-0 w-full', className)} {...props}>\n        {children}\n      </header>\n    </NAVBAR_CONTEXT.Provider>\n  );\n}\n\n/**\n * Primary bar above the main header.\n *\n * `start` defaults to the Government of India mark, which is the whole point\n * of this bar on a government site.\n */\nfunction NavbarUtilityBar({\n  className,\n  children,\n  start,\n  ...props\n}: ComponentProps<'div'> & { start?: ReactNode }) {\n  return (\n    <div className={cn('bg-primary text-primary-foreground', className)} {...props}>\n      <div className='mx-auto flex min-h-10 max-w-[1440px] flex-wrap items-center justify-between gap-x-6 gap-y-1 px-4 py-1.5 sm:px-6'>\n        {start ?? <NavbarGovBadge />}\n        <div className='flex items-center gap-3'>{children}</div>\n      </div>\n    </div>\n  );\n}\n\n/** The Government of India mark, linking out to india.gov.in by default. */\nfunction NavbarGovBadge({\n  className,\n  href = 'https://www.india.gov.in',\n  label = 'Government of India',\n  ...props\n}: ComponentProps<'a'> & { label?: string }) {\n  return (\n    <a\n      href={href}\n      target='_blank'\n      rel='noopener noreferrer'\n      className={cn(\n        'focus-visible:shadow-focus-neutral inline-flex items-center gap-2 no-underline focus-visible:outline-none',\n        className,\n      )}\n      {...props}\n    >\n      {/* Drawn from a theme token rather than shipped as an asset, so the\n          component needs no image file to install. */}\n      <div\n        aria-hidden\n        className='h-[15px] w-[22px] shrink-0 rounded-[2px] bg-[linear-gradient(to_bottom,var(--color-flag-band-top)_0_33.34%,var(--color-flag-band-middle)_33.34%_66.67%,var(--color-flag-band-bottom)_66.67%_100%)]'\n      />\n      <Label2 className='font-medium'>{label}</Label2>\n      <ExternalLink className='size-3.5 shrink-0' aria-hidden />\n    </a>\n  );\n}\n\n/**\n * Skip link. Hidden until focused, which is the only reason it works — it has\n * to be the first thing a keyboard user reaches.\n */\nfunction NavbarSkipLink({\n  className,\n  href = '#main',\n  children = 'Skip to main content',\n  ...props\n}: ComponentProps<'a'>) {\n  return (\n    <a\n      href={href}\n      className={cn(\n        'focus-visible:shadow-focus-neutral rounded-sm underline decoration-transparent transition-colors hover:decoration-current focus-visible:outline-none',\n        'sr-only focus:not-sr-only focus:relative',\n        className,\n      )}\n      {...props}\n    >\n      <Label2>{children}</Label2>\n    </a>\n  );\n}\n\n/** Thin divider between utility-bar controls. */\nfunction NavbarSeparator({ className, ...props }: ComponentProps<'div'>) {\n  return (\n    <div\n      aria-hidden\n      className={cn('bg-primary-foreground/30 hidden h-5 w-px sm:block', className)}\n      {...props}\n    />\n  );\n}\n\nconst UTILITY_CONTROL =\n  'focus-visible:shadow-focus-neutral inline-flex cursor-pointer items-center justify-center rounded-md transition-colors focus-visible:outline-none';\n\n/**\n * Text-size controls, as A-minus / A / A-plus.\n *\n * Wired to the same store as the accessibility panel, so the two cannot\n * disagree about how large the page currently is.\n */\nfunction NavbarTextSize({ className, ...props }: ComponentProps<'div'>) {\n  const { settings, setStep, stepBy, maxStep } = useAccessibilitySettings();\n  const step = settings.textStep;\n\n  const decrease = useCallback(() => stepBy('textStep', -1), [stepBy]);\n  const reset = useCallback(() => setStep('textStep', 0), [setStep]);\n  const increase = useCallback(() => stepBy('textStep', 1), [stepBy]);\n\n  return (\n    <div\n      role='group'\n      aria-label='Text size'\n      className={cn('flex items-center gap-1', className)}\n      {...props}\n    >\n      <button\n        type='button'\n        onClick={decrease}\n        disabled={step === 0}\n        aria-label='Decrease text size'\n        className={cn(UTILITY_CONTROL, 'hover:bg-primary-foreground/15 size-8 disabled:opacity-40')}\n      >\n        <AArrowDown className='size-4' aria-hidden />\n      </button>\n      <button\n        type='button'\n        onClick={reset}\n        aria-label={`Reset text size. Currently level ${step} of ${maxStep}`}\n        className={cn(\n          UTILITY_CONTROL,\n          'size-8',\n          step === 0 ? 'hover:bg-primary-foreground/15' : 'bg-primary-foreground/20',\n        )}\n      >\n        <ALargeSmall className='size-4' aria-hidden />\n      </button>\n      <button\n        type='button'\n        onClick={increase}\n        disabled={step === maxStep}\n        aria-label='Increase text size'\n        className={cn(UTILITY_CONTROL, 'hover:bg-primary-foreground/15 size-8 disabled:opacity-40')}\n      >\n        <AArrowUp className='size-4' aria-hidden />\n      </button>\n    </div>\n  );\n}\n\ninterface INavbarThemeToggleProps extends Omit<ComponentProps<'button'>, 'onToggle'> {\n  /** Controlled mode. Omit both to let the toggle manage the `dark` class. */\n  isDark?: boolean;\n  onToggle?: () => void;\n}\n\n/**\n * Light and dark toggle.\n *\n * Uncontrolled it flips the `dark` class on the document element. Pass\n * `isDark` and `onToggle` to defer to an existing theme provider instead —\n * two things writing that class will fight.\n */\n/** Subscribes to `dark` being added or removed on the document element. */\nfunction subscribeDarkClass(listener: () => void) {\n  const observer = new MutationObserver(listener);\n  observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });\n  return () => observer.disconnect();\n}\n\n/** Whether the document is currently in dark mode. */\nconst getDarkClass = () => document.documentElement.classList.contains('dark');\n/** The server cannot know the theme, so it renders light. */\nconst getServerDarkClass = () => false;\n\n/**\n * Light and dark toggle.\n *\n * Uncontrolled it flips the `dark` class on the document element. Pass\n * `isDark` and `onToggle` to defer to an existing theme provider instead —\n * two things writing that class will fight each other.\n */\nfunction NavbarThemeToggle({ className, isDark, onToggle, ...props }: INavbarThemeToggleProps) {\n  // Read the class through a store rather than syncing state in an effect, so\n  // an external theme provider flipping it is picked up too.\n  const observedDark = useSyncExternalStore(subscribeDarkClass, getDarkClass, getServerDarkClass);\n  const dark = isDark ?? observedDark;\n\n  const handleClick = useCallback(() => {\n    if (onToggle) {\n      onToggle();\n      return;\n    }\n    const root = document.documentElement;\n    root.classList.toggle('dark', !root.classList.contains('dark'));\n  }, [onToggle]);\n\n  const Icon = dark ? Sun : Moon;\n\n  return (\n    <button\n      type='button'\n      onClick={handleClick}\n      aria-pressed={dark}\n      aria-label={dark ? 'Switch to light theme' : 'Switch to dark theme'}\n      className={cn(UTILITY_CONTROL, 'hover:bg-primary-foreground/15 size-8', className)}\n      {...props}\n    >\n      <Icon className='size-4' aria-hidden />\n    </button>\n  );\n}\n\ninterface INavbarLanguageProps {\n  languages: { code: string; label: string }[];\n  value: string;\n  onSelect: (code: string) => void;\n  className?: string;\n}\n\n/** One language row. Split out so its handler is not rebuilt each render. */\nfunction NavbarLanguageItem({\n  language,\n  selected,\n  onSelect,\n}: {\n  language: { code: string; label: string };\n  selected: boolean;\n  onSelect: (code: string) => void;\n}) {\n  const handleSelect = useCallback(() => onSelect(language.code), [onSelect, language.code]);\n\n  return (\n    <DropdownItem onSelect={handleSelect} aria-current={selected || undefined}>\n      <Label3>{language.label}</Label3>\n    </DropdownItem>\n  );\n}\n\n/** Language selector, as the utility bar's globe and label. */\nfunction NavbarLanguage({ languages, value, onSelect, className }: INavbarLanguageProps) {\n  const current = languages.find(language => language.code === value) ?? languages[0];\n\n  return (\n    <Dropdown>\n      <DropdownTrigger\n        aria-label={`Language: ${current?.label}`}\n        className={cn(\n          UTILITY_CONTROL,\n          'hover:bg-primary-foreground/15 gap-1.5 px-2 py-1.5',\n          className,\n        )}\n      >\n        <Globe className='size-4 shrink-0' aria-hidden />\n        <Label2>{current?.label}</Label2>\n        <ChevronDown className='size-3 shrink-0' aria-hidden />\n      </DropdownTrigger>\n      <DropdownContent align='end'>\n        {languages.map(language => (\n          <NavbarLanguageItem\n            key={language.code}\n            language={language}\n            selected={language.code === value}\n            onSelect={onSelect}\n          />\n        ))}\n      </DropdownContent>\n    </Dropdown>\n  );\n}\n\n/** White bar holding brand, navigation and actions. */\nfunction NavbarMain({ className, children, ...props }: ComponentProps<'div'>) {\n  return (\n    <div className={cn('border-b border-neutral-100', className)} {...props}>\n      <div className='mx-auto flex min-h-[72px] max-w-[1440px] items-center gap-6 px-4 sm:px-6'>\n        {children}\n      </div>\n    </div>\n  );\n}\n\n/** Emblem and wordmark. */\nfunction NavbarBrand({ className, ...props }: ComponentProps<'a'>) {\n  return (\n    <a\n      className={cn(\n        'focus-visible:shadow-focus-primary mr-auto inline-flex shrink-0 items-center gap-3 no-underline focus-visible:outline-none',\n        className,\n      )}\n      {...props}\n    />\n  );\n}\n\n/**\n * Navigation list. Collapses behind NavbarToggle below `lg`, where it becomes a\n * stacked panel under the header rather than an overlay.\n */\nfunction NavbarNav({ className, children, ...props }: ComponentProps<'nav'>) {\n  const { open, navId } = useNavbar();\n\n  return (\n    <nav\n      id={navId}\n      aria-label='Main'\n      className={cn(\n        'lg:static lg:flex lg:w-auto lg:flex-row lg:items-center lg:gap-1 lg:border-0 lg:py-0',\n        open\n          ? 'bg-neutral-0 absolute inset-x-0 top-full z-40 flex w-full flex-col items-stretch gap-1 border-b border-neutral-100 px-4 py-2 sm:px-6'\n          : 'hidden',\n        className,\n      )}\n      {...props}\n    >\n      {children}\n    </nav>\n  );\n}\n\ninterface INavbarLinkProps extends ComponentProps<'a'> {\n  active?: boolean;\n  /** Shows a chevron, for a link that opens a menu. */\n  hasMenu?: boolean;\n}\n\n/** One navigation link. */\nfunction NavbarLink({ className, active, hasMenu, children, ...props }: INavbarLinkProps) {\n  return (\n    <a\n      aria-current={active ? 'page' : undefined}\n      className={cn(\n        'focus-visible:shadow-focus-primary hover:text-primary inline-flex items-center gap-1 rounded-md px-3 py-2 whitespace-nowrap no-underline transition-colors focus-visible:outline-none',\n        active ? 'text-primary font-medium' : 'text-neutral',\n        className,\n      )}\n      {...props}\n    >\n      <Label1 className='font-[inherit]'>{children}</Label1>\n      {hasMenu && <ChevronDown className='size-4 shrink-0' aria-hidden />}\n    </a>\n  );\n}\n\n/** Trailing actions: search, sign in, and so on. */\nfunction NavbarActions({ className, ...props }: ComponentProps<'div'>) {\n  return <div className={cn('flex shrink-0 items-center gap-2', className)} {...props} />;\n}\n\n/** Mobile disclosure button for the navigation. */\nfunction NavbarToggle({ className, ...props }: ComponentProps<'button'>) {\n  const { open, toggle, navId } = useNavbar();\n\n  return (\n    <button\n      type='button'\n      onClick={toggle}\n      aria-expanded={open}\n      aria-controls={navId}\n      aria-label={open ? 'Close menu' : 'Open menu'}\n      className={cn(\n        'focus-visible:shadow-focus-primary text-neutral inline-flex size-10 cursor-pointer items-center justify-center rounded-md border border-neutral-100 transition-colors hover:border-neutral-300 focus-visible:outline-none lg:hidden',\n        className,\n      )}\n      {...props}\n    >\n      {open ? <X className='size-5' aria-hidden /> : <Menu className='size-5' aria-hidden />}\n    </button>\n  );\n}\n\n/** Full-width row beneath the header, for the fill navigation layout. */\nfunction NavbarSecondaryRow({ className, children, ...props }: ComponentProps<'div'>) {\n  return (\n    <div className={cn('hidden border-b border-neutral-100 lg:block', className)} {...props}>\n      <div className='mx-auto flex max-w-[1440px] items-center justify-between px-4 sm:px-6'>\n        {children}\n      </div>\n    </div>\n  );\n}\n\nexport {\n  Navbar,\n  NavbarUtilityBar,\n  NavbarTextSize,\n  NavbarThemeToggle,\n  NavbarLanguage,\n  NavbarGovBadge,\n  NavbarSkipLink,\n  NavbarSeparator,\n  NavbarMain,\n  NavbarBrand,\n  NavbarNav,\n  NavbarLink,\n  NavbarActions,\n  NavbarToggle,\n  NavbarSecondaryRow,\n};\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}
