{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "inspector",
  "type": "registry:component",
  "title": "Inspector",
  "description": "The settings panel beside an editor's canvas: a rail of icons, each opening a page of titled sections.",
  "dependencies": [
    "@base-ui/react",
    "tailwind-variants"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "components/panelui/inspector.tsx",
      "type": "registry:component",
      "target": "@components/panelui/inspector.tsx",
      "content": "'use client';\n\n/**\n * Inspector — the settings panel beside an editor's canvas: a rail of\n * icons, each opening a page of titled sections.\n *\n * ```tsx\n * <Inspector defaultValue=\"pointer\">\n *   <Inspector.Rail>\n *     <Inspector.Tab value=\"pointer\" label=\"Pointer\"><MousePointer2Icon /></Inspector.Tab>\n *     <Inspector.Tab value=\"audio\" label=\"Audio\"><Volume2Icon /></Inspector.Tab>\n *   </Inspector.Rail>\n *   <Inspector.Panel>\n *     <Inspector.Page value=\"pointer\">\n *       <Inspector.Section>\n *         <Inspector.SectionTitle>Click sound</Inspector.SectionTitle>\n *         <Inspector.SectionAction><Switch label=\"Click sound\" /></Inspector.SectionAction>\n *         <Inspector.SectionContent>…</Inspector.SectionContent>\n *       </Inspector.Section>\n *     </Inspector.Page>\n *   </Inspector.Panel>\n * </Inspector>\n * ```\n *\n * ## The rail stands apart\n *\n * The rail is a pill of its own beside the panel, not a strip inside it.\n * It is navigation, and the panel is the thing being navigated; drawn\n * apart, the eye knows which of the two it is looking at. A filled marker\n * slides along the rail to the chosen page, and the page arrives from the\n * direction the marker went.\n *\n * ## Sections carry their own switch\n *\n * A section's title sits on one line with its action: the switch that turns\n * the whole section on, or a Reset that puts its values back. The controls\n * under it are what that switch or reset applies to, so the relation never\n * has to be explained. A section can fold away with `collapsible`.\n */\nimport { Collapsible } from '@base-ui/react/collapsible';\nimport { ScrollArea } from '@base-ui/react/scroll-area';\nimport { Tabs } from '@base-ui/react/tabs';\nimport { Tooltip } from '@base-ui/react/tooltip';\nimport { createContext, useContext, type ComponentProps, type CSSProperties, type ReactNode } from 'react';\nimport { tv } from 'tailwind-variants';\nimport { mergeProps } from '@base-ui/react/merge-props';\nimport { useRender } from '@base-ui/react/use-render';\nimport type { ElementType as RenderElementType } from 'react';\n\n/** Draws a part as another element, or composes it with a component: `render={<a href=\"/\" />}`. */\ntype RenderProp<Tag extends RenderElementType> = { render?: useRender.ComponentProps<Tag>['render'] };\n\nconst EASE = 'ease-[cubic-bezier(0.3,1.2,0.4,1)]';\n\nconst inspectorVariants = tv({\n  slots: {\n    root: 'group/inspector flex items-start gap-2 data-[orientation=vertical]:flex-row',\n    rail: [\n      'relative isolate flex shrink-0 flex-col items-center gap-1 rounded-[20px] bg-popover p-1.5 text-popover-foreground',\n      'shadow-[0_10px_30px_-12px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',\n    ],\n    marker: [\n      'absolute -z-10 rounded-[14px] bg-primary shadow-[0_1px_2px_rgb(0_0_0/0.18),inset_0_1px_0_rgb(255_255_255/0.18)]',\n      'top-(--active-tab-top) left-(--active-tab-left) h-(--active-tab-height) w-(--active-tab-width)',\n      `transition-[top,height] duration-[380ms] ${EASE} motion-reduce:transition-none`,\n    ],\n    tab: [\n      'relative z-10 inline-flex size-10 shrink-0 cursor-pointer items-center justify-center rounded-[14px] outline-none select-none',\n      'text-muted-foreground transition-[color,background-color,scale] duration-150',\n      'hover:bg-foreground/[0.06] hover:text-foreground active:scale-[0.94]',\n      'data-active:bg-transparent data-active:text-primary-foreground data-active:hover:bg-transparent',\n      'focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-inset',\n      'data-disabled:pointer-events-none data-disabled:opacity-40',\n      '[&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*=size-])]:size-[1.125rem]',\n    ],\n    railSeparator: 'my-1 h-px w-5 shrink-0 rounded-full bg-foreground/15',\n    tooltip: [\n      'origin-(--transform-origin) rounded-lg bg-foreground px-2 py-1 text-xs font-medium text-background shadow-md',\n      'transition-[opacity,scale] duration-150 data-starting-style:scale-95 data-starting-style:opacity-0 data-ending-style:opacity-0',\n      'data-instant:duration-0 motion-reduce:transition-none',\n    ],\n    panel: [\n      'relative flex w-72 min-w-0 flex-col overflow-hidden rounded-[22px] bg-popover text-popover-foreground',\n      'shadow-[0_10px_30px_-12px_rgb(0_0_0/0.3)] ring-1 ring-foreground/[0.06] dark:ring-foreground/10',\n    ],\n    page: [\n      'flex min-h-0 flex-1 flex-col outline-none',\n      `transition-[opacity,translate] duration-300 ${EASE} motion-reduce:transition-none`,\n      'data-starting-style:opacity-0',\n      'data-[activation-direction=down]:data-starting-style:translate-y-3 data-[activation-direction=up]:data-starting-style:-translate-y-3',\n      'data-ending-style:hidden',\n    ],\n    viewport: 'min-h-0 flex-1 overscroll-contain outline-none',\n    content: 'flex flex-col gap-1 p-3.5',\n    scrollbar:\n      'my-3 me-1 flex w-1.5 opacity-0 transition-opacity delay-300 data-hovering:opacity-100 data-hovering:delay-0 data-scrolling:opacity-100 data-scrolling:delay-0',\n    thumb: 'relative flex-1 rounded-full bg-foreground/20',\n    section: 'grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3 py-1.5',\n    sectionTitle: [\n      'col-start-1 row-start-1 flex min-h-8 items-center text-left text-[13px] font-medium text-foreground',\n      // A section that folds: its title is the button, with a chevron that turns.\n      'data-[slot=inspector-section-trigger]:-mx-1.5 data-[slot=inspector-section-trigger]:cursor-pointer data-[slot=inspector-section-trigger]:gap-1.5 data-[slot=inspector-section-trigger]:rounded-lg data-[slot=inspector-section-trigger]:px-1.5',\n      'data-[slot=inspector-section-trigger]:outline-none data-[slot=inspector-section-trigger]:hover:bg-foreground/[0.04] data-[slot=inspector-section-trigger]:focus-visible:ring-2 data-[slot=inspector-section-trigger]:focus-visible:ring-ring',\n    ],\n    // Right after the title: up while the section is open, down while it is folded.\n    chevron: [\n      'size-3.5 shrink-0 rotate-180 text-muted-foreground transition-transform duration-200',\n      'in-data-panel-open:rotate-0 motion-reduce:transition-none',\n    ],\n    sectionAction: 'col-start-2 row-start-1 flex items-center gap-1.5',\n    sectionContent: [\n      'col-span-2 flex flex-col gap-2 pt-2',\n      // Folding: the height follows the content, from and to nothing.\n      'data-[slot=inspector-section-panel]:h-(--collapsible-panel-height) data-[slot=inspector-section-panel]:overflow-hidden',\n      'data-[slot=inspector-section-panel]:transition-[height,opacity] data-[slot=inspector-section-panel]:duration-300',\n      'data-[slot=inspector-section-panel]:ease-[cubic-bezier(0.3,1.2,0.4,1)]',\n      'data-starting-style:h-0 data-starting-style:opacity-0 data-ending-style:h-0 data-ending-style:opacity-0 motion-reduce:transition-none',\n    ],\n    separator: 'my-2 h-px shrink-0 bg-foreground/[0.08]',\n  },\n});\n\nconst SectionContext = createContext<{ collapsible: boolean } | null>(null);\n\nexport interface InspectorProps extends Omit<Tabs.Root.Props, 'className' | 'orientation'> {\n  className?: string;\n}\n\n/**\n * The rail and the panel side by side. `defaultValue`, or `value` with\n * `onValueChange`, names the page that is open.\n */\nfunction InspectorRoot({ className, ...props }: InspectorProps) {\n  const slots = inspectorVariants();\n  return (\n    <Tooltip.Provider delay={500} closeDelay={0}>\n      <Tabs.Root data-slot=\"inspector\" orientation=\"vertical\" className={slots.root({ className })} {...props} />\n    </Tooltip.Provider>\n  );\n}\nInspectorRoot.displayName = 'Inspector';\n\nexport interface InspectorRailProps extends Omit<Tabs.List.Props, 'className'> {\n  className?: string;\n}\n\n/** The column of page icons, in a pill of its own beside the panel. */\nfunction InspectorRail({ className, children, ...props }: InspectorRailProps) {\n  const slots = inspectorVariants();\n  return (\n    <Tabs.List data-slot=\"inspector-rail\" className={slots.rail({ className })} {...props}>\n      {children}\n      <Tabs.Indicator data-slot=\"inspector-marker\" className={slots.marker()} renderBeforeHydration />\n    </Tabs.List>\n  );\n}\nInspectorRail.displayName = 'Inspector.Rail';\n\nexport interface InspectorTabProps extends Omit<Tabs.Tab.Props, 'className' | 'children'> {\n  /** What the page is called: the tab's tooltip and its name for a screen reader. */\n  label: string;\n  /** The icon. */\n  children?: ReactNode;\n  className?: string;\n}\n\n/** One page's icon on the rail. */\nfunction InspectorTab({ label, className, children, ...props }: InspectorTabProps) {\n  const slots = inspectorVariants();\n  return (\n    <Tooltip.Root>\n      <Tooltip.Trigger\n        render={\n          <Tabs.Tab data-slot=\"inspector-tab\" aria-label={label} className={slots.tab({ className })} {...props}>\n            {children}\n          </Tabs.Tab>\n        }\n      />\n      <Tooltip.Portal>\n        <Tooltip.Positioner side=\"left\" sideOffset={10} className=\"z-50\">\n          <Tooltip.Popup className={slots.tooltip()}>{label}</Tooltip.Popup>\n        </Tooltip.Positioner>\n      </Tooltip.Portal>\n    </Tooltip.Root>\n  );\n}\nInspectorTab.displayName = 'Inspector.Tab';\n\nexport interface InspectorRailSeparatorProps extends ComponentProps<'span'> {}\n\n/** A short line between groups of pages on the rail. */\nfunction InspectorRailSeparator({ className, render, ...props }: InspectorRailSeparatorProps & RenderProp<'span'>) {\n  const slots = inspectorVariants();\n  return useRender({\n    defaultTagName: 'span',\n    render,\n    props: mergeProps(\n      { 'aria-hidden': true, 'data-slot': 'inspector-rail-separator', className: slots.railSeparator({ className }) },\n      props\n    ),\n  });\n}\nInspectorRailSeparator.displayName = 'Inspector.RailSeparator';\n\nexport interface InspectorPanelProps extends ComponentProps<'div'> {}\n\n/**\n * The card the pages open in. Give it a height, such as `h-[32rem]`, and a\n * page longer than that scrolls inside it. Without pages, its children are\n * one page of sections.\n */\nfunction InspectorPanel({ className, children, render, ...props }: InspectorPanelProps & RenderProp<'div'>) {\n  const slots = inspectorVariants();\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      {\n        'data-slot': 'inspector-panel',\n        className: slots.panel({ className }),\n        children: <>{hasPages(children) ? children : <Scroll>{children}</Scroll>}</>,\n      },\n      props\n    ),\n  });\n}\nInspectorPanel.displayName = 'Inspector.Panel';\n\n/** Whether the panel's children are pages, rather than sections written straight into it. */\nfunction hasPages(children: ReactNode): boolean {\n  const list = Array.isArray(children) ? children : [children];\n  return list.some(\n    (child) =>\n      typeof child === 'object' &&\n      child !== null &&\n      'type' in child &&\n      (child as { type: unknown }).type === InspectorPage\n  );\n}\n\n/** The scrolling column of a page, with a thin bar that shows while it moves. */\nfunction Scroll({ children }: { children: ReactNode }) {\n  const slots = inspectorVariants();\n  return (\n    <ScrollArea.Root className=\"relative flex min-h-0 flex-1 flex-col\">\n      <ScrollArea.Viewport className={slots.viewport()}>\n        <ScrollArea.Content className={slots.content()}>{children}</ScrollArea.Content>\n      </ScrollArea.Viewport>\n      <ScrollArea.Scrollbar orientation=\"vertical\" className={slots.scrollbar()}>\n        <ScrollArea.Thumb className={slots.thumb()} />\n      </ScrollArea.Scrollbar>\n    </ScrollArea.Root>\n  );\n}\n\nexport interface InspectorPageProps extends Omit<Tabs.Panel.Props, 'className'> {\n  className?: string;\n}\n\n/** What one tab opens: a column of sections that scrolls inside the panel. */\nfunction InspectorPage({ className, children, ...props }: InspectorPageProps) {\n  const slots = inspectorVariants();\n  return (\n    <Tabs.Panel data-slot=\"inspector-page\" className={slots.page({ className })} {...props}>\n      <Scroll>{children}</Scroll>\n    </Tabs.Panel>\n  );\n}\nInspectorPage.displayName = 'Inspector.Page';\n\nexport interface InspectorSectionProps extends Omit<ComponentProps<'div'>, 'onChange'> {\n  /** Fold the section away by its title. Its content then shows only while it is open. */\n  collapsible?: boolean;\n  /** Whether a collapsible section starts open. Default true. */\n  defaultOpen?: boolean;\n  /** Whether a collapsible section is open, when the caller holds it. */\n  open?: boolean;\n  /** Called when a collapsible section opens or closes. */\n  onOpenChange?: (open: boolean) => void;\n}\n\n/** A titled group of settings: a title, an action on its line, and the controls under them. */\nfunction InspectorSection({\n  collapsible = false,\n  defaultOpen = true,\n  open,\n  onOpenChange,\n  className,\n  children,\n  ...props\n}: InspectorSectionProps) {\n  const slots = inspectorVariants();\n  if (!collapsible) {\n    return (\n      <SectionContext.Provider value={{ collapsible: false }}>\n        <div data-slot=\"inspector-section\" className={slots.section({ className })} {...props}>\n          {children}\n        </div>\n      </SectionContext.Provider>\n    );\n  }\n  return (\n    <SectionContext.Provider value={{ collapsible: true }}>\n      <Collapsible.Root\n        data-slot=\"inspector-section\"\n        defaultOpen={defaultOpen}\n        open={open}\n        onOpenChange={onOpenChange ? (next) => onOpenChange(next) : undefined}\n        className={slots.section({ className })}\n        {...(props as Omit<Collapsible.Root.Props, 'className'>)}\n      >\n        {children}\n      </Collapsible.Root>\n    </SectionContext.Provider>\n  );\n}\nInspectorSection.displayName = 'Inspector.Section';\n\nfunction useSection(part: string) {\n  const section = useContext(SectionContext);\n  if (!section) throw new Error(`${part} must be used inside an <Inspector.Section>.`);\n  return section;\n}\n\nexport interface InspectorSectionTitleProps extends ComponentProps<'h3'> {}\n\n/** The section's name. In a collapsible section it is the button that folds it, with a chevron. */\nfunction InspectorSectionTitle({ className, children, ...props }: InspectorSectionTitleProps) {\n  const { collapsible } = useSection('Inspector.SectionTitle');\n  const slots = inspectorVariants();\n  if (!collapsible) {\n    return (\n      <h3 data-slot=\"inspector-section-title\" className={slots.sectionTitle({ className })} {...props}>\n        {children}\n      </h3>\n    );\n  }\n  return (\n    <h3 className=\"col-start-1 row-start-1 m-0\">\n      <Collapsible.Trigger\n        data-slot=\"inspector-section-trigger\"\n        className={slots.sectionTitle({ className: ['w-full', className] })}\n      >\n        {children}\n        <svg\n          viewBox=\"0 0 16 16\"\n          aria-hidden\n          className={slots.chevron()}\n          fill=\"none\"\n          stroke=\"currentColor\"\n          strokeWidth={1.8}\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n        >\n          <path d=\"m4 10 4-4 4 4\" />\n        </svg>\n      </Collapsible.Trigger>\n    </h3>\n  );\n}\nInspectorSectionTitle.displayName = 'Inspector.SectionTitle';\n\nexport interface InspectorSectionActionProps extends ComponentProps<'div'> {}\n\n/** What sits at the end of the title's line: the switch for the whole section, or a Reset. */\nfunction InspectorSectionAction({ className, render, ...props }: InspectorSectionActionProps & RenderProp<'div'>) {\n  useSection('Inspector.SectionAction');\n  const slots = inspectorVariants();\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { 'data-slot': 'inspector-section-action', className: slots.sectionAction({ className }) },\n      props\n    ),\n  });\n}\nInspectorSectionAction.displayName = 'Inspector.SectionAction';\n\nexport interface InspectorSectionContentProps extends ComponentProps<'div'> {}\n\n/** The controls under the title, stacked with an even gap. */\nfunction InspectorSectionContent({ className, children, ...props }: InspectorSectionContentProps) {\n  const { collapsible } = useSection('Inspector.SectionContent');\n  const slots = inspectorVariants();\n  if (!collapsible) {\n    return (\n      <div data-slot=\"inspector-section-content\" className={slots.sectionContent({ className })} {...props}>\n        {children}\n      </div>\n    );\n  }\n  return (\n    <Collapsible.Panel\n      data-slot=\"inspector-section-panel\"\n      className={slots.sectionContent({ className })}\n      style={props.style as CSSProperties}\n    >\n      {children}\n    </Collapsible.Panel>\n  );\n}\nInspectorSectionContent.displayName = 'Inspector.SectionContent';\n\nexport interface InspectorSeparatorProps extends ComponentProps<'div'> {}\n\n/** A hairline between sections that belong to different things. */\nfunction InspectorSeparator({ className, render, ...props }: InspectorSeparatorProps & RenderProp<'div'>) {\n  const slots = inspectorVariants();\n  return useRender({\n    defaultTagName: 'div',\n    render,\n    props: mergeProps(\n      { role: 'separator', 'data-slot': 'inspector-separator', className: slots.separator({ className }) },\n      props\n    ),\n  });\n}\nInspectorSeparator.displayName = 'Inspector.Separator';\n\nexport const Inspector = Object.assign(InspectorRoot, {\n  Rail: InspectorRail,\n  Tab: InspectorTab,\n  RailSeparator: InspectorRailSeparator,\n  Panel: InspectorPanel,\n  Page: InspectorPage,\n  Section: InspectorSection,\n  SectionTitle: InspectorSectionTitle,\n  SectionAction: InspectorSectionAction,\n  SectionContent: InspectorSectionContent,\n  Separator: InspectorSeparator,\n});\n\nexport {\n  InspectorRail,\n  InspectorTab,\n  InspectorRailSeparator,\n  InspectorPanel,\n  InspectorPage,\n  InspectorSection,\n  InspectorSectionTitle,\n  InspectorSectionAction,\n  InspectorSectionContent,\n  InspectorSeparator,\n  inspectorVariants,\n};\n"
    }
  ]
}
