{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "gantt",
  "title": "Gantt",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-react/components/gantt/Gantt.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport type { GanttScale, GanttTask } from './types'\n\nexport interface GanttContextValue {\n  scale: GanttScale\n  setScale: (scale: GanttScale) => void\n  startDate: Date\n  endDate: Date\n  totalDays: number\n  columnWidth: number\n  rowHeight: number\n  headerHeight: number\n  treeWidth: number\n  tasks: GanttTask[]\n  onTaskClick?: (task: GanttTask) => void\n  onTaskChange?: (task: GanttTask) => void\n}\n\nexport const GanttContext = React.createContext<GanttContextValue | null>(null)\n\nexport function useGantt() {\n  const context = React.useContext(GanttContext)\n  if (!context) {\n    throw new Error('useGantt must be used within a <Gantt /> component')\n  }\n  return context\n}\n\nexport interface GanttProps extends React.HTMLAttributes<HTMLDivElement> {\n  tasks: GanttTask[]\n  scale?: GanttScale\n  onScaleChange?: (scale: GanttScale) => void\n  startDate?: string\n  endDate?: string\n  rowHeight?: number\n  headerHeight?: number\n  treeWidth?: number\n  onTaskClick?: (task: GanttTask) => void\n  onTaskChange?: (task: GanttTask) => void\n}\n\nexport const Gantt = React.forwardRef<HTMLDivElement, GanttProps>(\n  (\n    {\n      className,\n      tasks,\n      scale: controlledScale,\n      onScaleChange,\n      startDate,\n      endDate,\n      rowHeight = 40,\n      headerHeight = 48,\n      treeWidth = 280,\n      onTaskClick,\n      onTaskChange,\n      children,\n      ...props\n    },\n    ref,\n  ) => {\n    const [internalScale, setInternalScale] = React.useState<GanttScale>(controlledScale ?? 'day')\n    const currentScale = controlledScale ?? internalScale\n\n    const handleSetScale = React.useCallback(\n      (newScale: GanttScale) => {\n        setInternalScale(newScale)\n        onScaleChange?.(newScale)\n      },\n      [onScaleChange],\n    )\n\n    const resolvedStartDate = React.useMemo(() => {\n      if (startDate) return new Date(startDate)\n      if (tasks.length === 0) return new Date()\n      const dates = tasks.map((t) => new Date(t.startDate).getTime())\n      const min = Math.min(...dates)\n      const d = new Date(min)\n      d.setDate(d.getDate() - 3)\n      return d\n    }, [startDate, tasks])\n\n    const resolvedEndDate = React.useMemo(() => {\n      if (endDate) return new Date(endDate)\n      if (tasks.length === 0) {\n        const d = new Date()\n        d.setDate(d.getDate() + 30)\n        return d\n      }\n      const dates = tasks.map((t) => new Date(t.endDate).getTime())\n      const max = Math.max(...dates)\n      const d = new Date(max)\n      d.setDate(d.getDate() + 7)\n      return d\n    }, [endDate, tasks])\n\n    const totalDays = React.useMemo(() => {\n      const diff = resolvedEndDate.getTime() - resolvedStartDate.getTime()\n      return Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))\n    }, [resolvedStartDate, resolvedEndDate])\n\n    const columnWidth = React.useMemo(() => {\n      switch (currentScale) {\n        case 'day':\n          return 44\n        case 'week':\n          return 120\n        case 'month':\n          return 180\n        case 'year':\n          return 240\n        default:\n          return 44\n      }\n    }, [currentScale])\n\n    const contextValue = React.useMemo<GanttContextValue>(\n      () => ({\n        scale: currentScale,\n        setScale: handleSetScale,\n        startDate: resolvedStartDate,\n        endDate: resolvedEndDate,\n        totalDays,\n        columnWidth,\n        rowHeight,\n        headerHeight,\n        treeWidth,\n        tasks,\n        onTaskClick,\n        onTaskChange,\n      }),\n      [\n        currentScale,\n        handleSetScale,\n        resolvedStartDate,\n        resolvedEndDate,\n        totalDays,\n        columnWidth,\n        rowHeight,\n        headerHeight,\n        treeWidth,\n        tasks,\n        onTaskClick,\n        onTaskChange,\n      ],\n    )\n\n    return (\n      <GanttContext.Provider value={contextValue}>\n        <div\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"gantt\"\n          className={cn(\n            'border-border bg-card text-card-foreground relative flex w-full flex-col overflow-hidden rounded-xl border shadow-xs',\n            className,\n          )}\n          {...props}\n        >\n          {children}\n        </div>\n      </GanttContext.Provider>\n    )\n  },\n)\n\nGantt.displayName = 'Gantt'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/Gantt.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttHeader.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { Calendar } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { Button, ButtonGroup } from '@/components/ui/button'\nimport { useGantt } from './Gantt'\n\nexport interface GanttHeaderProps extends React.HTMLAttributes<HTMLDivElement> {\n  title?: string\n  showScaleSwitcher?: boolean\n  actions?: React.ReactNode\n}\n\nexport const GanttHeader = React.forwardRef<HTMLDivElement, GanttHeaderProps>(\n  ({ className, title = 'Project Timeline', showScaleSwitcher = true, actions, children, ...props }, ref) => {\n    const { scale, setScale } = useGantt()\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"gantt-header\"\n        className={cn('border-border bg-muted/30 flex items-center justify-between border-b px-4 py-2.5', className)}\n        {...props}\n      >\n        <div className=\"flex items-center gap-2\">\n          <Calendar className=\"text-primary size-4\" />\n          <span className=\"text-foreground text-sm font-semibold\">{title}</span>\n        </div>\n\n        <div className=\"flex items-center gap-3\">\n          {actions}\n          {children}\n\n          {showScaleSwitcher ? (\n            <ButtonGroup>\n              <Button size=\"xs\" variant={scale === 'day' ? 'default' : 'outline'} onClick={() => setScale('day')}>\n                Day\n              </Button>\n              <Button size=\"xs\" variant={scale === 'week' ? 'default' : 'outline'} onClick={() => setScale('week')}>\n                Week\n              </Button>\n              <Button size=\"xs\" variant={scale === 'month' ? 'default' : 'outline'} onClick={() => setScale('month')}>\n                Month\n              </Button>\n              <Button size=\"xs\" variant={scale === 'year' ? 'default' : 'outline'} onClick={() => setScale('year')}>\n                Year\n              </Button>\n            </ButtonGroup>\n          ) : null}\n        </div>\n      </div>\n    )\n  },\n)\n\nGanttHeader.displayName = 'GanttHeader'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttHeader.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttTree.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { ChevronRight, ChevronDown, Flag } from 'lucide-react'\nimport { cn } from '@/lib/utils'\nimport { useGantt } from './Gantt'\nimport type { GanttTask } from './types'\n\nexport interface GanttTreeProps extends React.HTMLAttributes<HTMLDivElement> {\n  showAssignee?: boolean\n  showPriority?: boolean\n  onTaskClick?: (task: GanttTask) => void\n}\n\nconst priorityColors: Record<string, string> = {\n  urgent: 'text-destructive',\n  high: 'text-amber-500',\n  medium: 'text-primary',\n  low: 'text-muted-foreground/60',\n}\n\nfunction calculateDays(startDate: string, endDate: string) {\n  const diff = new Date(endDate).getTime() - new Date(startDate).getTime()\n  const days = Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))\n  return `${days}d`\n}\n\nexport const GanttTree = React.forwardRef<HTMLDivElement, GanttTreeProps>(\n  ({ className, showAssignee = true, showPriority = true, onTaskClick: propOnTaskClick, ...props }, ref) => {\n    const { treeWidth, headerHeight, rowHeight, tasks, onTaskClick: ctxOnTaskClick } = useGantt()\n    const handleTaskClick = propOnTaskClick ?? ctxOnTaskClick\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"gantt-tree\"\n        style={{ width: `${treeWidth}px` }}\n        className={cn(\n          'border-border bg-card flex shrink-0 flex-col border-r transition-[width] select-none',\n          className,\n        )}\n        {...props}\n      >\n        <div\n          style={{ height: `${headerHeight}px` }}\n          className=\"border-border bg-muted/20 text-muted-foreground flex items-center justify-between border-b px-3 text-[11px] font-semibold tracking-wider uppercase\"\n        >\n          <span className=\"flex-1 truncate\">Deliverable</span>\n          {showPriority ? <span className=\"w-12 shrink-0 text-center\">Pri</span> : null}\n          <span className=\"w-16 shrink-0 text-right\">Duration</span>\n        </div>\n\n        <div className=\"divide-border/40 flex-1 divide-y overflow-y-auto\">\n          {tasks.map((task) => (\n            <div\n              key={task.id}\n              style={{ height: `${rowHeight}px` }}\n              className={cn(\n                'group/row text-foreground hover:bg-muted/40 flex cursor-pointer items-center justify-between px-3 text-xs transition-colors',\n                task.isGroup && 'bg-muted/10 font-semibold',\n              )}\n              onClick={() => handleTaskClick?.(task)}\n            >\n              <div className=\"flex min-w-0 flex-1 items-center gap-1.5 pr-2\">\n                {task.parentId ? <span className=\"w-4 shrink-0\" /> : null}\n\n                {task.status ? (\n                  <span\n                    className={cn(\n                      'size-2 shrink-0 rounded-full',\n                      task.status === 'done' && 'bg-emerald-500 ring-2 ring-emerald-500/20',\n                      task.status === 'in-progress' && 'bg-primary ring-primary/20 ring-2',\n                      task.status === 'at-risk' && 'bg-amber-500 ring-2 ring-amber-500/20',\n                      task.status === 'todo' && 'bg-muted-foreground/40',\n                      task.status === 'blocked' && 'bg-destructive ring-destructive/20 ring-2',\n                    )}\n                  />\n                ) : null}\n\n                <span className=\"truncate font-medium\">{task.name}</span>\n              </div>\n\n              {showPriority ? (\n                <div className=\"flex w-12 shrink-0 items-center justify-center\">\n                  {task.priority ? <Flag className={cn('size-3', priorityColors[task.priority])} /> : null}\n                </div>\n              ) : null}\n\n              <div className=\"text-muted-foreground w-16 shrink-0 text-right font-mono text-[11px]\">\n                {task.isMilestone ? (\n                  <span className=\"text-[10px] font-semibold text-amber-500\">Milestone</span>\n                ) : (\n                  <span>{calculateDays(task.startDate, task.endDate)}</span>\n                )}\n              </div>\n            </div>\n          ))}\n        </div>\n      </div>\n    )\n  },\n)\n\nGanttTree.displayName = 'GanttTree'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttTree.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttTimeline.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport { useGantt } from './Gantt'\nimport { GanttBar } from './GanttBar'\nimport { GanttMilestone } from './GanttMilestone'\nimport type { GanttTask } from './types'\n\nexport interface GanttTimelineProps extends React.HTMLAttributes<HTMLDivElement> {\n  showTodayLine?: boolean\n  showDependencies?: boolean\n  onTaskClick?: (task: GanttTask) => void\n}\n\nexport const GanttTimeline = React.forwardRef<HTMLDivElement, GanttTimelineProps>(\n  ({ className, showTodayLine = true, showDependencies = true, onTaskClick: propOnTaskClick, ...props }, ref) => {\n    const {\n      startDate,\n      totalDays,\n      columnWidth,\n      headerHeight,\n      rowHeight,\n      tasks,\n      onTaskClick: ctxOnTaskClick,\n    } = useGantt()\n    const handleTaskClick = propOnTaskClick ?? ctxOnTaskClick\n\n    const columns = React.useMemo(() => {\n      const list: { date: Date; label: string; subLabel: string; isWeekend: boolean }[] = []\n      const start = new Date(startDate)\n\n      for (let i = 0; i < totalDays; i++) {\n        const d = new Date(start)\n        d.setDate(d.getDate() + i)\n        const dayOfWeek = d.getDay()\n        const isWeekend = dayOfWeek === 0 || dayOfWeek === 6\n\n        list.push({\n          date: d,\n          label: d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' }),\n          subLabel: d.toLocaleDateString(undefined, { weekday: 'narrow' }),\n          isWeekend,\n        })\n      }\n      return list\n    }, [startDate, totalDays])\n\n    const timelineWidth = columns.length * columnWidth\n\n    const getTaskCoordinates = React.useCallback(\n      (task: GanttTask, index: number) => {\n        const start = new Date(startDate).getTime()\n        const taskStart = new Date(task.startDate).getTime()\n        const taskEnd = new Date(task.endDate).getTime()\n        const oneDay = 1000 * 60 * 60 * 24\n\n        const startDiffDays = Math.max(0, (taskStart - start) / oneDay)\n        const durationDays = Math.max(1, (taskEnd - taskStart) / oneDay)\n\n        const left = startDiffDays * columnWidth\n        const width = durationDays * columnWidth\n        const top = index * rowHeight + (rowHeight - 28) / 2\n\n        return { left, width, top, height: 28 }\n      },\n      [startDate, columnWidth, rowHeight],\n    )\n\n    const todayPosition = React.useMemo(() => {\n      const start = new Date(startDate).getTime()\n      const today = new Date().setHours(0, 0, 0, 0)\n      const oneDay = 1000 * 60 * 60 * 24\n      const diffDays = (today - start) / oneDay\n\n      if (diffDays < 0 || diffDays > totalDays) return null\n      return diffDays * columnWidth + columnWidth / 2\n    }, [startDate, totalDays, columnWidth])\n\n    const dependencyPaths = React.useMemo(() => {\n      if (!showDependencies) return []\n      const taskMap = new Map<string, { task: GanttTask; index: number }>()\n      tasks.forEach((t, i) => taskMap.set(t.id, { task: t, index: i }))\n\n      const paths: { d: string; fromId: string; toId: string }[] = []\n\n      tasks.forEach((toTask, toIdx) => {\n        if (!toTask.dependencies || toTask.dependencies.length === 0) return\n        toTask.dependencies.forEach((fromId) => {\n          const fromEntry = taskMap.get(fromId)\n          if (!fromEntry) return\n\n          const fromCoords = getTaskCoordinates(fromEntry.task, fromEntry.index)\n          const toCoords = getTaskCoordinates(toTask, toIdx)\n\n          const startX = fromEntry.task.isMilestone ? fromCoords.left : fromCoords.left + fromCoords.width\n          const startY = fromCoords.top + 14\n\n          const endX = toCoords.left\n          const endY = toCoords.top + 14\n\n          const deltaX = Math.max(16, (endX - startX) / 2)\n          const d = `M ${startX} ${startY} C ${startX + deltaX} ${startY}, ${endX - deltaX} ${endY}, ${endX} ${endY}`\n          paths.push({ d, fromId, toId: toTask.id })\n        })\n      })\n\n      return paths\n    }, [showDependencies, tasks, getTaskCoordinates])\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"gantt-timeline\"\n        className={cn('bg-background relative flex-1 overflow-x-auto overflow-y-hidden select-none', className)}\n        {...props}\n      >\n        <div style={{ width: `${timelineWidth}px` }} className=\"relative\">\n          <div\n            style={{ height: `${headerHeight}px` }}\n            className=\"border-border bg-muted/10 sticky top-0 z-20 flex border-b\"\n          >\n            {columns.map((col, i) => (\n              <div\n                key={i}\n                style={{ width: `${columnWidth}px` }}\n                className={cn(\n                  'border-border/50 text-muted-foreground flex flex-col items-center justify-center border-r text-[10px]',\n                  col.isWeekend && 'bg-muted/20 text-muted-foreground/60',\n                )}\n              >\n                <span className=\"text-foreground font-medium\">{col.label}</span>\n                <span className=\"text-[9px]\">{col.subLabel}</span>\n              </div>\n            ))}\n          </div>\n\n          <div className=\"relative\">\n            <div className=\"pointer-events-none absolute inset-0 flex\">\n              {columns.map((col, i) => (\n                <div\n                  key={i}\n                  style={{ width: `${columnWidth}px` }}\n                  className={cn('border-border/30 h-full border-r', col.isWeekend && 'bg-muted/15')}\n                />\n              ))}\n            </div>\n\n            {showTodayLine && todayPosition != null ? (\n              <div\n                style={{ left: `${todayPosition}px` }}\n                className=\"pointer-events-none absolute inset-y-0 z-30 flex flex-col items-center\"\n              >\n                <div className=\"bg-destructive text-destructive-foreground rounded-full px-1.5 py-0.5 text-[9px] font-bold shadow-xs\">\n                  Today\n                </div>\n                <div className=\"bg-destructive/60 h-full w-[1.5px] border-r border-dashed\" />\n              </div>\n            ) : null}\n\n            {dependencyPaths.length > 0 ? (\n              <svg\n                width={timelineWidth}\n                height={tasks.length * rowHeight}\n                className=\"pointer-events-none absolute inset-0 z-10\"\n              >\n                <defs>\n                  <marker\n                    id=\"gantt-arrow-react\"\n                    viewBox=\"0 0 6 6\"\n                    refX=\"5\"\n                    refY=\"3\"\n                    markerWidth=\"6\"\n                    markerHeight=\"6\"\n                    orient=\"auto\"\n                  >\n                    <path d=\"M 0 0 L 6 3 L 0 6 z\" className=\"fill-primary/60\" />\n                  </marker>\n                </defs>\n                {dependencyPaths.map((p, i) => (\n                  <path\n                    key={i}\n                    d={p.d}\n                    fill=\"none\"\n                    className=\"stroke-primary/50\"\n                    strokeWidth=\"1.5\"\n                    strokeDasharray=\"3,3\"\n                    markerEnd=\"url(#gantt-arrow-react)\"\n                  />\n                ))}\n              </svg>\n            ) : null}\n\n            {tasks.map((task, idx) => {\n              const coords = getTaskCoordinates(task, idx)\n              return (\n                <div\n                  key={task.id}\n                  style={{ height: `${rowHeight}px` }}\n                  className=\"border-border/40 hover:bg-muted/10 relative border-b transition-colors\"\n                >\n                  {task.isMilestone ? (\n                    <GanttMilestone task={task} left={coords.left} top={rowHeight / 2} onTaskClick={handleTaskClick} />\n                  ) : (\n                    <GanttBar\n                      task={task}\n                      left={coords.left}\n                      width={coords.width}\n                      top={(coords.height - 28) / 2 + 6}\n                      height={28}\n                      onTaskClick={handleTaskClick}\n                    />\n                  )}\n                </div>\n              )\n            })}\n          </div>\n        </div>\n      </div>\n    )\n  },\n)\n\nGanttTimeline.displayName = 'GanttTimeline'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttTimeline.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttBar.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport type { GanttTask } from './types'\n\nexport interface GanttBarProps extends React.HTMLAttributes<HTMLDivElement> {\n  task: GanttTask\n  left: number\n  width: number\n  top: number\n  height: number\n  onTaskClick?: (task: GanttTask) => void\n}\n\nconst statusColors: Record<string, string> = {\n  done: 'bg-emerald-500/20 text-emerald-700 dark:text-emerald-300 border-emerald-500/40',\n  'in-progress': 'bg-primary/20 text-primary border-primary/40',\n  'at-risk': 'bg-amber-500/20 text-amber-700 dark:text-amber-300 border-amber-500/40',\n  todo: 'bg-muted/80 text-muted-foreground border-border',\n  blocked: 'bg-destructive/20 text-destructive border-destructive/40',\n}\n\nconst progressColors: Record<string, string> = {\n  done: 'bg-emerald-500/40',\n  'in-progress': 'bg-primary/40',\n  'at-risk': 'bg-amber-500/40',\n  todo: 'bg-muted-foreground/20',\n  blocked: 'bg-destructive/40',\n}\n\nexport const GanttBar = React.forwardRef<HTMLDivElement, GanttBarProps>(\n  ({ className, task, left, width, top, height, onTaskClick, ...props }, ref) => {\n    if (task.isGroup) {\n      return (\n        <div\n          ref={ref}\n          data-uipkge=\"\"\n          data-slot=\"gantt-group-bar\"\n          style={{\n            left: `${left}px`,\n            width: `${Math.max(24, width)}px`,\n            top: `${top + 4}px`,\n            height: `${height - 8}px`,\n          }}\n          className={cn(\n            'group/bar bg-foreground/80 text-background hover:bg-foreground absolute z-10 flex cursor-pointer items-center justify-between rounded-xs px-2 text-xs font-semibold shadow-xs select-none',\n            className,\n          )}\n          onClick={() => onTaskClick?.(task)}\n          {...props}\n        >\n          <span className=\"truncate\">{task.name}</span>\n          {task.progress != null ? <span className=\"font-mono text-[10px] opacity-80\">{task.progress}%</span> : null}\n        </div>\n      )\n    }\n\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"gantt-bar\"\n        style={{\n          left: `${left}px`,\n          width: `${Math.max(24, width)}px`,\n          top: `${top}px`,\n          height: `${height}px`,\n        }}\n        className={cn(\n          'group/bar absolute z-10 flex cursor-pointer items-center overflow-hidden rounded-md border text-xs font-medium shadow-xs transition-[box-shadow,transform] select-none hover:scale-[1.01] hover:shadow-md',\n          task.color ? task.color : statusColors[task.status ?? 'in-progress'],\n          className,\n        )}\n        onClick={() => onTaskClick?.(task)}\n        {...props}\n      >\n        {task.progress != null && task.progress > 0 ? (\n          <div\n            style={{ width: `${task.progress}%` }}\n            className={cn('absolute inset-y-0 left-0 transition-all', progressColors[task.status ?? 'in-progress'])}\n          />\n        ) : null}\n\n        <div className=\"relative z-10 flex w-full min-w-0 items-center justify-between px-2\">\n          <span className=\"truncate font-medium\">{task.name}</span>\n          {task.progress != null ? (\n            <span className=\"ml-1 shrink-0 font-mono text-[10px] opacity-80\">{task.progress}%</span>\n          ) : null}\n        </div>\n\n        <div\n          aria-hidden=\"true\"\n          className=\"bg-foreground/20 absolute inset-y-0 left-0 w-1.5 cursor-ew-resize opacity-0 transition-opacity group-hover/bar:opacity-100\"\n        />\n        <div\n          aria-hidden=\"true\"\n          className=\"bg-foreground/20 absolute inset-y-0 right-0 w-1.5 cursor-ew-resize opacity-0 transition-opacity group-hover/bar:opacity-100\"\n        />\n      </div>\n    )\n  },\n)\n\nGanttBar.displayName = 'GanttBar'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttBar.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttMilestone.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport { cn } from '@/lib/utils'\nimport type { GanttTask } from './types'\n\nexport interface GanttMilestoneProps extends React.HTMLAttributes<HTMLDivElement> {\n  task: GanttTask\n  left: number\n  top: number\n  size?: number\n  onTaskClick?: (task: GanttTask) => void\n}\n\nexport const GanttMilestone = React.forwardRef<HTMLDivElement, GanttMilestoneProps>(\n  ({ className, task, left, top, size = 16, onTaskClick, ...props }, ref) => {\n    return (\n      <div\n        ref={ref}\n        data-uipkge=\"\"\n        data-slot=\"gantt-milestone\"\n        style={{\n          left: `${left - size / 2}px`,\n          top: `${top - size / 2}px`,\n          width: `${size}px`,\n          height: `${size}px`,\n        }}\n        title={`${task.name} (${task.startDate})`}\n        className={cn(\n          'border-primary bg-primary absolute z-20 rotate-45 cursor-pointer rounded-xs border-2 shadow-sm transition-transform hover:scale-125',\n          className,\n        )}\n        onClick={() => onTaskClick?.(task)}\n        {...props}\n      />\n    )\n  },\n)\n\nGanttMilestone.displayName = 'GanttMilestone'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttMilestone.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/GanttContextMenu.tsx",
      "content": "'use client'\n\nimport * as React from 'react'\nimport {\n  ContextMenu,\n  ContextMenuContent,\n  ContextMenuItem,\n  ContextMenuLabel,\n  ContextMenuRadioGroup,\n  ContextMenuRadioItem,\n  ContextMenuSeparator,\n  ContextMenuShortcut,\n  ContextMenuSub,\n  ContextMenuSubContent,\n  ContextMenuSubTrigger,\n  ContextMenuTrigger,\n} from '@/components/ui/context-menu'\nimport { Edit2, Copy, Trash2, Clock, Flag, Layers } from 'lucide-react'\nimport type { GanttTask, GanttTaskStatus, GanttTaskPriority } from './types'\n\nexport interface GanttContextMenuProps {\n  task: GanttTask\n  onEdit?: (task: GanttTask) => void\n  onStatusChange?: (task: GanttTask, status: GanttTaskStatus) => void\n  onPriorityChange?: (task: GanttTask, priority: GanttTaskPriority) => void\n  onDuplicate?: (task: GanttTask) => void\n  onDelete?: (task: GanttTask) => void\n  children: React.ReactNode\n}\n\nexport function GanttContextMenu({\n  task,\n  onEdit,\n  onStatusChange,\n  onPriorityChange,\n  onDuplicate,\n  onDelete,\n  children,\n}: GanttContextMenuProps) {\n  const copyTaskId = () => {\n    if (typeof navigator !== 'undefined' && navigator.clipboard) {\n      navigator.clipboard.writeText(task.id)\n    }\n  }\n\n  return (\n    <ContextMenu>\n      <ContextMenuTrigger asChild>{children}</ContextMenuTrigger>\n      <ContextMenuContent className=\"w-56\">\n        <ContextMenuLabel className=\"flex items-center justify-between text-xs\">\n          <span className=\"truncate font-semibold\">{task.name}</span>\n          <span className=\"text-muted-foreground font-mono text-[10px]\">{task.id}</span>\n        </ContextMenuLabel>\n        <ContextMenuSeparator />\n\n        <ContextMenuItem onClick={() => onEdit?.(task)}>\n          <Edit2 className=\"mr-2 size-3.5\" />\n          <span>View Details</span>\n          <ContextMenuShortcut>↵</ContextMenuShortcut>\n        </ContextMenuItem>\n\n        <ContextMenuSub>\n          <ContextMenuSubTrigger>\n            <Clock className=\"text-primary mr-2 size-3.5\" />\n            <span>Change Status</span>\n          </ContextMenuSubTrigger>\n          <ContextMenuSubContent className=\"w-44\">\n            <ContextMenuRadioGroup value={task.status ?? 'todo'}>\n              <ContextMenuRadioItem value=\"done\" onClick={() => onStatusChange?.(task, 'done')}>\n                <span className=\"mr-2 size-2 rounded-full bg-emerald-500\" />\n                <span>Completed</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"in-progress\" onClick={() => onStatusChange?.(task, 'in-progress')}>\n                <span className=\"bg-primary mr-2 size-2 rounded-full\" />\n                <span>In Progress</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"at-risk\" onClick={() => onStatusChange?.(task, 'at-risk')}>\n                <span className=\"mr-2 size-2 rounded-full bg-amber-500\" />\n                <span>At Risk</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"blocked\" onClick={() => onStatusChange?.(task, 'blocked')}>\n                <span className=\"bg-destructive mr-2 size-2 rounded-full\" />\n                <span>Blocked</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"todo\" onClick={() => onStatusChange?.(task, 'todo')}>\n                <span className=\"bg-muted-foreground/40 mr-2 size-2 rounded-full\" />\n                <span>To Do</span>\n              </ContextMenuRadioItem>\n            </ContextMenuRadioGroup>\n          </ContextMenuSubContent>\n        </ContextMenuSub>\n\n        <ContextMenuSub>\n          <ContextMenuSubTrigger>\n            <Flag className=\"mr-2 size-3.5 text-amber-500\" />\n            <span>Set Priority</span>\n          </ContextMenuSubTrigger>\n          <ContextMenuSubContent className=\"w-40\">\n            <ContextMenuRadioGroup value={task.priority ?? 'medium'}>\n              <ContextMenuRadioItem value=\"urgent\" onClick={() => onPriorityChange?.(task, 'urgent')}>\n                <Flag className=\"text-destructive mr-2 size-3\" />\n                <span>Urgent</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"high\" onClick={() => onPriorityChange?.(task, 'high')}>\n                <Flag className=\"mr-2 size-3 text-amber-500\" />\n                <span>High</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"medium\" onClick={() => onPriorityChange?.(task, 'medium')}>\n                <Flag className=\"text-primary mr-2 size-3\" />\n                <span>Medium</span>\n              </ContextMenuRadioItem>\n              <ContextMenuRadioItem value=\"low\" onClick={() => onPriorityChange?.(task, 'low')}>\n                <Flag className=\"text-muted-foreground mr-2 size-3\" />\n                <span>Low</span>\n              </ContextMenuRadioItem>\n            </ContextMenuRadioGroup>\n          </ContextMenuSubContent>\n        </ContextMenuSub>\n\n        <ContextMenuSeparator />\n\n        <ContextMenuItem onClick={copyTaskId}>\n          <Copy className=\"mr-2 size-3.5\" />\n          <span>Copy Task ID</span>\n          <ContextMenuShortcut>⌘C</ContextMenuShortcut>\n        </ContextMenuItem>\n\n        <ContextMenuItem onClick={() => onDuplicate?.(task)}>\n          <Layers className=\"mr-2 size-3.5\" />\n          <span>Duplicate</span>\n          <ContextMenuShortcut>⌘D</ContextMenuShortcut>\n        </ContextMenuItem>\n\n        <ContextMenuSeparator />\n\n        <ContextMenuItem className=\"text-destructive focus:text-destructive\" onClick={() => onDelete?.(task)}>\n          <Trash2 className=\"mr-2 size-3.5\" />\n          <span>Delete Deliverable</span>\n          <ContextMenuShortcut>⌫</ContextMenuShortcut>\n        </ContextMenuItem>\n      </ContextMenuContent>\n    </ContextMenu>\n  )\n}\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/GanttContextMenu.tsx"
    },
    {
      "path": "packages/registry-react/components/gantt/types.ts",
      "content": "export type GanttScale = 'day' | 'week' | 'month' | 'quarter' | 'year'\n\nexport type GanttTaskStatus = 'todo' | 'in-progress' | 'done' | 'blocked' | 'at-risk'\n\nexport type GanttTaskPriority = 'low' | 'medium' | 'high' | 'urgent'\n\nexport interface GanttDependency {\n  fromId: string\n  toId: string\n  type?: 'finish-to-start' | 'start-to-start' | 'finish-to-finish'\n}\n\nexport interface GanttAssignee {\n  name: string\n  avatar?: string\n  initials?: string\n  role?: string\n}\n\nexport interface GanttTask {\n  id: string\n  name: string\n  startDate: string // YYYY-MM-DD\n  endDate: string // YYYY-MM-DD\n  progress?: number // 0 to 100\n  color?: string\n  status?: GanttTaskStatus\n  priority?: GanttTaskPriority\n  assignee?: GanttAssignee\n  isMilestone?: boolean\n  isGroup?: boolean\n  parentId?: string | null\n  isExpanded?: boolean\n  dependencies?: string[] // task IDs\n  children?: GanttTask[]\n}\n\nexport interface GanttColumn {\n  key: string\n  label: string\n  width?: string\n}\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/types.ts"
    },
    {
      "path": "packages/registry-react/components/gantt/index.ts",
      "content": "export { Gantt, useGantt, type GanttProps, type GanttContextValue } from './Gantt'\nexport { GanttHeader, type GanttHeaderProps } from './GanttHeader'\nexport { GanttTree, type GanttTreeProps } from './GanttTree'\nexport { GanttTimeline, type GanttTimelineProps } from './GanttTimeline'\nexport { GanttBar, type GanttBarProps } from './GanttBar'\nexport { GanttMilestone, type GanttMilestoneProps } from './GanttMilestone'\nexport { GanttContextMenu, type GanttContextMenuProps } from './GanttContextMenu'\nexport * from './types'\n",
      "type": "registry:ui",
      "target": "~/components/ui/gantt/index.ts"
    }
  ],
  "dependencies": [
    "lucide-react"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/react/button.json",
    "https://uipkge.dev/r/react/context-menu.json"
  ],
  "description": "Interactive Gantt chart primitive with multi-scale timeline (day/week/month/year), collapsible task tree, progress fill, milestone markers, dependency lines, and right-click GanttContextMenu.",
  "categories": [
    "data-display"
  ]
}