{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "bottom-bar",
  "type": "registry:ui",
  "title": "BottomBar",
  "description": "Composable bottom work panel with disclosure controls, preset or custom sizing, standalone use and SidebarShell integration.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "https://components.dethink.co.uk/r/dethink-base.json"
  ],
  "files": [
    {
      "path": "packages/components/src/components/bottom-bar/bottom-bar.tsx",
      "type": "registry:file",
      "target": "~/components/dethink/components/bottom-bar/bottom-bar.tsx",
      "content": "import {\n  createContext,\n  forwardRef,\n  useContext,\n  useEffect,\n  useId,\n  useRef,\n  useState,\n  type ButtonHTMLAttributes,\n  type CSSProperties,\n  type HTMLAttributes,\n} from \"react\";\nimport { cn } from \"../../utils/cn\";\nimport { markSidebarShellPart } from \"../../utils/sidebar-shell-part\";\n\nexport type BottomBarSize = \"sm\" | \"md\" | \"lg\";\nexport interface BottomBarProps extends HTMLAttributes<HTMLDivElement> {\n  open?: boolean;\n  defaultOpen?: boolean;\n  onOpenChange?: (open: boolean) => void;\n  size?: BottomBarSize;\n  /** Content height: a CSS length, or pixels when a number. */\n  height?: string | number;\n  /** Maximum total bar height, including its header. Defaults to 40dvh. */\n  maxHeight?: string | number;\n  span?: \"content\" | \"shell\";\n  /** Stable ID shared by the content and every trigger. */\n  contentId?: string;\n}\nexport type BottomBarHeaderProps = HTMLAttributes<HTMLDivElement>;\nexport interface BottomBarTriggerProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n  expandLabel?: string;\n  collapseLabel?: string;\n}\nexport type BottomBarContentProps = Omit<\n  HTMLAttributes<HTMLDivElement>,\n  \"id\" | \"hidden\"\n>;\n\ntype BottomBarState = {\n  open: boolean;\n  setOpen: (open: boolean) => void;\n  contentId: string;\n  setContentFocused: (focused: boolean) => void;\n};\nconst BottomBarContext = createContext<BottomBarState | null>(null);\nfunction useBottomBar() {\n  const context = useContext(BottomBarContext);\n  if (!context)\n    throw new Error(\"BottomBar parts must be used inside BottomBar.\");\n  return context;\n}\nconst sizes: Record<BottomBarSize, string> = {\n  sm: \"10rem\",\n  md: \"16rem\",\n  lg: \"24rem\",\n};\nconst length = (value: string | number) =>\n  typeof value === \"number\" ? `${value}px` : value;\n\nexport const BottomBar = forwardRef<HTMLDivElement, BottomBarProps>(\n  function BottomBar(\n    {\n      open: controlledOpen,\n      defaultOpen = true,\n      onOpenChange,\n      size = \"md\",\n      height,\n      maxHeight = \"40dvh\",\n      span = \"content\",\n      contentId: providedId,\n      children,\n      className,\n      style,\n      ...props\n    },\n    ref,\n  ) {\n    const [internalOpen, setInternalOpen] = useState(defaultOpen);\n    const open = controlledOpen ?? internalOpen;\n    const generatedId = useId();\n    const contentId = providedId ?? `bottom-bar-${generatedId}`;\n    const rootRef = useRef<HTMLDivElement | null>(null);\n    const contentFocusedRef = useRef(false);\n    const setOpen = (next: boolean) => {\n      if (next === open) return;\n      if (controlledOpen === undefined) setInternalOpen(next);\n      onOpenChange?.(next);\n    };\n    useEffect(() => {\n      if (!open && contentFocusedRef.current) {\n        rootRef.current\n          ?.querySelector<HTMLButtonElement>(\n            '[data-slot=\"bottom-bar-trigger\"]:not(:disabled)',\n          )\n          ?.focus();\n        contentFocusedRef.current = false;\n      }\n    }, [open]);\n    return (\n      <BottomBarContext.Provider\n        value={{\n          open,\n          setOpen,\n          contentId,\n          setContentFocused: (focused) => {\n            contentFocusedRef.current = focused;\n          },\n        }}\n      >\n        <div\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          data-slot=\"bottom-bar\"\n          data-state={open ? \"open\" : \"closed\"}\n          data-size={size}\n          data-span={span}\n          className={cn(\n            \"border-border bg-background text-foreground relative row-start-3 grid max-h-[min(var(--bottom-bar-max-height),var(--bottom-bar-shell-limit,var(--bottom-bar-max-height)))] min-h-0 min-w-0 grid-rows-[auto_minmax(0,1fr)] overflow-hidden border-t data-[span=shell]:col-span-full data-[span=shell]:row-start-2 data-[span=shell]:rounded-xl data-[span=shell]:border\",\n            className,\n          )}\n          style={\n            {\n              \"--bottom-bar-height\": length(height ?? sizes[size]),\n              \"--bottom-bar-max-height\": length(maxHeight),\n              ...style,\n            } as CSSProperties\n          }\n        >\n          {children}\n        </div>\n      </BottomBarContext.Provider>\n    );\n  },\n);\nmarkSidebarShellPart(BottomBar, \"Footer\");\n\nexport const BottomBarHeader = forwardRef<HTMLDivElement, BottomBarHeaderProps>(\n  function BottomBarHeader({ className, ...props }, ref) {\n    const { open } = useBottomBar();\n    return (\n      <div\n        {...props}\n        ref={ref}\n        data-slot=\"bottom-bar-header\"\n        data-state={open ? \"open\" : \"closed\"}\n        className={cn(\n          \"bg-muted/20 flex min-w-0 flex-wrap items-center gap-[var(--dt-density-gap)] px-3 py-[var(--dt-density-gap)]\",\n          className,\n        )}\n      />\n    );\n  },\n);\n\nexport const BottomBarTrigger = forwardRef<\n  HTMLButtonElement,\n  BottomBarTriggerProps\n>(function BottomBarTrigger(\n  {\n    children,\n    className,\n    onClick,\n    expandLabel = \"Expand bottom bar\",\n    collapseLabel = \"Collapse bottom bar\",\n    ...props\n  },\n  ref,\n) {\n  const { open, setOpen, contentId } = useBottomBar();\n  return (\n    <button\n      type=\"button\"\n      aria-label={\n        children == null ? (open ? collapseLabel : expandLabel) : undefined\n      }\n      {...props}\n      ref={ref}\n      data-slot=\"bottom-bar-trigger\"\n      data-state={open ? \"open\" : \"closed\"}\n      aria-controls={contentId}\n      aria-expanded={open}\n      className={cn(\n        \"border-border bg-background hover:bg-muted focus-visible:outline-ring inline-flex min-h-8 min-w-8 shrink-0 items-center justify-center rounded-md border px-2 text-sm focus-visible:outline-2 disabled:pointer-events-none disabled:opacity-50\",\n        className,\n      )}\n      onClick={(event) => {\n        onClick?.(event);\n        if (!event.defaultPrevented) setOpen(!open);\n      }}\n    >\n      {children ?? (\n        <svg\n          aria-hidden=\"true\"\n          width=\"16\"\n          height=\"16\"\n          viewBox=\"0 0 24 24\"\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth=\"1.5\"\n        >\n          <path d={open ? \"m6 9 6 6 6-6\" : \"m6 15 6-6 6 6\"} />\n        </svg>\n      )}\n    </button>\n  );\n});\n\n/* eslint-disable jsx-a11y/no-noninteractive-tabindex -- A bounded scroll region must be focusable for keyboard scrolling. */\nexport const BottomBarContent = forwardRef<\n  HTMLDivElement,\n  BottomBarContentProps\n>(function BottomBarContent(\n  { className, onFocusCapture, onBlurCapture, ...props },\n  ref,\n) {\n  const { open, contentId, setContentFocused } = useBottomBar();\n  return (\n    <div\n      role=\"region\"\n      aria-label=\"Bottom bar content\"\n      tabIndex={0}\n      {...props}\n      ref={ref}\n      id={contentId}\n      hidden={!open}\n      data-slot=\"bottom-bar-content\"\n      data-state={open ? \"open\" : \"closed\"}\n      className={cn(\n        \"border-border focus-visible:ring-ring h-[var(--bottom-bar-height)] max-h-full min-h-0 min-w-0 overflow-auto overscroll-contain border-t p-3 pb-[max(0.75rem,env(safe-area-inset-bottom))] text-sm outline-none focus-visible:ring-2 focus-visible:ring-inset\",\n        className,\n      )}\n      onFocusCapture={(event) => {\n        setContentFocused(true);\n        onFocusCapture?.(event);\n      }}\n      onBlurCapture={(event) => {\n        if (\n          event.relatedTarget &&\n          !event.currentTarget.contains(event.relatedTarget)\n        )\n          setContentFocused(false);\n        onBlurCapture?.(event);\n      }}\n    />\n  );\n});\n\n/* eslint-enable jsx-a11y/no-noninteractive-tabindex */\n"
    },
    {
      "path": "packages/components/src/components/bottom-bar/index.ts",
      "type": "registry:file",
      "target": "~/components/dethink/components/bottom-bar/index.ts",
      "content": "export {\n  BottomBar,\n  BottomBarHeader,\n  BottomBarTrigger,\n  BottomBarContent,\n  type BottomBarProps,\n  type BottomBarHeaderProps,\n  type BottomBarTriggerProps,\n  type BottomBarContentProps,\n  type BottomBarSize,\n} from \"./bottom-bar\";\n"
    },
    {
      "path": "packages/components/src/utils/cn.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/cn.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}\n"
    },
    {
      "path": "packages/components/src/utils/sidebar-shell-part.ts",
      "type": "registry:file",
      "target": "~/components/dethink/utils/sidebar-shell-part.ts",
      "content": "import { isValidElement, type ReactNode } from \"react\";\n\ntype SidebarShellPartName =\n  \"Navigation\" | \"Header\" | \"Main\" | \"Footer\" | \"SkipLink\";\nconst SIDEBAR_SHELL_PART = Symbol.for(\"@dethink/sidebar-shell.part\");\n\nexport function markSidebarShellPart(\n  component: unknown,\n  partName: SidebarShellPartName,\n) {\n  Object.defineProperty(component, SIDEBAR_SHELL_PART, {\n    configurable: true,\n    value: partName,\n  });\n}\n\nexport function getSidebarShellPart(child: ReactNode) {\n  if (!isValidElement(child) || typeof child.type === \"string\")\n    return undefined;\n  return (child.type as { [SIDEBAR_SHELL_PART]?: SidebarShellPartName })[\n    SIDEBAR_SHELL_PART\n  ];\n}\n"
    }
  ]
}
