{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gantt-timeline",
  "type": "registry:ui",
  "title": "Gantt timeline",
  "description": "Compact timeline / Gantt chart with phase rows and status colors.",
  "dependencies": [],
  "devDependencies": [],
  "registryDependencies": [
    "utils",
    "panel"
  ],
  "files": [
    {
      "path": "registry/blacksite/charts/gantt-timeline.tsx",
      "type": "registry:ui",
      "target": "components/charts/gantt-timeline.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn, clamp } from \"@/lib/utils\";\n\nexport interface GanttTask {\n  id: string;\n  label: string;\n  /** Start position in 0..columns range (inclusive). */\n  start: number;\n  /** End position in 0..columns range (exclusive). end > start. */\n  end: number;\n  tone?: \"primary\" | \"success\" | \"warning\" | \"danger\" | \"info\" | \"gold\" | \"neutral\";\n  /** Optional dependency task IDs — drawn as connector arrows. */\n  dependsOn?: string[];\n}\n\ninterface GanttTimelineProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** Column labels rendered along the X axis. */\n  columns: string[];\n  tasks: GanttTask[];\n  /** Optional vertical \"now\" line at column index (can be fractional). */\n  nowAt?: number;\n  /** Row height in px. */\n  rowHeight?: number;\n  /** Width of the leading label column in px. */\n  labelWidth?: number;\n}\n\nconst toneToBg: Record<NonNullable<GanttTask[\"tone\"]>, string> = {\n  primary: \"bg-primary/70 border-primary\",\n  success: \"bg-success/70 border-success\",\n  warning: \"bg-warning/70 border-warning\",\n  danger: \"bg-danger/70 border-danger\",\n  info: \"bg-info/70 border-info\",\n  gold: \"bg-gold/70 border-gold\",\n  neutral: \"bg-foreground/40 border-foreground/60\",\n};\n\nconst GanttTimeline = React.memo(\n  React.forwardRef<HTMLDivElement, GanttTimelineProps>(\n    ({ className, columns, tasks, nowAt, rowHeight = 28, labelWidth = 160, ...props }, ref) => {\n      const cols = Math.max(columns.length, 1);\n\n      return (\n        <div\n          ref={ref}\n          className={cn(\"border-border relative overflow-x-auto border-t\", className)}\n          {...props}\n        >\n          {/* Header row */}\n          <div\n            className=\"border-border bg-background-elevated/60 grid border-b\"\n            style={{\n              gridTemplateColumns: `${labelWidth}px repeat(${cols}, minmax(72px, 1fr))`,\n            }}\n          >\n            <div className=\"h-7\" />\n            {columns.map((c) => (\n              <div\n                key={c}\n                className=\"border-border/60 text-mono text-foreground-muted flex h-7 items-center border-l px-2 text-[10px] tracking-[0.08em] uppercase\"\n              >\n                {c}\n              </div>\n            ))}\n          </div>\n\n          {/* Body */}\n          <div\n            className=\"relative grid\"\n            style={{\n              gridTemplateColumns: `${labelWidth}px repeat(${cols}, minmax(72px, 1fr))`,\n            }}\n          >\n            {tasks.map((task) => (\n              <React.Fragment key={task.id}>\n                <div\n                  className=\"border-border/60 text-mono text-foreground flex items-center border-b px-2 text-[11px] tracking-[0.06em] uppercase\"\n                  style={{ height: rowHeight }}\n                >\n                  {task.label}\n                </div>\n                <div\n                  className=\"border-border/60 relative col-span-full border-b\"\n                  style={{\n                    gridColumn: `2 / span ${cols}`,\n                    height: rowHeight,\n                  }}\n                >\n                  {/* Column tick lines */}\n                  <div\n                    className=\"absolute inset-0 grid\"\n                    style={{ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` }}\n                  >\n                    {columns.map((_, i) => (\n                      <div key={i} className=\"border-border/30 border-l first:border-l-0\" />\n                    ))}\n                  </div>\n                  {/* Bar */}\n                  <div\n                    className={cn(\n                      \"absolute top-1/2 h-4 -translate-y-1/2 rounded-[2px] border\",\n                      toneToBg[task.tone ?? \"primary\"],\n                    )}\n                    style={{\n                      left: `${(clamp(task.start, 0, cols) / cols) * 100}%`,\n                      width: `${(Math.max(task.end - task.start, 0.25) / cols) * 100}%`,\n                    }}\n                    title={task.label}\n                  />\n                </div>\n              </React.Fragment>\n            ))}\n\n            {/* \"Now\" line */}\n            {typeof nowAt === \"number\" && (\n              <div\n                aria-hidden=\"true\"\n                className=\"bg-foreground/40 pointer-events-none absolute top-0 bottom-0 w-px\"\n                style={{\n                  left: `calc(${labelWidth}px + ((100% - ${labelWidth}px) * ${clamp(nowAt, 0, cols) / cols}))`,\n                }}\n              >\n                <div className=\"bg-foreground/70 absolute -top-1 -left-1 size-2 rounded-full\" />\n              </div>\n            )}\n          </div>\n        </div>\n      );\n    },\n  ),\n);\nGanttTimeline.displayName = \"GanttTimeline\";\n\nexport { GanttTimeline };\n"
    }
  ]
}
