Menu

Change language

GitHub repo

Gantt

ganttui

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.

Installation

$npx shadcn-vue@latest add https://uipkge.dev/r/vue/gantt.json
Named registry:npx shadcn-vue@latest add @uipkge/ganttInstalls to:app/components/ui/gantt/

Examples

Loading interactive previews…

Props

NameType / ValuesDefaultRequired
tasksGanttTask[]—required
scaleGanttScale'day'optional
startDatestring—optional
endDatestring—optional
rowHeightnumber40optional
headerHeightnumber48optional
treeWidthnumber280optional
classHTMLAttributes['class']—optional

Schema

Type aliases from this item's source — use them to shape the data you pass in.

GanttDependency
interface GanttDependency {
  fromId: string
  toId: string
  type?: 'finish-to-start' | 'start-to-start' | 'finish-to-finish'
}
GanttAssignee
interface GanttAssignee {
  name: string
  avatar?: string
  initials?: string
  role?: string
}
GanttTask
interface GanttTask {
  id: string
  name: string
  startDate: string // YYYY-MM-DD
  endDate: string // YYYY-MM-DD
  progress?: number // 0 to 100
  color?: string
  status?: GanttTaskStatus
  priority?: GanttTaskPriority
  assignee?: GanttAssignee
  isMilestone?: boolean
  isGroup?: boolean
  parentId?: string | null
  isExpanded?: boolean
  dependencies?: string[] // task IDs
  children?: GanttTask[]
}
GanttColumn
interface GanttColumn {
  key: string
  label: string
  width?: string
}

npm dependencies

Used by

Files installed (9)

  • app/components/ui/gantt/Gantt.vue2.6 kB
    <script setup lang="ts">
    import type { HTMLAttributes } from 'vue'
    import { computed, provide, ref, toRef } from 'vue'
    import { cn } from '@/lib/utils'
    import type { GanttScale, GanttTask } from './types'
    
    interface Props {
      tasks: GanttTask[]
      scale?: GanttScale
      startDate?: string
      endDate?: string
      rowHeight?: number
      headerHeight?: number
      treeWidth?: number
      class?: HTMLAttributes['class']
    }
    
    const props = withDefaults(defineProps<Props>(), {
      scale: 'day',
      rowHeight: 40,
      headerHeight: 48,
      treeWidth: 280,
    })
    
    const emits = defineEmits<{
      (e: 'update:scale', scale: GanttScale): void
      (e: 'task-click', task: GanttTask): void
      (e: 'task-change', task: GanttTask): void
    }>()
    
    const currentScale = ref<GanttScale>(props.scale)
    
    // Compute start and end dates from tasks if not explicitly provided
    const resolvedStartDate = computed(() => {
      if (props.startDate) return new Date(props.startDate)
      if (props.tasks.length === 0) return new Date()
      const dates = props.tasks.map((t) => new Date(t.startDate).getTime())
      const min = Math.min(...dates)
      const d = new Date(min)
      d.setDate(d.getDate() - 3) // padding
      return d
    })
    
    const resolvedEndDate = computed(() => {
      if (props.endDate) return new Date(props.endDate)
      if (props.tasks.length === 0) {
        const d = new Date()
        d.setDate(d.getDate() + 30)
        return d
      }
      const dates = props.tasks.map((t) => new Date(t.endDate).getTime())
      const max = Math.max(...dates)
      const d = new Date(max)
      d.setDate(d.getDate() + 7) // padding
      return d
    })
    
    const totalDays = computed(() => {
      const diff = resolvedEndDate.value.getTime() - resolvedStartDate.value.getTime()
      return Math.max(1, Math.ceil(diff / (1000 * 60 * 60 * 24)))
    })
    
    const columnWidth = computed(() => {
      switch (currentScale.value) {
        case 'day':
          return 44
        case 'week':
          return 120
        case 'month':
          return 180
        case 'year':
          return 240
        default:
          return 44
      }
    })
    
    provide('ganttContext', {
      scale: currentScale,
      startDate: resolvedStartDate,
      endDate: resolvedEndDate,
      totalDays,
      columnWidth,
      rowHeight: toRef(props, 'rowHeight'),
      headerHeight: toRef(props, 'headerHeight'),
      treeWidth: toRef(props, 'treeWidth'),
      tasks: toRef(props, 'tasks'),
      onTaskClick: (task: GanttTask) => emits('task-click', task),
      onTaskChange: (task: GanttTask) => emits('task-change', task),
    })
    </script>
    
    <template>
      <div
        data-uipkge
        data-slot="gantt"
        :class="
          cn(
            'border-border bg-card text-card-foreground relative flex w-full flex-col overflow-hidden rounded-xl border shadow-xs',
            props.class,
          )
        "
      >
        <slot />
      </div>
    </template>
    
  • app/components/ui/gantt/GanttHeader.vue1.8 kB
  • app/components/ui/gantt/GanttTree.vue4 kB
  • app/components/ui/gantt/GanttTimeline.vue7.5 kB
  • app/components/ui/gantt/GanttBar.vue3.3 kB
  • app/components/ui/gantt/GanttMilestone.vue0.9 kB
  • app/components/ui/gantt/GanttContextMenu.vue5.4 kB
  • app/components/ui/gantt/types.ts0.9 kB
  • app/components/ui/gantt/index.ts0.4 kB

Raw manifest:https://uipkge.dev/r/vue/gantt.json