{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chat-window",
  "title": "Chat Window",
  "description": "Chat Window from the Get IT Done design system.",
  "dependencies": [
    "lucide-react@^0.379.0"
  ],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/heading-tag.json",
    "https://zero-design.itdone.de/r/labels.json",
    "https://zero-design.itdone.de/r/labels-context.json",
    "https://zero-design.itdone.de/r/recipes.json",
    "https://zero-design.itdone.de/r/tabs.json",
    "https://zero-design.itdone.de/r/utils.json",
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/ui/chat-window.tsx",
      "content": "'use client';\n\nimport { Minus, X } from 'lucide-react';\nimport * as React from 'react';\n\nimport { focusRing } from '@/lib/recipes';\nimport { cn } from '@/lib/utils';\nimport type { HeadingTag } from '@/components/ui/heading-tag';\nimport type { LabelOverrides } from '@/components/ui/labels';\nimport { useLabels } from '@/components/ui/labels-context';\nimport { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';\n\nexport interface ChatWindowView {\n  id: string;\n  label: React.ReactNode;\n  content: React.ReactNode;\n}\n\nconst heightStyle = {\n  sm: 'h-80',\n  md: 'h-96',\n  // Content area height (viewport minus top bar) minus the bottom-4 float offset and the inset token, so the window never overlaps the top bar.\n  content: 'h-[calc(theme(height.content)-theme(spacing.4)-var(--size-inset))]',\n} as const;\n\nconst paddingStyle = { none: 'p-0', sm: 'p-3', md: 'p-4', lg: 'p-6' } as const;\n\nexport interface ChatWindowProps extends Omit<React.HTMLAttributes<HTMLElement>, 'title' | 'children'> {\n  open: boolean;\n  onOpenChange?: (open: boolean) => void;\n  title: React.ReactNode;\n  // Heading element of the title, so the window fits the document outline. Default h2.\n  titleAs?: HeadingTag;\n  // Collapses the body to the header bar. Controlled by the consumer.\n  minimized?: boolean;\n  onMinimizedChange?: (minimized: boolean) => void;\n  // Second header line under the title, for example the assistant role or a status. Describes the dialog.\n  subtitle?: React.ReactNode;\n  // Window height when expanded. `content` fills the content area below the top bar. Default `md`.\n  height?: keyof typeof heightStyle;\n  // Body padding. Default `md`.\n  padding?: keyof typeof paddingStyle;\n  // Several views: a tab list under the fixed header switches the scrolling body. Replaces `children`.\n  views?: ChatWindowView[];\n  // Controlled view id.\n  view?: string;\n  defaultView?: string;\n  onViewChange?: (id: string) => void;\n  // Accessible name of the view tab list. Defaults to the title text when it is a string.\n  viewsLabel?: string;\n  // Slot in the header before the window buttons, e.g. a status badge or a menu.\n  headerActions?: React.ReactNode;\n  // Message list and other content. Scrolls when it overflows.\n  children?: React.ReactNode;\n  // Composer below the body.\n  footer?: React.ReactNode;\n  // `fixed` floats over the window, `absolute` floats inside a relative container.\n  positioning?: 'fixed' | 'absolute';\n  side?: 'right' | 'left';\n  // Moves focus into the window when it opens and back to the opener when it closes. Default true.\n  manageFocus?: boolean;\n  closeLabel?: string;\n  minimizeLabel?: string;\n  restoreLabel?: string;\n}\n\n// Floating non-modal chat window: the page behind stays usable. Escape inside the window closes it.\nconst ChatWindow = React.forwardRef<HTMLElement, ChatWindowProps>(\n  (\n    {\n      className,\n      open,\n      onOpenChange,\n      title,\n      titleAs: Title = 'h2',\n      subtitle,\n      height = 'md',\n      padding = 'md',\n      views,\n      view,\n      defaultView,\n      onViewChange,\n      viewsLabel,\n      minimized = false,\n      onMinimizedChange,\n      headerActions,\n      children,\n      footer,\n      positioning = 'fixed',\n      side = 'right',\n      manageFocus = true,\n      closeLabel,\n      minimizeLabel,\n      restoreLabel,\n      onKeyDown,\n      ...props\n    },\n    ref,\n  ) => {\n    const labels: LabelOverrides = { close: closeLabel, chatMinimize: minimizeLabel, chatRestore: restoreLabel };\n    const l = useLabels(labels);\n    const titleId = React.useId();\n    const subtitleId = React.useId();\n    const innerRef = React.useRef<HTMLElement | null>(null);\n    const openerRef = React.useRef<HTMLElement | null>(null);\n\n    const setRefs = (node: HTMLElement | null) => {\n      innerRef.current = node;\n      if (typeof ref === 'function') ref(node);\n      else if (ref) (ref as React.MutableRefObject<HTMLElement | null>).current = node;\n    };\n\n    React.useEffect(() => {\n      if (!open || !manageFocus) return undefined;\n      openerRef.current = document.activeElement instanceof HTMLElement ? document.activeElement : null;\n      innerRef.current?.focus();\n      return () => {\n        const opener = openerRef.current;\n        if (opener && document.contains(opener)) opener.focus();\n      };\n    }, [open, manageFocus]);\n\n    if (!open) return null;\n    const toggleLabel = minimized ? l.chatRestore : l.chatMinimize;\n    const iconButton = cn('inline-flex h-control-sm w-control-sm items-center justify-center rounded-control text-icon-subtle hover:bg-muted hover:text-foreground', focusRing);\n    return (\n      <section\n        ref={setRefs}\n        role=\"dialog\"\n        aria-modal=\"false\"\n        aria-labelledby={titleId}\n        aria-describedby={subtitle ? subtitleId : undefined}\n        tabIndex={-1}\n        data-minimized={minimized || undefined}\n        onKeyDown={(event) => {\n          onKeyDown?.(event);\n          if (!event.defaultPrevented && event.key === 'Escape') {\n            event.stopPropagation();\n            onOpenChange?.(false);\n          }\n        }}\n        className={cn(\n          // max-w keeps both float offsets (left-4 and right-4) inside the viewport on phones.\n          'z-banner flex w-sheet-sm max-w-[calc(100%-theme(spacing.8))] flex-col overflow-hidden rounded-surface border border-solid border-border bg-popover text-popover-foreground shadow-elevation-raised outline-none',\n          positioning === 'fixed' ? 'fixed' : 'absolute',\n          side === 'right' ? 'right-4' : 'left-4',\n          'bottom-4',\n          !minimized && cn(heightStyle[height], 'max-h-full'),\n          className,\n        )}\n        {...props}\n      >\n        <header className=\"flex shrink-0 items-center gap-2 border-0 border-b border-solid border-hairline px-4 py-2\">\n          <div className=\"flex min-w-0 flex-1 flex-col\">\n            <Title id={titleId} className=\"typography-ui-semibold m-0 truncate\">\n              {title}\n            </Title>\n            {subtitle ? (\n              <p id={subtitleId} className=\"typography-caption m-0 truncate text-text-subtle\">\n                {subtitle}\n              </p>\n            ) : null}\n          </div>\n          {headerActions}\n          {onMinimizedChange ? (\n            <button type=\"button\" aria-label={toggleLabel} aria-expanded={!minimized} onClick={() => onMinimizedChange(!minimized)} className={iconButton}>\n              <Minus aria-hidden=\"true\" className=\"h-icon-md w-icon-md\" />\n            </button>\n          ) : null}\n          <button type=\"button\" aria-label={l.close} onClick={() => onOpenChange?.(false)} className={iconButton}>\n            <X aria-hidden=\"true\" className=\"h-icon-md w-icon-md\" />\n          </button>\n        </header>\n        {minimized ? null : (\n          <>\n            {views && views.length > 0 ? (\n              <Tabs\n                value={view}\n                defaultValue={defaultView ?? views[0].id}\n                onValueChange={onViewChange}\n                className=\"flex min-h-0 flex-1 flex-col\"\n              >\n                <TabsList variant=\"line\" aria-label={viewsLabel ?? (typeof title === 'string' ? title : undefined)} className=\"shrink-0 px-4\">\n                  {views.map((v) => (\n                    <TabsTrigger key={v.id} value={v.id}>\n                      {v.label}\n                    </TabsTrigger>\n                  ))}\n                </TabsList>\n                {views.map((v) => (\n                  <TabsContent key={v.id} value={v.id} className={cn('mt-0 min-h-0 flex-1 overflow-y-auto', paddingStyle[padding])}>\n                    {v.content}\n                  </TabsContent>\n                ))}\n              </Tabs>\n            ) : (\n              <div className={cn('min-h-0 flex-1 overflow-y-auto', paddingStyle[padding])}>{children}</div>\n            )}\n            {footer ? <div className=\"shrink-0 border-0 border-t border-solid border-hairline p-3\">{footer}</div> : null}\n          </>\n        )}\n      </section>\n    );\n  },\n);\nChatWindow.displayName = 'ChatWindow';\n\nexport { ChatWindow };\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}