Menu

Change language

GitHub repo

Project Roadmap

blockproductivity

Full interactive project roadmap and deliverables surface: Gantt timeline with multi-scale zooming, work breakdown task tree, overall progress metrics, search and status/assignee filters, task detail slide-over Sheet, and Add Task modal dialog.

Installation

$npx shadcn-svelte@latest add https://uipkge.dev/r/svelte/project-roadmap.json
Manifest:https://uipkge.dev/r/svelte/project-roadmap.jsonInstalls to:src/lib/components/blocks/project-roadmap/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

RoadmapProject
interface RoadmapProject {
  id: string
  name: string
  description?: string
  tasks: GanttTask[]
}

Files installed (5)

  • src/lib/components/blocks/project-roadmap/ProjectRoadmap.svelte6.9 kB
    <script lang="ts" module>
      import type { GanttScale, GanttTask } from '$lib/components/ui/gantt'
    
      export interface ProjectRoadmapProps {
        initialTasks?: GanttTask[]
        title?: string
        description?: string
      }
    </script>
    
    <script lang="ts">
      import { Plus, Search, Download } from '@lucide/svelte'
      import { Button } from '$lib/components/ui/button'
      import { Input, InputGroup, InputGroupAddon } from '$lib/components/ui/input'
      import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '$lib/components/ui/select'
      import { Gantt, GanttHeader, GanttTree, GanttTimeline } from '$lib/components/ui/gantt'
      import RoadmapMetrics from './RoadmapMetrics.svelte'
      import RoadmapTaskDetail from './RoadmapTaskDetail.svelte'
      import RoadmapAddTaskDialog from './RoadmapAddTaskDialog.svelte'
    
      const DEFAULT_TASKS: GanttTask[] = [
        {
          id: 'task-1',
          name: 'System Architecture & Database Schema',
          startDate: '2026-08-01',
          endDate: '2026-08-08',
          progress: 100,
          status: 'done',
          priority: 'high',
          assignee: { name: 'Sarah Connor', initials: 'SC' },
        },
        {
          id: 'task-2',
          name: 'Auth, RBAC & Multi-Tenant Session Engine',
          startDate: '2026-08-06',
          endDate: '2026-08-16',
          progress: 85,
          status: 'in-progress',
          priority: 'urgent',
          assignee: { name: 'Marcus Rivera', initials: 'MR' },
        },
        {
          id: 'task-3',
          name: 'Real-time WebSocket Notification Bus',
          startDate: '2026-08-12',
          endDate: '2026-08-22',
          progress: 50,
          status: 'in-progress',
          priority: 'high',
          assignee: { name: 'Priya Nair', initials: 'PN' },
        },
        {
          id: 'task-4',
          name: 'Alpha Core API Release Milestone',
          startDate: '2026-08-23',
          endDate: '2026-08-23',
          isMilestone: true,
          status: 'todo',
          priority: 'urgent',
        },
        {
          id: 'task-5',
          name: 'Stripe Billing & Invoicing Integration',
          startDate: '2026-08-24',
          endDate: '2026-09-04',
          progress: 15,
          status: 'todo',
          priority: 'medium',
          assignee: { name: 'Sundar Krishnan', initials: 'SK' },
        },
        {
          id: 'task-6',
          name: 'End-to-End Playwright Automated Testing',
          startDate: '2026-08-28',
          endDate: '2026-09-08',
          progress: 0,
          status: 'todo',
          priority: 'low',
          assignee: { name: 'Diane Cho', initials: 'DC' },
        },
        {
          id: 'task-7',
          name: 'Production Cloudflare Pages Deployment',
          startDate: '2026-09-10',
          endDate: '2026-09-10',
          isMilestone: true,
          status: 'todo',
        },
      ]
    
      let {
        initialTasks = DEFAULT_TASKS,
        title = 'Engineering Q3 Deliverables',
        description = 'Track roadmap work breakdown, sprint milestones, and cross-team dependencies.',
      }: ProjectRoadmapProps = $props()
    
      // svelte-ignore state_referenced_locally -- intentionally initial-only; tasks are locally owned after mount.
      let tasks = $state<GanttTask[]>([...initialTasks])
      let searchQuery = $state('')
      let statusFilter = $state('all')
      let assigneeFilter = $state('all')
      let scale = $state<GanttScale>('day')
    
      let selectedTask = $state<GanttTask | null>(null)
      let sheetOpen = $state(false)
      let addDialogOpen = $state(false)
    
      const filteredTasks = $derived(
        tasks.filter((t) => {
          const matchesSearch = !searchQuery || t.name.toLowerCase().includes(searchQuery.toLowerCase())
          const matchesStatus = statusFilter === 'all' || t.status === statusFilter
          const matchesAssignee = assigneeFilter === 'all' || t.assignee?.name === assigneeFilter
          return matchesSearch && matchesStatus && matchesAssignee
        }),
      )
    
      function handleTaskClick(task: GanttTask) {
        selectedTask = task
        sheetOpen = true
      }
    
      function handleAddTask(newTask: GanttTask) {
        tasks.push(newTask)
      }
    </script>
    
    <div data-uipkge data-slot="project-roadmap" class="space-y-6">
      <!-- Section Top Header -->
      <div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
        <div>
          <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">{title}</h2>
          <p class="text-muted-foreground mt-0.5 text-sm">{description}</p>
        </div>
    
        <div class="flex items-center gap-2">
          <Button aria-label="Download attachment" variant="outline" size="sm">
            <Download class="mr-1 size-3.5" />
            Export
          </Button>
          <Button size="sm" onclick={() => (addDialogOpen = true)}>
            <Plus class="mr-1 size-3.5" />
            Add Task
          </Button>
        </div>
      </div>
    
      <!-- Summary Metrics -->
      <RoadmapMetrics {tasks} />
    
      <!-- Filters & Action Bar -->
      <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
        <div class="flex max-w-lg flex-1 flex-wrap items-center gap-2">
          <InputGroup size="small" class="w-56">
            <InputGroupAddon>
              <Search class="size-3.5" />
            </InputGroupAddon>
            <Input bind:value={searchQuery} placeholder="Filter tasks by name..." />
          </InputGroup>
    
          <Select bind:value={statusFilter}>
            <SelectTrigger class="h-8 w-32 text-xs">
              <SelectValue placeholder="All Status" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Status</SelectItem>
              <SelectItem value="done">Completed</SelectItem>
              <SelectItem value="in-progress">In Progress</SelectItem>
              <SelectItem value="todo">To Do</SelectItem>
            </SelectContent>
          </Select>
    
          <Select bind:value={assigneeFilter}>
            <SelectTrigger class="h-8 w-36 text-xs">
              <SelectValue placeholder="All Assignees" />
            </SelectTrigger>
            <SelectContent>
              <SelectItem value="all">All Assignees</SelectItem>
              <SelectItem value="Sarah Connor">Sarah Connor</SelectItem>
              <SelectItem value="Marcus Rivera">Marcus Rivera</SelectItem>
              <SelectItem value="Priya Nair">Priya Nair</SelectItem>
              <SelectItem value="Sundar Krishnan">Sundar Krishnan</SelectItem>
              <SelectItem value="Diane Cho">Diane Cho</SelectItem>
            </SelectContent>
          </Select>
        </div>
    
        <p class="text-muted-foreground font-mono text-xs">
          Showing <span class="text-foreground font-bold">{filteredTasks.length}</span> of
          {tasks.length} deliverables
          <span class="text-muted-foreground/60 ml-2">(Right-click task for quick actions)</span>
        </p>
      </div>
    
      <!-- Main Interactive Gantt Workspace with Right-Click Context Actions -->
      <Gantt tasks={filteredTasks} {scale} onscalechange={(s) => (scale = s)} ontaskclick={handleTaskClick} class="h-[480px]">
        <GanttHeader {title} />
        <div class="flex min-h-0 flex-1 overflow-hidden">
          <GanttTree />
          <GanttTimeline />
        </div>
      </Gantt>
    
      <!-- Task Detail Drawer -->
      <RoadmapTaskDetail bind:open={sheetOpen} task={selectedTask} />
    
      <!-- Add Task Modal Dialog -->
      <RoadmapAddTaskDialog bind:open={addDialogOpen} onAddTask={handleAddTask} />
    </div>
    
  • src/lib/components/blocks/project-roadmap/RoadmapMetrics.svelte2.8 kB
  • src/lib/components/blocks/project-roadmap/RoadmapTaskDetail.svelte3.8 kB
  • src/lib/components/blocks/project-roadmap/RoadmapAddTaskDialog.svelte5 kB
  • src/lib/components/blocks/project-roadmap/types.ts0.2 kB

Raw manifest:https://uipkge.dev/r/svelte/project-roadmap.json