npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

p1-gantt

v0.5.0

Published

Reusable Gantt chart React component

Downloads

109

Readme

p1-gantt

A reusable, dependency-free React Gantt / timeline component written in TypeScript.

Render rows of time-positioned bars against a multi-level time axis, with opt-in dragging, resizing, cross-row moves, snapping, background bands, frozen rows, and fully custom rendering — all driven by a single config object and styled through inline-style hooks (no CSS import required).

Features

  • 🧱 Multi-level time header — stack any combination of minute/hour/day/week/month/quarter/year levels.
  • ↔️ Drag to move & resize — enabled per-chart by providing callbacks, opt-out per item.
  • ⤵️ Cross-row moves — drag a bar onto another row with a live drop-target highlight.
  • 🧲 Snapping — snap drag deltas to any minute granularity.
  • 🟦 Background bands — non-interactive ranges for breaks, holidays, unavailable slots.
  • 📌 Frozen rows — pin rows to the top while the rest scroll.
  • Empty-slot clicks — click empty grid to create new items, with a live ghost preview.
  • 💬 Tooltips & custom renderers — bring your own JSX for bars, labels, headers, tooltips, bands.
  • 🧬 TypeScript-first — generic over your row / item / band data types.
  • 🔗 Dependency linksconfig.links draws FS/SS/FF/SF arrows. linkSourceId + onLinkCreate for click-to-link. Left-edge resize as well as right.

Installation

npm install p1-gantt
# or
yarn add p1-gantt

react and react-dom (v18 or v19) are peer dependencies.

Quick start

The chart fills its parent, so give the container an explicit height.

import { Gantt } from 'p1-gantt';
import type { GanttConfig } from 'p1-gantt';

export function Schedule() {
  const config: GanttConfig = {
    xAxis: {
      start: new Date(2024, 0, 1, 8, 0),
      end: new Date(2024, 0, 1, 18, 0),
      cellWidth: 60,
      levels: [
        { unit: 'hour', step: 2, labelFormat: 'HH:mm' },
        { unit: 'hour', labelFormat: 'HH' },
      ],
    },
    rows: [
      { id: 'room-1', data: { name: 'Room 1' } },
      { id: 'room-2', data: { name: 'Room 2' } },
    ],
    items: [
      { id: 'a', rowId: 'room-1', start: new Date(2024, 0, 1, 9), end: new Date(2024, 0, 1, 11), label: 'Consult' },
      { id: 'b', rowId: 'room-2', start: new Date(2024, 0, 1, 13), end: new Date(2024, 0, 1, 15), label: 'Follow-up' },
    ],
    renderers: {
      renderLabel: (row) => <strong>{row.data.name}</strong>,
    },
    interactions: {
      snapMinutes: 15,
      // Providing onItemMove enables dragging; onItemResize enables resizing.
      onItemMove: ({ item, nextStart, nextEnd, nextRowId }) => {
        // Persist the change and update your own `items` state.
      },
    },
  };

  return (
    <div style={{ height: 400 }}>
      <Gantt config={config} />
    </div>
  );
}

Controlled data: the component is presentational. It never mutates items itself — your onItemMove / onItemResize / onEmptyClick handlers persist the change and feed a new items array back in. On the next items change the bar settles into its new position seamlessly (the drop is optimistically pinned in the meantime).

Configuration

Everything is passed via the single config: GanttConfig prop.

GanttConfig

| Field | Type | Default | Description | | --- | --- | --- | --- | | xAxis | TimeAxisConfig | — | Time range, cell width and header levels. | | rows | GanttRow[] | — | One entry per lane. | | items | GanttItem[] | — | The bars to position. | | bands | GanttBand[] | [] | Non-interactive background ranges. | | renderers | GanttRenderers | — | Custom JSX for bars, labels, headers, tooltips, bands. | | styles | GanttStyleHooks | — | Per-element inline-style overrides. | | interactions | GanttInteractions | — | Callbacks + drag/snap behavior. | | rowHeight | number | 40 | Default row height (px). | | headerHeight | number | 32 | Height of each header level (px). | | labelWidth | number | 180 | Width of the sticky left label column (px). | | tooltipPos | 'top' \| 'bottom' \| 'left' \| 'right' | 'top' | Tooltip anchor side. | | stackOverlaps | boolean | false | Lay overlapping items out into separate lanes instead of overlapping them. | | expanded | boolean | false | When stacking, give each lane full row height (vs. a compact fanned-out offset). | | onToggle | () => void | — | Invoked by a custom renderToggle / renderLabel control to flip expanded. |

TimeAxisConfig

| Field | Type | Description | | --- | --- | --- | | start / end | Date | Visible time range (half-open: end excluded). | | cellWidth | number | Pixel width of one lowest-level cell. | | levels | TimeHeaderLevel[] | Header rows, coarsest first; the last level sets the grid resolution. |

Each level: { unit, step?, labelFormat? }, where unit is one of minute | hour | day | week | month | quarter | year. labelFormat supports the tokens EEEE/EEE, d, MMMM/MMM, yyyy/yy, HH, mm, and QQQ (formatted via Intl, en-US, 24-hour).

GanttRow / GanttItem / GanttBand

interface GanttRow<T>  { id: string | number; data: T; height?: number; frozen?: boolean }
interface GanttItem<T> { id: string | number; rowId: string | number; start: Date; end: Date; label: string; movable?: boolean; resizable?: boolean; data?: T }
interface GanttBand<T> { id?: string | number; rowId: string | number | '*'; start: Date; end: Date; kind?: string; label?: string; data?: T }

A band with rowId: '*' renders on every row.

GanttInteractions

| Field | Type | Description | | --- | --- | --- | | onItemClick | (item) => void | Click on a bar. | | onItemMove | (req) => void \| Promise<…> | Defining this enables drag-to-move. Receives { item, nextStart, nextEnd, nextRowId }. | | onItemResize | (req) => void \| Promise<…> | Defining this enables resize. Receives { item, nextEnd }. | | onEmptyClick | (req) => void | Click on empty grid. Receives { rowId, start, end }; shows a ghost preview on hover. | | snapMinutes | number | Snap drag deltas to this many minutes. Default: no snapping. | | allowCrossRowMove | boolean | Allow dragging a bar to a different row. Default false. | | minItemMinutes | number | Minimum duration on resize. Default: snapMinutes or 5. |

Drag/resize are gated by both the chart callback and the per-item movable / resizable flags — set either to false to lock an item.

renderers & styles

renderers returns JSX for renderHeaderCell, renderLabel (receives the row plus a { hasOverlaps, isExpanded, onToggle } context), renderToggle (an expand/collapse control rendered in the header corner), renderItem, renderTooltip, and renderBand. styles provides inline-style hooks (getItemStyle, getRowStyle, getHeaderStyle, getBandStyle, getDropTargetRowStyle, getEmptyClickPreviewStyle, …) plus border configs (chartBorder, headerBorder, labelBorder, contentBorder). All are optional and fall back to neutral defaults.

Notes & current limitations

  • Container height is required — the chart is height: 100%.
  • Overlapping items — by default, items sharing a row overlap visually. Set stackOverlaps to fan them into separate lanes, and expanded to give each lane full row height.
  • Keyboard / a11y — interactions are currently pointer-only.

Development

nvm use            # Node >= 18 (see .nvmrc)
yarn install
yarn build         # bundle (Vite) + emit types (tsc)
yarn test          # run unit tests (Vitest)
yarn test:watch    # watch mode

License

Apache-2.0