{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "navigation-bar",
  "title": "Navigation Bar",
  "description": "The bottom navigation bar, with the M3 Expressive active indicator.",
  "registryDependencies": [
    "https://guillermo-rebolledo.github.io/materialcn/r/materialcn-theme.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/navigation-context.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/tooltip.json",
    "https://guillermo-rebolledo.github.io/materialcn/r/utils.json"
  ],
  "files": [
    {
      "path": "src/components/ui/navigation-bar.tsx",
      "content": "import { Children, useEffect, useLayoutEffect, useRef, useState, type ComponentProps, type CSSProperties, type KeyboardEvent, type MouseEvent, type ReactNode } from \"react\"\n\nimport { cn } from \"@/lib/utils\"\nimport { NavigationContext, useNavigation, type NavigationItemLayout } from \"./navigation-context\"\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"./tooltip\"\n\ntype NavigationOrientation = \"horizontal\" | \"vertical\"\n\n/**\n * Material 3 navigation bar.\n *\n * Kit geometry (`Navigation Bar: Vertical items` / `Horizontal items` on the\n * Navigation page): a 64dp Surface Container bar. Each destination is 64dp\n * tall with a 56×32 Secondary Container indicator pill around the 24dp icon\n * and a label-medium caption 4dp beneath it; the inline variant puts icon and\n * caption together inside a 40dp pill. Hover/focus/press state layers paint on\n * the indicator, not the whole destination, and the selected caption uses the\n * Secondary role.\n *\n * The vertical orientation is the compact navigation rail's destination\n * column: 96dp wide with 4dp between destinations.\n */\ntype NavigationBarProps = ComponentProps<\"nav\"> & {\n  itemLayout?: NavigationItemLayout\n  onValueChange: (value: string) => void\n  orientation?: NavigationOrientation\n  value: string\n}\n\nfunction NavigationBar({\n  \"aria-label\": ariaLabel = \"Primary navigation\",\n  children,\n  className,\n  itemLayout = \"stacked\",\n  onKeyDown,\n  onValueChange,\n  orientation = \"horizontal\",\n  ref,\n  value,\n  ...props\n}: NavigationBarProps) {\n  const [focusValue, setFocusValue] = useState<string | null>(null)\n  const rootRef = useRef<HTMLElement>(null)\n  const ensureTabStop = () => {\n    const items = Array.from(\n      rootRef.current?.querySelectorAll<HTMLElement>('[data-slot=\"navigation-bar-item\"]:not(:disabled):not([aria-disabled=\"true\"])') ?? [],\n    )\n    if (!items.length) return\n    const active = items.find((item) => item.tabIndex === 0) ?? items[0]\n    items.forEach((item) => {\n      item.tabIndex = item === active ? 0 : -1\n    })\n  }\n\n  useLayoutEffect(ensureTabStop)\n  useEffect(() => {\n    if (!rootRef.current) return\n    const observer = new MutationObserver(ensureTabStop)\n    observer.observe(rootRef.current, {\n      attributeFilter: [\"aria-disabled\", \"disabled\"],\n      attributes: true,\n      childList: true,\n      subtree: true,\n    })\n    return () => observer.disconnect()\n  }, [])\n  const handleKeyDown = (event: KeyboardEvent<HTMLElement>) => {\n    onKeyDown?.(event)\n    if (event.defaultPrevented) return\n    const forward = orientation === \"horizontal\" ? \"ArrowRight\" : \"ArrowDown\"\n    const backward = orientation === \"horizontal\" ? \"ArrowLeft\" : \"ArrowUp\"\n    if (![forward, backward, \"Home\", \"End\"].includes(event.key)) return\n    const items = Array.from(\n      event.currentTarget.querySelectorAll<HTMLElement>('[data-slot=\"navigation-bar-item\"]:not(:disabled):not([aria-disabled=\"true\"])'),\n    )\n    if (!items.length) return\n    event.preventDefault()\n    const index = items.indexOf(document.activeElement as HTMLElement)\n    const next = event.key === \"Home\"\n      ? 0\n      : event.key === \"End\"\n        ? items.length - 1\n        : event.key === forward\n          ? (index + 1) % items.length\n          : (index - 1 + items.length) % items.length\n    items[next]?.focus()\n  }\n\n  return (\n    <NavigationContext.Provider value={{ focusValue: focusValue ?? value, itemLayout, onValueChange, setFocusValue, value }}>\n      <nav\n        {...props}\n        ref={(node) => {\n          rootRef.current = node\n          if (typeof ref === \"function\") ref(node)\n          else if (ref) ref.current = node\n        }}\n        aria-label={ariaLabel}\n        aria-orientation={orientation}\n        data-slot=\"navigation-bar\"\n        data-orientation={orientation}\n        data-item-layout={itemLayout}\n        data-count={Children.count(children)}\n        className={cn(\n          \"flex bg-m3-surface-container text-foreground\",\n          \"data-[orientation=horizontal]:h-16 data-[orientation=horizontal]:w-full data-[orientation=horizontal]:items-stretch\",\n          \"data-[orientation=horizontal]:data-[item-layout=inline]:justify-center data-[orientation=horizontal]:data-[item-layout=inline]:gap-5\",\n          \"data-[orientation=vertical]:w-24 data-[orientation=vertical]:flex-col data-[orientation=vertical]:gap-1\",\n          className,\n        )}\n        onKeyDown={handleKeyDown}\n      >\n        {children}\n      </nav>\n    </NavigationContext.Provider>\n  )\n}\n\ntype NavigationBarItemProps = {\n  badge?: ReactNode\n  className?: string\n  disabled?: boolean\n  href?: string\n  id?: string\n  icon: ReactNode\n  label: string\n  onClick?: (event: MouseEvent<HTMLElement>) => void\n  showLabel?: boolean\n  style?: CSSProperties\n  title?: string\n  tooltip?: ReactNode\n  tooltipDisabled?: boolean\n  value: string\n}\n\nfunction NavigationBarItem({\n  badge,\n  className,\n  disabled,\n  href,\n  id,\n  icon,\n  label,\n  onClick,\n  showLabel = true,\n  style,\n  title,\n  tooltip,\n  tooltipDisabled = false,\n  value,\n}: NavigationBarItemProps) {\n  const navigation = useNavigation()\n  const layout = navigation.itemLayout ?? \"stacked\"\n  const stacked = layout === \"stacked\"\n  const selected = navigation.value === value\n  const tabbable = !disabled && (navigation.focusValue ?? navigation.value) === value\n  const itemClassName = cn(\n    \"group/navigation-item relative flex min-w-0 flex-col items-center justify-center gap-1 text-m3-label-md text-muted-foreground outline-none\",\n    \"focus-visible:ring-3 focus-visible:ring-inset focus-visible:ring-m3-secondary\",\n    \"disabled:pointer-events-none disabled:text-m3-on-surface/38 aria-disabled:pointer-events-none aria-disabled:text-m3-on-surface/38\",\n    \"in-data-[orientation=horizontal]:flex-1\",\n    \"in-data-[orientation=vertical]:w-full in-data-[orientation=vertical]:flex-none\",\n    layout === \"row\"\n      ? \"min-h-14 rounded-full\"\n      : cn(\"min-h-16 rounded-m3-md\", stacked && showLabel ? \"py-1.5\" : \"py-1\"),\n    layout === \"inline\" && \"in-data-[orientation=horizontal]:flex-none\",\n    className,\n  )\n  const handleClick = (event: MouseEvent<HTMLElement>) => {\n    if (disabled) {\n      event.preventDefault()\n      return\n    }\n    onClick?.(event)\n    if (!event.defaultPrevented) navigation.onValueChange(value)\n  }\n  const content = (\n    <>\n      <span\n        data-slot=\"navigation-bar-indicator\"\n        className={cn(\n          \"relative isolate flex shrink-0 items-center justify-center rounded-full\",\n          \"transition-colors duration-(--m3-spring-effects-fast-duration) ease-(--m3-spring-effects-fast) motion-reduce:transition-none\",\n          // State layer: the content colour washed over the indicator at the\n          // kit's 8 / 10 / 10 % — it never spreads to the whole destination.\n          \"after:pointer-events-none after:absolute after:inset-0 after:-z-10 after:rounded-[inherit] after:bg-current after:opacity-0\",\n          \"after:transition-opacity after:duration-(--m3-spring-effects-fast-duration) after:ease-(--m3-spring-effects-fast)\",\n          \"group-hover/navigation-item:after:opacity-8 group-focus-visible/navigation-item:after:opacity-10 group-active/navigation-item:after:opacity-10\",\n          \"group-data-selected/navigation-item:bg-m3-secondary-container group-data-selected/navigation-item:text-m3-on-secondary-container\",\n          stacked && (showLabel ? \"h-8 w-14\" : \"size-14\"),\n          layout === \"inline\" && \"h-10 gap-1 px-4\",\n          layout === \"row\" && \"h-14 w-full justify-start gap-2 px-4 text-m3-label-lg\",\n        )}\n      >\n        <span className=\"relative flex shrink-0 [&>svg]:size-6\">\n          {icon}\n          {badge && <span className=\"absolute -top-1 left-4\">{badge}</span>}\n        </span>\n        {!stacked && showLabel && <span className=\"min-w-0 truncate\">{label}</span>}\n      </span>\n      {stacked && showLabel && (\n        <span className=\"max-w-full truncate px-1 group-data-selected/navigation-item:text-m3-secondary\">{label}</span>\n      )}\n    </>\n  )\n\n  const item = href ? (\n      <a\n        href={disabled ? undefined : href}\n        id={id}\n        style={style}\n        title={title}\n        aria-current={selected ? \"page\" : undefined}\n        aria-disabled={disabled || undefined}\n        data-selected={selected || undefined}\n        data-value={value}\n        data-slot=\"navigation-bar-item\"\n        className={itemClassName}\n        tabIndex={tabbable ? 0 : -1}\n        onClick={handleClick}\n        onFocus={() => navigation.setFocusValue?.(value)}\n      >\n        {content}\n      </a>\n  ) : (\n    <button\n      type=\"button\"\n      id={id}\n      style={style}\n      title={title}\n      aria-current={selected ? \"page\" : undefined}\n      data-selected={selected || undefined}\n      data-value={value}\n      data-slot=\"navigation-bar-item\"\n      className={itemClassName}\n      disabled={disabled}\n      tabIndex={tabbable ? 0 : -1}\n      onClick={handleClick}\n      onFocus={() => navigation.setFocusValue?.(value)}\n    >\n      {content}\n    </button>\n  )\n\n  if (!tooltip) return item\n  return (\n    <Tooltip disabled={tooltipDisabled}>\n      <TooltipTrigger render={item} disabled={tooltipDisabled} />\n      {!tooltipDisabled && <TooltipContent side=\"right\">{tooltip}</TooltipContent>}\n    </Tooltip>\n  )\n}\n\nexport {\n  NavigationBar,\n  NavigationBarItem,\n  type NavigationBarItemProps,\n  type NavigationBarProps,\n  type NavigationOrientation,\n}\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}