{
  "$schema": "https://shadcn-vue.com/schema/registry-item.json",
  "name": "gantt",
  "title": "Gantt",
  "type": "registry:ui",
  "files": [
    {
      "path": "packages/registry-vue/components/gantt/Gantt.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, provide, ref, toRef } from 'vue'\nimport { cn } from '@/lib/utils'\nimport type { GanttScale, GanttTask } from './types'\n\ninterface Props {\n  tasks: GanttTask[]\n  scale?: GanttScale\n  startDate?: string\n  endDate?: string\n  rowHeight?: number\n  headerHeight?: number\n  treeWidth?: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  scale: 'day',\n  rowHeight: 40,\n  headerHeight: 48,\n  treeWidth: 280,\n})\n\nconst emits = defineEmits<{\n  (e: 'update:scale', scale: GanttScale): void\n  (e: 'task-click', task: GanttTask): void\n  (e: 'task-change', task: GanttTask): void\n}>()\n\nconst currentScale = ref<GanttScale>(props.scale)\n\n// Compute start and end dates from tasks if not explicitly provided\nconst resolvedStartDate = computed(() => {\n  if (props.startDate) return new Date(props.startDate)\n  if (props.tasks.length === 0) return new Date()\n  const dates = props.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) // padding\n  return d\n})\n\nconst resolvedEndDate = computed(() => {\n  if (props.endDate) return new Date(props.endDate)\n  if (props.tasks.length === 0) {\n    const d = new Date()\n    d.setDate(d.getDate() + 30)\n    return d\n  }\n  const dates = props.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) // padding\n  return d\n})\n\nconst totalDays = computed(() => {\n  const diff = resolvedEndDate.value.getTime() - resolvedStartDate.value.getTime()\n  return Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))\n})\n\nconst columnWidth = computed(() => {\n  switch (currentScale.value) {\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})\n\nprovide('ganttContext', {\n  scale: currentScale,\n  startDate: resolvedStartDate,\n  endDate: resolvedEndDate,\n  totalDays,\n  columnWidth,\n  rowHeight: toRef(props, 'rowHeight'),\n  headerHeight: toRef(props, 'headerHeight'),\n  treeWidth: toRef(props, 'treeWidth'),\n  tasks: toRef(props, 'tasks'),\n  onTaskClick: (task: GanttTask) => emits('task-click', task),\n  onTaskChange: (task: GanttTask) => emits('task-change', task),\n})\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"gantt\"\n    :class=\"\n      cn(\n        'border-border bg-card text-card-foreground relative flex w-full flex-col overflow-hidden rounded-xl border shadow-xs',\n        props.class,\n      )\n    \"\n  >\n    <slot />\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/Gantt.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttHeader.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { inject } from 'vue'\nimport { Calendar, ChevronLeft, ChevronRight } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport { Button, ButtonGroup } from '@/components/ui/button'\nimport type { GanttScale } from './types'\n\ninterface Props {\n  title?: string\n  showScaleSwitcher?: boolean\n  showNavigation?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  title: 'Project Timeline',\n  showScaleSwitcher: true,\n  showNavigation: false,\n})\n\nconst context = inject<any>('ganttContext')\n\nfunction setScale(scale: GanttScale) {\n  if (context?.scale) {\n    context.scale.value = scale\n  }\n}\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"gantt-header\"\n    :class=\"cn('border-border bg-muted/30 flex items-center justify-between border-b px-4 py-2.5', props.class)\"\n  >\n    <div class=\"flex items-center gap-2\">\n      <Calendar class=\"text-primary size-4\" />\n      <span class=\"text-foreground text-sm font-semibold\">{{ title }}</span>\n    </div>\n\n    <div class=\"flex items-center gap-3\">\n      <slot name=\"actions\" />\n\n      <ButtonGroup v-if=\"showScaleSwitcher && context\">\n        <Button size=\"xs\" :variant=\"context.scale.value === 'day' ? 'default' : 'outline'\" @click=\"setScale('day')\">\n          Day\n        </Button>\n        <Button size=\"xs\" :variant=\"context.scale.value === 'week' ? 'default' : 'outline'\" @click=\"setScale('week')\">\n          Week\n        </Button>\n        <Button size=\"xs\" :variant=\"context.scale.value === 'month' ? 'default' : 'outline'\" @click=\"setScale('month')\">\n          Month\n        </Button>\n        <Button size=\"xs\" :variant=\"context.scale.value === 'year' ? 'default' : 'outline'\" @click=\"setScale('year')\">\n          Year\n        </Button>\n      </ButtonGroup>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttHeader.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttTree.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { inject, ref } from 'vue'\nimport { ChevronRight, ChevronDown, Flag, User } from 'lucide-vue-next'\nimport { cn } from '@/lib/utils'\nimport type { GanttTask } from './types'\n\ninterface Props {\n  showAssignee?: boolean\n  showPriority?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  showAssignee: true,\n  showPriority: true,\n})\n\nconst context = inject<any>('ganttContext')\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</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"gantt-tree\"\n    :style=\"{ width: `${context?.treeWidth.value ?? 300}px` }\"\n    :class=\"cn('border-border bg-card flex shrink-0 flex-col border-r transition-[width] select-none', props.class)\"\n  >\n    <!-- Column Headers -->\n    <div\n      :style=\"{ height: `${context?.headerHeight.value ?? 48}px` }\"\n      class=\"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 class=\"flex-1 truncate\">Deliverable</span>\n      <span v-if=\"showPriority\" class=\"w-12 shrink-0 text-center\">Pri</span>\n      <span class=\"w-16 shrink-0 text-right\">Duration</span>\n    </div>\n\n    <!-- Rows List -->\n    <div class=\"divide-border/40 flex-1 divide-y overflow-y-auto\">\n      <div\n        v-for=\"task in context?.tasks.value ?? []\"\n        :key=\"task.id\"\n        :style=\"{ height: `${context?.rowHeight.value ?? 40}px` }\"\n        :class=\"\n          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        \"\n        @click=\"context?.onTaskClick(task)\"\n      >\n        <!-- Task Name & Chevron / Indent -->\n        <div class=\"flex min-w-0 flex-1 items-center gap-1.5 pr-2\">\n          <button\n            v-if=\"task.isGroup || (task.children && task.children.length > 0)\"\n            type=\"button\"\n            class=\"text-muted-foreground hover:text-foreground flex size-4 shrink-0 items-center justify-center rounded-xs p-0.5\"\n            @click.stop=\"context?.toggleExpand?.(task.id)\"\n          >\n            <ChevronDown v-if=\"task.isExpanded !== false\" class=\"size-3\" />\n            <ChevronRight v-else class=\"size-3\" />\n          </button>\n          <span v-else-if=\"task.parentId\" class=\"w-4 shrink-0\" />\n\n          <!-- Status Indicator Dot -->\n          <span\n            v-if=\"task.status\"\n            :class=\"[\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\n          <span class=\"truncate font-medium\">{{ task.name }}</span>\n        </div>\n\n        <!-- Priority Flag -->\n        <div v-if=\"showPriority\" class=\"flex w-12 shrink-0 items-center justify-center\">\n          <Flag v-if=\"task.priority\" :class=\"cn('size-3', priorityColors[task.priority])\" />\n        </div>\n\n        <!-- Duration / Due Date Tag -->\n        <div class=\"text-muted-foreground w-16 shrink-0 text-right font-mono text-[11px]\">\n          <span v-if=\"task.isMilestone\" class=\"text-[10px] font-semibold text-amber-500\"> Milestone </span>\n          <span v-else>\n            {{ calculateDays(task.startDate, task.endDate) }}\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttTree.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttTimeline.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed, inject, ref } from 'vue'\nimport { cn } from '@/lib/utils'\nimport GanttBar from './GanttBar.vue'\nimport GanttMilestone from './GanttMilestone.vue'\nimport type { GanttTask } from './types'\n\ninterface Props {\n  showTodayLine?: boolean\n  showDependencies?: boolean\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  showTodayLine: true,\n  showDependencies: true,\n})\n\nconst context = inject<any>('ganttContext')\n\n// Generate header date columns\nconst columns = computed(() => {\n  if (!context) return []\n  const list: { date: Date; label: string; subLabel: string; isWeekend: boolean }[] = []\n  const start = new Date(context.startDate.value)\n  const total = context.totalDays.value\n\n  for (let i = 0; i < total; 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})\n\nconst timelineWidth = computed(() => {\n  if (!context) return 800\n  return columns.value.length * context.columnWidth.value\n})\n\nfunction getTaskCoordinates(task: GanttTask, index: number) {\n  if (!context) return { left: 0, width: 100, top: 0, height: 28 }\n  const start = new Date(context.startDate.value).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 * context.columnWidth.value\n  const width = durationDays * context.columnWidth.value\n  const rowHeight = context.rowHeight.value\n  const top = index * rowHeight + (rowHeight - 28) / 2\n\n  return { left, width, top, height: 28 }\n}\n\nconst todayPosition = computed(() => {\n  if (!context) return null\n  const start = new Date(context.startDate.value).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 > context.totalDays.value) return null\n  return diffDays * context.columnWidth.value + context.columnWidth.value / 2\n})\n\n// Compute SVG Dependency curves between tasks\nconst dependencyPaths = computed(() => {\n  if (!context || !props.showDependencies) return []\n  const tasksList: GanttTask[] = context.tasks.value ?? []\n  const taskMap = new Map<string, { task: GanttTask; index: number }>()\n  tasksList.forEach((t, i) => taskMap.set(t.id, { task: t, index: i }))\n\n  const paths: { d: string; fromId: string; toId: string }[] = []\n\n  tasksList.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})\n</script>\n\n<template>\n  <div\n    data-uipkge\n    data-slot=\"gantt-timeline\"\n    :class=\"cn('bg-background relative flex-1 overflow-x-auto overflow-y-hidden select-none', props.class)\"\n  >\n    <div :style=\"{ width: `${timelineWidth}px` }\" class=\"relative\">\n      <!-- Header Dates -->\n      <div\n        :style=\"{ height: `${context?.headerHeight.value ?? 48}px` }\"\n        class=\"border-border bg-muted/10 sticky top-0 z-20 flex border-b\"\n      >\n        <div\n          v-for=\"(col, i) in columns\"\n          :key=\"i\"\n          :style=\"{ width: `${context?.columnWidth.value ?? 44}px` }\"\n          :class=\"\n            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        >\n          <span class=\"text-foreground font-medium\">{{ col.label }}</span>\n          <span class=\"text-[9px]\">{{ col.subLabel }}</span>\n        </div>\n      </div>\n\n      <!-- Grid Background & Task Bars -->\n      <div class=\"relative\">\n        <!-- Column grid lines -->\n        <div class=\"pointer-events-none absolute inset-0 flex\">\n          <div\n            v-for=\"(col, i) in columns\"\n            :key=\"i\"\n            :style=\"{ width: `${context?.columnWidth.value ?? 44}px` }\"\n            :class=\"cn('border-border/30 h-full border-r', col.isWeekend && 'bg-muted/15')\"\n          />\n        </div>\n\n        <!-- Today Marker Line -->\n        <div\n          v-if=\"showTodayLine && todayPosition != null\"\n          :style=\"{ left: `${todayPosition}px` }\"\n          class=\"pointer-events-none absolute inset-y-0 z-30 flex flex-col items-center\"\n        >\n          <div\n            class=\"bg-destructive text-destructive-foreground rounded-full px-1.5 py-0.5 text-[9px] font-bold shadow-xs\"\n          >\n            Today\n          </div>\n          <div class=\"bg-destructive/60 h-full w-[1.5px] border-r border-dashed\" />\n        </div>\n\n        <!-- SVG Dependencies Overlay -->\n        <svg\n          v-if=\"dependencyPaths.length > 0\"\n          :width=\"timelineWidth\"\n          :height=\"(context?.tasks.value.length ?? 0) * (context?.rowHeight.value ?? 40)\"\n          class=\"pointer-events-none absolute inset-0 z-10\"\n        >\n          <defs>\n            <marker id=\"gantt-arrow\" viewBox=\"0 0 6 6\" refX=\"5\" refY=\"3\" markerWidth=\"6\" markerHeight=\"6\" orient=\"auto\">\n              <path d=\"M 0 0 L 6 3 L 0 6 z\" class=\"fill-primary/60\" />\n            </marker>\n          </defs>\n          <path\n            v-for=\"(p, i) in dependencyPaths\"\n            :key=\"i\"\n            :d=\"p.d\"\n            fill=\"none\"\n            class=\"stroke-primary/50\"\n            stroke-width=\"1.5\"\n            stroke-dasharray=\"3,3\"\n            marker-end=\"url(#gantt-arrow)\"\n          />\n        </svg>\n\n        <!-- Task Rows & Bars -->\n        <div\n          v-for=\"(task, idx) in context?.tasks.value ?? []\"\n          :key=\"task.id\"\n          :style=\"{ height: `${context?.rowHeight.value ?? 40}px` }\"\n          class=\"border-border/40 hover:bg-muted/10 relative border-b transition-colors\"\n        >\n          <template v-if=\"task.isMilestone\">\n            <GanttMilestone\n              :task=\"task\"\n              :left=\"getTaskCoordinates(task, idx).left\"\n              :top=\"(context?.rowHeight.value ?? 40) / 2\"\n              @click=\"context?.onTaskClick(task)\"\n            />\n          </template>\n          <template v-else>\n            <GanttBar\n              :task=\"task\"\n              :left=\"getTaskCoordinates(task, idx).left\"\n              :width=\"getTaskCoordinates(task, idx).width\"\n              :top=\"(getTaskCoordinates(task, idx).height - 28) / 2 + 6\"\n              :height=\"28\"\n              @click=\"context?.onTaskClick(task)\"\n            />\n          </template>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttTimeline.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttBar.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { computed } from 'vue'\nimport { cn } from '@/lib/utils'\nimport type { GanttTask } from './types'\n\ninterface Props {\n  task: GanttTask\n  left: number\n  width: number\n  top: number\n  height: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = defineProps<Props>()\n\nconst emits = defineEmits<{\n  (e: 'click', 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</script>\n\n<template>\n  <!-- Group Parent Task Bracket Bar -->\n  <div\n    v-if=\"task.isGroup\"\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    :class=\"\n      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        props.class,\n      )\n    \"\n    @click=\"$emit('click', task)\"\n  >\n    <span class=\"truncate\">{{ task.name }}</span>\n    <span v-if=\"task.progress != null\" class=\"font-mono text-[10px] opacity-80\"> {{ task.progress }}% </span>\n  </div>\n\n  <!-- Standard Deliverable Bar -->\n  <div\n    v-else\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    :class=\"\n      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        props.class,\n      )\n    \"\n    @click=\"$emit('click', task)\"\n  >\n    <!-- Progress fill -->\n    <div\n      v-if=\"task.progress != null && task.progress > 0\"\n      :style=\"{ width: `${task.progress}%` }\"\n      :class=\"cn('absolute inset-y-0 left-0 transition-all', progressColors[task.status ?? 'in-progress'])\"\n    />\n\n    <!-- Content -->\n    <div class=\"relative z-10 flex w-full min-w-0 items-center justify-between px-2\">\n      <span class=\"truncate font-medium\">{{ task.name }}</span>\n      <span v-if=\"task.progress != null\" class=\"ml-1 shrink-0 font-mono text-[10px] opacity-80\">\n        {{ task.progress }}%\n      </span>\n    </div>\n\n    <!-- Left / Right Resize Handles -->\n    <div\n      aria-hidden=\"true\"\n      class=\"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      class=\"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</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttBar.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttMilestone.vue",
      "content": "<script setup lang=\"ts\">\nimport type { HTMLAttributes } from 'vue'\nimport { cn } from '@/lib/utils'\nimport type { GanttTask } from './types'\n\ninterface Props {\n  task: GanttTask\n  left: number\n  top: number\n  size?: number\n  class?: HTMLAttributes['class']\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  size: 16,\n})\n\nconst emits = defineEmits<{\n  (e: 'click', task: GanttTask): void\n}>()\n</script>\n\n<template>\n  <div\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    :class=\"\n      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        props.class,\n      )\n    \"\n    :title=\"`${task.name} (${task.startDate})`\"\n    @click=\"$emit('click', task)\"\n  />\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttMilestone.vue"
    },
    {
      "path": "packages/registry-vue/components/gantt/GanttContextMenu.vue",
      "content": "<script setup lang=\"ts\">\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, CheckCircle2, AlertCircle, Flag, Calendar, Layers } from 'lucide-vue-next'\nimport type { GanttTask, GanttTaskStatus, GanttTaskPriority } from './types'\n\nconst props = defineProps<{\n  task: GanttTask\n}>()\n\nconst emits = defineEmits<{\n  (e: 'edit', task: GanttTask): void\n  (e: 'status-change', task: GanttTask, status: GanttTaskStatus): void\n  (e: 'priority-change', task: GanttTask, priority: GanttTaskPriority): void\n  (e: 'duplicate', task: GanttTask): void\n  (e: 'delete', task: GanttTask): void\n}>()\n\nfunction copyTaskId() {\n  if (typeof navigator !== 'undefined' && navigator.clipboard) {\n    navigator.clipboard.writeText(props.task.id)\n  }\n}\n</script>\n\n<template>\n  <ContextMenu>\n    <ContextMenuTrigger as-child>\n      <slot />\n    </ContextMenuTrigger>\n    <ContextMenuContent class=\"w-56\">\n      <ContextMenuLabel class=\"flex items-center justify-between text-xs\">\n        <span class=\"truncate font-semibold\">{{ task.name }}</span>\n        <span class=\"text-muted-foreground font-mono text-[10px]\">{{ task.id }}</span>\n      </ContextMenuLabel>\n      <ContextMenuSeparator />\n\n      <!-- Edit / Inspect -->\n      <ContextMenuItem @select=\"$emit('edit', task)\">\n        <Edit2 class=\"mr-2 size-3.5\" />\n        <span>View Details</span>\n        <ContextMenuShortcut>↵</ContextMenuShortcut>\n      </ContextMenuItem>\n\n      <!-- Status Submenu -->\n      <ContextMenuSub>\n        <ContextMenuSubTrigger>\n          <Clock class=\"text-primary mr-2 size-3.5\" />\n          <span>Change Status</span>\n        </ContextMenuSubTrigger>\n        <ContextMenuSubContent class=\"w-44\">\n          <ContextMenuRadioGroup :model-value=\"task.status ?? 'todo'\">\n            <ContextMenuRadioItem value=\"done\" @select=\"$emit('status-change', task, 'done')\">\n              <span class=\"mr-2 size-2 rounded-full bg-emerald-500\" />\n              <span>Completed</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"in-progress\" @select=\"$emit('status-change', task, 'in-progress')\">\n              <span class=\"bg-primary mr-2 size-2 rounded-full\" />\n              <span>In Progress</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"at-risk\" @select=\"$emit('status-change', task, 'at-risk')\">\n              <span class=\"mr-2 size-2 rounded-full bg-amber-500\" />\n              <span>At Risk</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"blocked\" @select=\"$emit('status-change', task, 'blocked')\">\n              <span class=\"bg-destructive mr-2 size-2 rounded-full\" />\n              <span>Blocked</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"todo\" @select=\"$emit('status-change', task, 'todo')\">\n              <span class=\"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      <!-- Priority Submenu -->\n      <ContextMenuSub>\n        <ContextMenuSubTrigger>\n          <Flag class=\"mr-2 size-3.5 text-amber-500\" />\n          <span>Set Priority</span>\n        </ContextMenuSubTrigger>\n        <ContextMenuSubContent class=\"w-40\">\n          <ContextMenuRadioGroup :model-value=\"task.priority ?? 'medium'\">\n            <ContextMenuRadioItem value=\"urgent\" @select=\"$emit('priority-change', task, 'urgent')\">\n              <Flag class=\"text-destructive mr-2 size-3\" />\n              <span>Urgent</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"high\" @select=\"$emit('priority-change', task, 'high')\">\n              <Flag class=\"mr-2 size-3 text-amber-500\" />\n              <span>High</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"medium\" @select=\"$emit('priority-change', task, 'medium')\">\n              <Flag class=\"text-primary mr-2 size-3\" />\n              <span>Medium</span>\n            </ContextMenuRadioItem>\n            <ContextMenuRadioItem value=\"low\" @select=\"$emit('priority-change', task, 'low')\">\n              <Flag class=\"text-muted-foreground mr-2 size-3\" />\n              <span>Low</span>\n            </ContextMenuRadioItem>\n          </ContextMenuRadioGroup>\n        </ContextMenuSubContent>\n      </ContextMenuSub>\n\n      <ContextMenuSeparator />\n\n      <!-- Copy ID -->\n      <ContextMenuItem @select=\"copyTaskId\">\n        <Copy class=\"mr-2 size-3.5\" />\n        <span>Copy Task ID</span>\n        <ContextMenuShortcut>⌘C</ContextMenuShortcut>\n      </ContextMenuItem>\n\n      <!-- Duplicate -->\n      <ContextMenuItem @select=\"$emit('duplicate', task)\">\n        <Layers class=\"mr-2 size-3.5\" />\n        <span>Duplicate</span>\n        <ContextMenuShortcut>⌘D</ContextMenuShortcut>\n      </ContextMenuItem>\n\n      <ContextMenuSeparator />\n\n      <!-- Delete -->\n      <ContextMenuItem class=\"text-destructive focus:text-destructive\" @select=\"$emit('delete', task)\">\n        <Trash2 class=\"mr-2 size-3.5\" />\n        <span>Delete Deliverable</span>\n        <ContextMenuShortcut>⌫</ContextMenuShortcut>\n      </ContextMenuItem>\n    </ContextMenuContent>\n  </ContextMenu>\n</template>\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/GanttContextMenu.vue"
    },
    {
      "path": "packages/registry-vue/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": "~/app/components/ui/gantt/types.ts"
    },
    {
      "path": "packages/registry-vue/components/gantt/index.ts",
      "content": "export { default as Gantt } from './Gantt.vue'\nexport { default as GanttHeader } from './GanttHeader.vue'\nexport { default as GanttTree } from './GanttTree.vue'\nexport { default as GanttTimeline } from './GanttTimeline.vue'\nexport { default as GanttBar } from './GanttBar.vue'\nexport { default as GanttMilestone } from './GanttMilestone.vue'\nexport { default as GanttContextMenu } from './GanttContextMenu.vue'\nexport * from './types'\n",
      "type": "registry:ui",
      "target": "~/app/components/ui/gantt/index.ts"
    }
  ],
  "dependencies": [
    "lucide-vue-next"
  ],
  "devDependencies": [],
  "registryDependencies": [
    "https://uipkge.dev/r/vue/button.json",
    "https://uipkge.dev/r/vue/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"
  ]
}