{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "floating-nav",
  "title": "Floating Nav",
  "description": "Navigation that floats over the page: a pill with a sliding highlight, a capsule that opens into a panel, a bar that shrinks on scroll or a bottom tab bar.",
  "dependencies": [
    "lucide-react",
    "motion"
  ],
  "registryDependencies": [
    "utils",
    "https://miseui.junsu4621.workers.dev/r/motion.json"
  ],
  "files": [
    {
      "path": "registry/miseui/ui/floating-nav.tsx",
      "content": "import type * as React from \"react\"\r\nimport { useId, useState } from \"react\"\r\nimport { LayoutGroup, MotionConfig, motion, useReducedMotion } from \"motion/react\"\r\nimport { SPRING_DEFAULT } from \"@/registry/miseui/lib/motion\"\r\nimport { IslandView } from \"@/registry/miseui/ui/floating-nav-island\"\r\nimport { useNavScroll, type FloatingNavItem } from \"@/registry/miseui/ui/floating-nav-state\"\r\nimport { BarView, PillView, TabsView, type NavViewProps } from \"@/registry/miseui/ui/floating-nav-views\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\ntype FloatingNavVariant = \"pill\" | \"island\" | \"bar\" | \"tabs\"\r\ntype FloatingNavEffect = \"hide\"\r\ntype FloatingNavPosition = \"fixed\" | \"sticky\" | \"absolute\"\r\n\r\ntype Edge = \"floating\" | \"flush\" | \"bottom\"\r\n\r\nconst EDGE: Record<FloatingNavVariant, Edge> = { pill: \"floating\", island: \"floating\", bar: \"flush\", tabs: \"bottom\" }\r\n\r\nconst POSITION_CLASS: Record<FloatingNavPosition, Record<Edge, string>> = {\r\n  fixed: { floating: \"fixed inset-x-0 top-4\", flush: \"fixed inset-x-0 top-0\", bottom: \"fixed inset-x-0 bottom-0\" },\r\n  sticky: { floating: \"sticky top-2\", flush: \"sticky top-0\", bottom: \"sticky bottom-0\" },\r\n  absolute: { floating: \"absolute inset-x-0 top-2\", flush: \"absolute inset-x-0 top-0\", bottom: \"absolute inset-x-0 bottom-0\" },\r\n}\r\n\r\n/** Far enough to clear its own offset from the edge. */\r\nconst HIDDEN_Y: Record<Edge, string> = { floating: \"-160%\", flush: \"-110%\", bottom: \"110%\" }\r\n\r\ntype FloatingNavProps = Omit<React.ComponentProps<\"nav\">, \"onSelect\" | \"aria-label\"> & {\r\n  /** Names the landmark. */\r\n  label: string\r\n  items: readonly FloatingNavItem[]\r\n  /** The current item, for a controlled nav. Usually read from the router. */\r\n  value?: string\r\n  defaultValue?: string\r\n  onValueChange?: (value: string) => void\r\n  /** `pill` links with a sliding highlight, `island` a capsule that opens into a panel, `bar` a top bar that shrinks into a pill, `tabs` a bottom tab bar. */\r\n  variant?: FloatingNavVariant\r\n  /** `hide` leaves on scroll down and returns on scroll up, near the top, and when focus lands inside. */\r\n  effect?: FloatingNavEffect\r\n  /** How it floats. Use `sticky` or `absolute` with `scrollContainer` inside a small scroller. */\r\n  position?: FloatingNavPosition\r\n  /** The element that scrolls, when it is not the window. */\r\n  scrollContainer?: React.RefObject<HTMLElement | null>\r\n  /** Shown by `bar` and `island`. */\r\n  brand?: React.ReactNode\r\n  /** Shown by `bar`. */\r\n  actions?: React.ReactNode\r\n}\r\n\r\nfunction NavView({ variant, compact, ...view }: NavViewProps & { variant: FloatingNavVariant; compact: boolean }) {\r\n  if (variant === \"island\") return <IslandView {...view} />\r\n  if (variant === \"bar\") return <BarView {...view} compact={compact} />\r\n  if (variant === \"tabs\") return <TabsView {...view} />\r\n  return <PillView {...view} />\r\n}\r\n\r\nfunction FloatingNav({\r\n  label,\r\n  items,\r\n  value,\r\n  defaultValue,\r\n  onValueChange,\r\n  variant = \"pill\",\r\n  effect,\r\n  position = \"fixed\",\r\n  scrollContainer,\r\n  brand,\r\n  actions,\r\n  className,\r\n  onFocus,\r\n  onBlur,\r\n  ...props\r\n}: FloatingNavProps) {\r\n  // layoutId is global to the page; without a per-instance group two navs would fight over one highlight.\r\n  const groupId = useId()\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  const [uncontrolled, setUncontrolled] = useState(defaultValue ?? items[0]?.value)\r\n  const [focusInside, setFocusInside] = useState(false)\r\n  const { hidden, compact } = useNavScroll(scrollContainer, effect === \"hide\" || variant === \"bar\")\r\n  // A hidden nav stays in the tab order on purpose: a Tab landing in it brings it back instead of focusing something unseen.\r\n  const away = effect === \"hide\" && hidden && !focusInside\r\n  const edge = EDGE[variant]\r\n  const view = {\r\n    label,\r\n    items,\r\n    current: value !== undefined ? value : uncontrolled,\r\n    onSelect: (next: string) => {\r\n      setUncontrolled(next)\r\n      onValueChange?.(next)\r\n    },\r\n    brand,\r\n    actions,\r\n  }\r\n  return (\r\n    <nav\r\n      data-slot=\"floating-nav\"\r\n      data-variant={variant}\r\n      data-effect={effect}\r\n      data-position={position}\r\n      data-hidden={away ? \"true\" : \"false\"}\r\n      aria-label={label}\r\n      // The empty sides of a centred pill must not catch clicks meant for the page.\r\n      className={cn(\"pointer-events-none z-40 flex min-w-0 justify-center\", POSITION_CLASS[position][edge], className)}\r\n      onFocus={(event) => {\r\n        setFocusInside(true)\r\n        onFocus?.(event)\r\n      }}\r\n      onBlur={(event) => {\r\n        if (!event.currentTarget.contains(event.relatedTarget)) setFocusInside(false)\r\n        onBlur?.(event)\r\n      }}\r\n      {...props}\r\n    >\r\n      <MotionConfig transition={reduceMotion ? { duration: 0 } : SPRING_DEFAULT}>\r\n        <LayoutGroup id={groupId}>\r\n          <motion.div\r\n            data-slot=\"floating-nav-mover\"\r\n            initial={false}\r\n            animate={{ y: away ? HIDDEN_Y[edge] : 0 }}\r\n            className={cn(\"flex w-full min-w-0 justify-center\", away && \"**:pointer-events-none!\")}\r\n          >\r\n            <NavView variant={variant} compact={compact} {...view} />\r\n          </motion.div>\r\n        </LayoutGroup>\r\n      </MotionConfig>\r\n    </nav>\r\n  )\r\n}\r\n\r\nexport { FloatingNav, type FloatingNavProps, type FloatingNavItem, type FloatingNavVariant, type FloatingNavEffect, type FloatingNavPosition }\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/floating-nav-island.tsx",
      "content": "import type * as React from \"react\"\r\nimport { useEffect, useId, useRef, useState } from \"react\"\r\nimport { ChevronDown } from \"lucide-react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\nimport { LINK_BASE_CLASS, NavLink, type NavViewProps } from \"@/registry/miseui/ui/floating-nav-views\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\n/** Half the 44 px capsule. Stays the same when open, so the corners do not change shape as the panel grows. */\r\nconst RADIUS_PX = 22\r\n\r\ninterface IslandPanelProps extends Pick<NavViewProps, \"items\" | \"current\"> {\r\n  id: string\r\n  panelRef: React.RefObject<HTMLDivElement | null>\r\n  animated: boolean\r\n  onChoose: (value: string) => void\r\n}\r\n\r\nfunction IslandPanel({ id, panelRef, animated, items, current, onChoose }: IslandPanelProps) {\r\n  return (\r\n    <motion.div ref={panelRef} id={id} layout={animated ? \"position\" : false} data-slot=\"floating-nav-panel\" initial={animated ? { opacity: 0 } : false} animate={{ opacity: 1 }} className=\"p-2 pt-0\">\r\n      <ul className=\"grid gap-1\">\r\n        {items.map((item) => {\r\n          const active = current === item.value\r\n          return (\r\n            <li key={item.value}>\r\n              <NavLink\r\n                item={item}\r\n                active={active}\r\n                onSelect={onChoose}\r\n                className={cn(\r\n                  \"flex h-10 w-full items-center gap-2 rounded-[14px] px-2 text-start text-[13px] focus-visible:ring-primary-foreground/70\",\r\n                  active ? \"bg-primary-foreground/15 font-medium\" : \"opacity-80 hover:bg-primary-foreground/10 hover:opacity-100\",\r\n                )}\r\n              >\r\n                {item.icon}\r\n                {item.label}\r\n              </NavLink>\r\n            </li>\r\n          )\r\n        })}\r\n      </ul>\r\n    </motion.div>\r\n  )\r\n}\r\n\r\nfunction useIslandOpen(rootRef: React.RefObject<HTMLElement | null>, panelRef: React.RefObject<HTMLElement | null>) {\r\n  const [open, setOpen] = useState(false)\r\n  useEffect(() => {\r\n    if (!open) return\r\n    panelRef.current?.querySelector<HTMLElement>(\"a, button\")?.focus()\r\n    const onPress = (event: PointerEvent) => {\r\n      if (!rootRef.current?.contains(event.target as Node)) setOpen(false)\r\n    }\r\n    document.addEventListener(\"pointerdown\", onPress)\r\n    return () => document.removeEventListener(\"pointerdown\", onPress)\r\n  }, [open, panelRef, rootRef])\r\n  return [open, setOpen] as const\r\n}\r\n\r\ninterface IslandTriggerProps {\r\n  triggerRef: React.RefObject<HTMLButtonElement | null>\r\n  animated: boolean\r\n  open: boolean\r\n  panelId: string\r\n  label: string\r\n  text: string\r\n  brand: React.ReactNode\r\n  onPress: () => void\r\n}\r\n\r\nfunction IslandTrigger({ triggerRef, animated, open, panelId, label, text, brand, onPress }: IslandTriggerProps) {\r\n  return (\r\n    <motion.button\r\n      ref={triggerRef}\r\n      type=\"button\"\r\n      layout={animated ? \"position\" : false}\r\n      aria-expanded={open}\r\n      aria-controls={open ? panelId : undefined}\r\n      aria-label={label}\r\n      onClick={onPress}\r\n      style={{ borderRadius: RADIUS_PX }}\r\n      className={cn(LINK_BASE_CLASS, \"flex h-11 w-full items-center gap-2 px-4 text-[13px] font-medium focus-visible:ring-primary-foreground/70\")}\r\n    >\r\n      {brand ? <span className=\"flex shrink-0 items-center\">{brand}</span> : null}\r\n      <span className=\"min-w-0 flex-1 text-start\">{text}</span>\r\n      <ChevronDown aria-hidden=\"true\" className={cn(\"size-4! opacity-70 transition-[rotate] duration-(--duration-base) ease-(--ease-mise) motion-reduce:transition-none\", open && \"rotate-180\")} />\r\n    </motion.button>\r\n  )\r\n}\r\n\r\nconst CAPSULE_CLASS = \"pointer-events-auto h-fit self-start overflow-hidden bg-primary text-primary-foreground shadow-lg shadow-black/10 dark:shadow-black/40\"\r\n\r\nexport function IslandView({ label, items, current, onSelect, brand }: NavViewProps) {\r\n  const panelId = useId()\r\n  const rootRef = useRef<HTMLDivElement>(null)\r\n  const triggerRef = useRef<HTMLButtonElement>(null)\r\n  const panelRef = useRef<HTMLDivElement>(null)\r\n  const [open, setOpen] = useIslandOpen(rootRef, panelRef)\r\n  const animated = !(useReducedMotion() ?? false)\r\n  const currentLabel = items.find((item) => item.value === current)?.label ?? label\r\n  const close = () => {\r\n    setOpen(false)\r\n    triggerRef.current?.focus()\r\n  }\r\n  return (\r\n    // Keeps the capsule's height in the flow. The panel opens over the page, so nothing around it moves.\r\n    <div data-slot=\"floating-nav-island-slot\" className=\"flex h-11 w-full min-w-0 justify-center\">\r\n      <motion.div\r\n        ref={rootRef}\r\n        layout={animated}\r\n        data-slot=\"floating-nav-island\"\r\n        data-state={open ? \"open\" : \"closed\"}\r\n        style={{ borderRadius: RADIUS_PX }}\r\n        className={cn(CAPSULE_CLASS, open ? \"w-72 max-w-[calc(100%-8px)]\" : \"w-fit max-w-full\")}\r\n        onKeyDown={(event) => {\r\n          if (event.key !== \"Escape\" || !open) return\r\n          event.stopPropagation()\r\n          close()\r\n        }}\r\n        onBlur={(event) => {\r\n          if (open && event.relatedTarget && !event.currentTarget.contains(event.relatedTarget)) setOpen(false)\r\n        }}\r\n      >\r\n        <IslandTrigger triggerRef={triggerRef} animated={animated} open={open} panelId={panelId} label={`${label} menu, ${currentLabel}`} text={currentLabel} brand={brand} onPress={() => setOpen(!open)} />\r\n        {open ? (\r\n          <IslandPanel\r\n            id={panelId}\r\n            panelRef={panelRef}\r\n            animated={animated}\r\n            items={items}\r\n            current={current}\r\n            onChoose={(value) => {\r\n              onSelect(value)\r\n              close()\r\n            }}\r\n          />\r\n        ) : null}\r\n      </motion.div>\r\n    </div>\r\n  )\r\n}\r\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/floating-nav-state.ts",
      "content": "import type * as React from \"react\"\nimport { useEffect, useState } from \"react\"\n\nexport interface FloatingNavItem {\n  value: string\n  label: string\n  /** Renders a link. Without it the item is a button and only reports the choice. */\n  href?: string\n  icon?: React.ReactNode\n}\n\nexport interface ScrollStep {\n  hidden: boolean\n  lastY: number\n}\n\n/** Near the top the nav always shows: the start of a page is where people look for it. */\nexport const TOP_ZONE_PX = 64\n/** Past this the `bar` variant shrinks, so a stray wheel notch at the top does not make it jump. */\nexport const COMPACT_AFTER_PX = 32\n/** Less than this is a trembling finger, not a direction. */\nconst JITTER_PX = 6\n\n/** Clamped first, because rubber-banding past either end of a page reports a false change of direction. */\nexport function nextScrollStep(step: ScrollStep, y: number, maxY: number): ScrollStep {\n  const clamped = Math.min(Math.max(y, 0), Math.max(maxY, 0))\n  if (clamped <= TOP_ZONE_PX) return { hidden: false, lastY: clamped }\n  const delta = clamped - step.lastY\n  if (Math.abs(delta) < JITTER_PX) return step\n  return { hidden: delta > 0, lastY: clamped }\n}\n\nfunction readScroll(element: HTMLElement | null) {\n  if (element) return { y: element.scrollTop, maxY: element.scrollHeight - element.clientHeight }\n  return { y: window.scrollY, maxY: document.documentElement.scrollHeight - window.innerHeight }\n}\n\n/** State only flips when an answer changes, so a scroll stream does not re-render anything. */\nexport function useNavScroll(scrollContainer: React.RefObject<HTMLElement | null> | undefined, watching: boolean) {\n  const [view, setView] = useState({ hidden: false, compact: false })\n  useEffect(() => {\n    if (!watching) return\n    const element = scrollContainer?.current ?? null\n    const target: HTMLElement | Window = element ?? window\n    const start = readScroll(element)\n    const stepRef = { current: { hidden: false, lastY: start.y } as ScrollStep }\n    const onScroll = () => {\n      const { y, maxY } = readScroll(element)\n      stepRef.current = nextScrollStep(stepRef.current, y, maxY)\n      const { hidden } = stepRef.current\n      const compact = y > COMPACT_AFTER_PX\n      setView((previous) => (previous.hidden === hidden && previous.compact === compact ? previous : { hidden, compact }))\n    }\n    onScroll()\n    target.addEventListener(\"scroll\", onScroll, { passive: true })\n    return () => target.removeEventListener(\"scroll\", onScroll)\n  }, [scrollContainer, watching])\n  return view\n}\n",
      "type": "registry:ui"
    },
    {
      "path": "registry/miseui/ui/floating-nav-views.tsx",
      "content": "import type * as React from \"react\"\r\nimport { motion, useReducedMotion } from \"motion/react\"\r\nimport type { FloatingNavItem } from \"@/registry/miseui/ui/floating-nav-state\"\r\nimport { cn } from \"@/registry/miseui/lib/utils\"\r\n\r\nexport interface NavViewProps {\r\n  label: string\r\n  items: readonly FloatingNavItem[]\r\n  current: string | undefined\r\n  onSelect: (value: string) => void\r\n  brand?: React.ReactNode\r\n  actions?: React.ReactNode\r\n}\r\n\r\n/** Solid for people who ask for less transparency, and a page's text never shows through it. */\r\nexport const GLASS_CLASS = [\r\n  \"bg-background/85 backdrop-blur-lg backdrop-saturate-150\",\r\n  \"[@media(prefers-reduced-transparency:reduce)]:bg-background [@media(prefers-reduced-transparency:reduce)]:backdrop-blur-none\",\r\n].join(\" \")\r\n\r\n/** Inset, because the rows sit in scrollers and clipped capsules that would cut an outer ring. */\r\nexport const LINK_BASE_CLASS = [\r\n  \"outline-none select-none whitespace-nowrap\",\r\n  \"transition-[color,background-color,scale] duration-(--duration-fast) ease-(--ease-mise)\",\r\n  \"focus-visible:ring-2 focus-visible:ring-ring/70 focus-visible:ring-inset\",\r\n  \"active:scale-[0.97] active:duration-100 motion-reduce:active:scale-100\",\r\n  \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5\",\r\n].join(\" \")\r\n\r\nconst SCROLL_ROW_CLASS = \"flex min-w-0 gap-1 overflow-x-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden\"\r\n\r\ninterface NavLinkProps {\r\n  item: FloatingNavItem\r\n  active: boolean\r\n  onSelect: (value: string) => void\r\n  className?: string\r\n  children: React.ReactNode\r\n}\r\n\r\nexport function NavLink({ item, active, onSelect, className, children }: NavLinkProps) {\r\n  const shared = {\r\n    \"data-slot\": \"floating-nav-link\",\r\n    \"data-active\": active ? \"true\" : undefined,\r\n    \"aria-current\": active ? (\"page\" as const) : undefined,\r\n    className: cn(LINK_BASE_CLASS, className),\r\n  }\r\n  if (item.href) {\r\n    return (\r\n      <a {...shared} href={item.href} onClick={() => onSelect(item.value)}>\r\n        {children}\r\n      </a>\r\n    )\r\n  }\r\n  return (\r\n    <button type=\"button\" {...shared} onClick={() => onSelect(item.value)}>\r\n      {children}\r\n    </button>\r\n  )\r\n}\r\n\r\nfunction Highlight({ className }: { className: string }) {\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  return (\r\n    <motion.span\r\n      aria-hidden=\"true\"\r\n      data-slot=\"floating-nav-highlight\"\r\n      layoutId={reduceMotion ? undefined : \"floating-nav-highlight\"}\r\n      className={cn(\"absolute -z-10\", className)}\r\n    />\r\n  )\r\n}\r\n\r\nexport function PillView({ items, current, onSelect }: NavViewProps) {\r\n  return (\r\n    <div data-slot=\"floating-nav-pill\" className={cn(\"pointer-events-auto flex max-w-full min-w-0 rounded-full p-1 shadow-md ring-1 shadow-black/5 ring-foreground/10 dark:shadow-black/40\", GLASS_CLASS)}>\r\n      {/* A scroller, so the row itself is not rounded: only the bar around it is. */}\r\n      <ul className={SCROLL_ROW_CLASS}>\r\n        {items.map((item) => (\r\n          <li key={item.value} className=\"shrink-0\">\r\n            <NavLink\r\n              item={item}\r\n              active={current === item.value}\r\n              onSelect={onSelect}\r\n              className={cn(\r\n                \"relative isolate inline-flex h-8 items-center gap-2 rounded-full px-3 text-[13px] font-medium pointer-coarse:h-10\",\r\n                current === item.value ? \"text-foreground\" : \"text-muted-foreground hover:text-foreground\",\r\n              )}\r\n            >\r\n              {current === item.value ? <Highlight className=\"inset-0 rounded-full bg-mise-soft\" /> : null}\r\n              {item.label}\r\n            </NavLink>\r\n          </li>\r\n        ))}\r\n      </ul>\r\n    </div>\r\n  )\r\n}\r\n\r\nfunction BarLinks({ items, current, onSelect }: Pick<NavViewProps, \"items\" | \"current\" | \"onSelect\">) {\r\n  return items.map((item) => {\r\n    const active = current === item.value\r\n    return (\r\n      // Margin instead of justify-end, which would push overflow off the start where it cannot be scrolled to.\r\n      <li key={item.value} className=\"shrink-0 first:ms-auto\">\r\n        <NavLink\r\n          item={item}\r\n          active={active}\r\n          onSelect={onSelect}\r\n          className={cn(\r\n            \"relative inline-flex h-10 items-center rounded-md px-2 text-[13px] font-medium\",\r\n            active ? \"text-foreground after:absolute after:inset-x-2 after:bottom-1 after:h-0.5 after:rounded-full after:bg-mise\" : \"text-muted-foreground hover:text-foreground\",\r\n          )}\r\n        >\r\n          {item.label}\r\n        </NavLink>\r\n      </li>\r\n    )\r\n  })\r\n}\r\n\r\nconst BAR_SHAPE_CLASS = {\r\n  compact: \"mt-2 h-11 w-fit max-w-[calc(100%-16px)] px-2 shadow-md ring-1 shadow-black/5 ring-foreground/10 dark:shadow-black/40\",\r\n  full: \"h-14 w-full px-3 shadow-[0_1px_0_var(--border)]\",\r\n}\r\n\r\nexport function BarView({ items, current, onSelect, brand, actions, compact }: NavViewProps & { compact: boolean }) {\r\n  const reduceMotion = useReducedMotion() ?? false\r\n  const layout = reduceMotion ? false : \"position\"\r\n  return (\r\n    // Holds the full height at all times: the page below never moves when the bar shrinks.\r\n    <div data-slot=\"floating-nav-bar-slot\" className=\"flex h-14 w-full min-w-0 justify-center\">\r\n      <motion.div\r\n        layout={!reduceMotion}\r\n        data-slot=\"floating-nav-bar\"\r\n        data-compact={compact ? \"true\" : \"false\"}\r\n        initial={false}\r\n        animate={{ borderRadius: compact ? 22 : 0 }}\r\n        className={cn(\"pointer-events-auto flex min-w-0 items-center gap-2 transition-shadow duration-(--duration-base) ease-(--ease-mise)\", GLASS_CLASS, compact ? BAR_SHAPE_CLASS.compact : BAR_SHAPE_CLASS.full)}\r\n      >\r\n        {brand ? (\r\n          <motion.div layout={layout} className=\"flex shrink-0 items-center gap-2 text-[15px] font-medium text-foreground\">\r\n            {brand}\r\n          </motion.div>\r\n        ) : null}\r\n        <motion.ul layout={layout} className={cn(SCROLL_ROW_CLASS, !compact && \"flex-1\")}>\r\n          <BarLinks items={items} current={current} onSelect={onSelect} />\r\n        </motion.ul>\r\n        {actions ? (\r\n          <motion.div layout={layout} className=\"flex shrink-0 items-center gap-1\">\r\n            {actions}\r\n          </motion.div>\r\n        ) : null}\r\n      </motion.div>\r\n    </div>\r\n  )\r\n}\r\n\r\nexport function TabsView({ items, current, onSelect }: NavViewProps) {\r\n  return (\r\n    <div data-slot=\"floating-nav-tabs\" className={cn(\"pointer-events-auto w-full min-w-0 pb-[env(safe-area-inset-bottom)] shadow-[0_-1px_0_var(--border)]\", GLASS_CLASS)}>\r\n      {/* Items share the width, so a tab bar never scrolls and each column stays a large target. */}\r\n      <ul className=\"grid\" style={{ gridTemplateColumns: `repeat(${items.length}, minmax(0, 1fr))` }}>\r\n        {items.map((item) => {\r\n          const active = current === item.value\r\n          return (\r\n            <li key={item.value} className=\"min-w-0\">\r\n              <NavLink\r\n                item={item}\r\n                active={active}\r\n                onSelect={onSelect}\r\n                className={cn(\"flex h-14 w-full flex-col items-center justify-center gap-1 text-xs\", active ? \"font-medium text-foreground\" : \"text-muted-foreground hover:text-foreground\")}\r\n              >\r\n                <span aria-hidden=\"true\" className=\"relative isolate flex h-7 w-12 items-center justify-center\">\r\n                  {active ? <Highlight className=\"inset-0 rounded-full bg-mise-soft\" /> : null}\r\n                  {item.icon}\r\n                </span>\r\n                {item.label}\r\n              </NavLink>\r\n            </li>\r\n          )\r\n        })}\r\n      </ul>\r\n    </div>\r\n  )\r\n}\r\n",
      "type": "registry:ui"
    }
  ],
  "categories": [
    "navigation"
  ],
  "type": "registry:ui"
}