{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "dock",
  "title": "Dock",
  "description": "A floating bar of icon links pinned to the bottom or top of the viewport, with tooltips and an optional reveal after scrolling.",
  "dependencies": [
    "@radix-ui/react-slot"
  ],
  "registryDependencies": [
    "@corsair-ui/utils",
    "@corsair-ui/tooltip",
    "@corsair-ui/use-in-view"
  ],
  "files": [
    {
      "path": "registry/default/ui/dock.tsx",
      "content": "\"use client\";\n\nimport { Slot } from \"@radix-ui/react-slot\";\nimport { createContext, useContext, useSyncExternalStore, type ComponentProps } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useInView } from \"@/registry/default/hooks/use-in-view\";\nimport { cn } from \"@/registry/default/lib/utils\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"@/registry/default/ui/tooltip\";\n\ntype DockPosition = \"bottom\" | \"top\";\n\nconst DockContext = createContext<DockPosition>(\"bottom\");\n\nconst subscribe = () => () => {};\n\n/** False while hydrating server HTML, true once running in the browser. */\nfunction useBrowser() {\n  return useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false\n  );\n}\n\nfunction toLength(value: number | string) {\n  return typeof value === \"number\" ? `${value}px` : value;\n}\n\ninterface DockProps extends ComponentProps<\"nav\"> {\n  /** Shows (`true`) or hides (`false`) the dock, taking over from `showAfter`. */\n  open?: boolean;\n  /**\n   * How far the page has to scroll before the dock shows: px, or any CSS\n   * length such as \"80vh\". 0 shows it all the time.\n   */\n  showAfter?: number | string;\n  /** Which edge of the viewport it floats on. */\n  position?: DockPosition;\n  /** Classes for the fixed wrapper that centres the dock. */\n  wrapperClassName?: string;\n}\n\n/**\n * A floating bar of icon links pinned to the bottom (or top) centre of\n * the viewport. It is a `<nav>` landmark named \"Quick links\". With\n * `showAfter` it stays out of the way until the page has scrolled that far,\n * found with an IntersectionObserver on an invisible marker at the top of\n * the page, never a scroll listener. While hidden it is `inert`, so it\n * cannot be tabbed into or read, and exposes `data-state=\"hidden\"`; it\n * slides in with a transition that `prefers-reduced-motion` turns off.\n *\n * @example\n * <Dock showAfter=\"80vh\">\n *   <DockItem label=\"Home\" asChild>\n *     <a href=\"/\"><HouseIcon /></a>\n *   </DockItem>\n *   <DockSeparator />\n *   <DockItem label=\"Search\" onClick={openSearch}><SearchIcon /></DockItem>\n * </Dock>\n */\nfunction Dock({\n  open,\n  showAfter = 0,\n  position = \"bottom\",\n  \"aria-label\": label = \"Quick links\",\n  className,\n  wrapperClassName,\n  children,\n  ...props\n}: DockProps) {\n  const browser = useBrowser();\n  const watching = open === undefined && showAfter !== 0 && showAfter !== \"0\";\n  // Until the observer answers, assume the page is still at the top.\n  const [observe, markerInView] = useInView<HTMLDivElement>({ initial: true });\n  const supported = browser && typeof IntersectionObserver !== \"undefined\";\n  const visible = open ?? (!watching || !supported || !markerInView);\n  const top = position === \"top\";\n\n  return (\n    <>\n      {watching && supported\n        ? createPortal(\n            <div\n              ref={observe}\n              aria-hidden=\"true\"\n              data-slot=\"dock-sentinel\"\n              className=\"pointer-events-none absolute top-0 left-0 w-px\"\n              style={{ height: toLength(showAfter) }}\n            />,\n            document.body\n          )\n        : null}\n      <div\n        data-slot=\"dock-wrapper\"\n        className={cn(\n          \"pointer-events-none fixed inset-x-0 z-40 flex justify-center\",\n          top ? \"top-6\" : \"bottom-6\",\n          wrapperClassName\n        )}\n      >\n        <nav\n          aria-label={label}\n          data-slot=\"dock\"\n          data-state={visible ? \"visible\" : \"hidden\"}\n          data-position={position}\n          inert={!visible ? true : undefined}\n          className={cn(\n            \"bg-background/80 pointer-events-auto flex items-center gap-1 rounded-full border px-2 py-2 shadow-lg backdrop-blur-[8px]\",\n            \"transition-[transform,opacity] duration-300 ease-out motion-reduce:transition-none\",\n            \"data-[state=hidden]:pointer-events-none data-[state=hidden]:opacity-0\",\n            top\n              ? \"data-[state=hidden]:-translate-y-[calc(100%_+_1.5rem)]\"\n              : \"data-[state=hidden]:translate-y-[calc(100%_+_1.5rem)]\",\n            className\n          )}\n          {...props}\n        >\n          <DockContext.Provider value={position}>{children}</DockContext.Provider>\n        </nav>\n      </div>\n    </>\n  );\n}\n\ninterface DockItemProps extends ComponentProps<\"button\"> {\n  /** Accessible name of the item, also shown as its tooltip. */\n  label: string;\n  /** Render the single child element (a link, for example) as the item instead. */\n  asChild?: boolean;\n}\n\n/**\n * One icon button or link in a Dock. `label` names it for screen readers\n * and shows as a tooltip on hover and on keyboard focus. Icons without a\n * `size-*` class are set to `size-5`; give one its own `size-*` to change it.\n */\nfunction DockItem({ label, asChild = false, className, type, ...props }: DockItemProps) {\n  const position = useContext(DockContext);\n  const Comp = asChild ? Slot : \"button\";\n\n  return (\n    <Tooltip>\n      <TooltipTrigger asChild>\n        <Comp\n          data-slot=\"dock-item\"\n          type={asChild ? type : (type ?? \"button\")}\n          aria-label={label}\n          className={cn(\n            \"text-muted-foreground hover:bg-accent hover:text-accent-foreground inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-full\",\n            \"transition-[color,background-color,box-shadow,transform] outline-none active:scale-95 motion-reduce:transition-none\",\n            \"focus-visible:ring-ring/50 focus-visible:ring-[3px]\",\n            \"aria-[current=page]:text-foreground aria-[current=page]:bg-accent\",\n            \"[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-5\",\n            className\n          )}\n          {...props}\n        />\n      </TooltipTrigger>\n      <TooltipContent side={position === \"top\" ? \"bottom\" : \"top\"} sideOffset={8}>\n        {label}\n      </TooltipContent>\n    </Tooltip>\n  );\n}\n\n/** A thin vertical line between groups of items. */\nfunction DockSeparator({ className, ...props }: ComponentProps<\"div\">) {\n  return (\n    <div\n      role=\"separator\"\n      aria-orientation=\"vertical\"\n      data-slot=\"dock-separator\"\n      className={cn(\"bg-border mx-1 h-6 w-px shrink-0\", className)}\n      {...props}\n    />\n  );\n}\n\nexport { Dock, DockItem, DockSeparator, type DockItemProps, type DockPosition, type DockProps };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}