dnd-timeline

repository·main·Indexed 42 days ago

https://github.com/samuelarbibe/dnd-timeline

A headless, hook-based timeline library built on top of dnd-kit. It provides the logic and positional styling for complex timeline interactions—including dragging, nesting, and sorting—while leaving all visual styling to the developer. Features include RTL support, touch support, and a fully controlled state model using TimelineContext, useItem, and useRow hooks.

Tokens
29.3K
Snippets
84
Records
133
Agent score
51%

What's inside dnd-timeline

  1. What is dnd-timeline?

    main

    dnd-timeline is a headless-UI library designed for creating timelines, built on top of @dnd-kit.

    Key characteristics include:

    • Headless: Contains 0 styling, except for functional styling like position and z-index. You are responsible for the visual design.
    • Hook-based: Provides hooks such as useItem and useRow to integrate timeline logic into your existing component architecture.
    • Flexible: Exposes utility functions and positional styling that can be used with any styling library.
    • Performant: Uses CSS transformations for intermediate states and animations to ensure 0 re-renders during these transitions.
    • RTL Support: Natively supports Right-to-Left layouts; simply add dir="rtl" to a parent container.
    • Touch Support: Works with touch devices by default and allows for sensor configuration via @dnd-kit sensors.
  2. Get started with dnd-timeline

    main

    The dnd-timeline library is a headless timeline library built on top of dnd-kit. It provides the logic and context necessary to build interactive, draggable timelines.

    To begin using the library, you should consult the following core sections:

    • Guide: Covers the Introduction and Installation.
    • Documentation: Details the core primitives like TimelineContext, Row, Subrow, and Item.
    • Examples: Provides a basic implementation reference.
  3. Key features of dnd-timeline

    main

    The library provides several advanced timeline capabilities:

    • Stacked rows: Items with intersecting relevance are automatically stacked within the same row.
    • Snap to Grid: Items snap to a predefined grid upon being dropped; the grid can adapt based on the current zoom level.
    • Time Axis & Cursor: Supports an optional time axis with zoom-dependent markers and a time cursor to indicate current time.
    • Pan and Zoom:
      • Zoom: Hold <kbd>Ctrl</kbd> and use the mouse wheel.
      • Pan: Hold <kbd>Ctrl + Shift</kbd> and use the mouse wheel.
    • Dynamic Control: Rows and items can be dynamically disabled based on your own client-side logic.
    • External DnD Integration: Designed to work alongside other drag-and-drop interactions, allowing items to be dragged into or out of the timeline.
  4. What is an Item in dnd-timeline?

    main

    An Item is an object placed inside a Row. Its position and size are determined relative to its values.

    Technically, an Item is an extension of dnd-kit's Draggable concept. Because dnd-timeline is a headless library, it does not provide a pre-built <Item /> component; you must implement your own using the provided useItem hook to handle positioning and drag-and-drop functionality.

  5. What is a Row in dnd-timeline

    main

    A Row is a container for items within the timeline. The timeline is composed of multiple rows stacked vertically, where each row holds the items related to that specific row.

    Technically, a Row is an extension of dnd-kit's Droppable concept. Because dnd-timeline is a headless library, it does not provide a pre-built <Row /> component; instead, you must implement your own using the provided useRow hook to handle the necessary drag-and-drop references and styling.

  6. How TimelineContext works

    main

    The TimelineContext is the central provider for the dnd-timeline library. It manages the timeline's state, wraps your code with dnd-kit's DndContext, and handles event callbacks.

    Key concepts:

    • Fully Controlled: The library is fully controlled. You are responsible for managing the state (like rows, items, and range) and providing update functions via callbacks.
    • Mandatory Wrapper: All timeline components must be wrapped in a <TimelineContext> to function correctly.
    • Event Helpers: Every event handler provided by the context includes helper functions (like getSpanFromDragEvent or getSpanFromResizeEvent) to safely calculate an item's new span. Do not calculate spans manually; always use these helpers.
    function App() {
      return (
        <TimelineContext>
          {/* Your timeline components here */}
        </TimelineContext>
      )
    }
  7. Core concepts of dnd-timeline

    main

    Headless UI

    dnd-timeline is a headless library, meaning it contains zero styling for aesthetics. It only provides functional styling necessary for operation, such as position and z-index. You are responsible for applying your own styles using libraries like Tailwind CSS, MUI, or Ant Design.

    Hook-based Architecture

    The library exposes hooks like useItem and useRow. These hooks are designed to integrate into your existing component architecture, providing the logic needed for timeline interactions without dictating your DOM structure.

    dnd-kit Integration

    dnd-timeline is built on top of dnd-kit. Consequently, all features and sensor configurations available in dnd-kit are applicable to dnd-timeline.

  8. Use helper functions to update item spans

    main

    When handling drag or resize events, dnd-timeline provides helper functions inside the event's active.data.current object. You should use these to calculate the new Span instead of calculating it manually. This ensures compatibility with snapping and custom strategies.

    For Drag Events: Use event.active.data.current.getSpanFromDragEvent?.(event) inside onDragEnd, onDragMove, etc.

    For Resize Events: Use event.active.data.current.getSpanFromResizeEvent?.(event) inside onResizeMove or onResizeEnd.

    const onDragEnd = (event: DragEndEvent) => {
      const overedId = event.over?.id.toString();
      if (!overedId) return;
    
      const activeItemId = event.active.id;
      // Use the helper to get the updated span
      const updatedSpan = event.active.data.current.getSpanFromDragEvent?.(event);
      
      if (updatedSpan) {
        // update your state using the updated span
      }
    }
  9. Understand the Row concept in dnd-timeline

    main

    A Row is a container for items within the timeline. The timeline is composed of multiple rows stacked vertically, where each row holds its specific related items.

    Technically, a Row is an extension of dnd-kit's Droppable concept. Because dnd-timeline is a headless library, it does not provide a pre-built <Row /> component; you must implement your own using the provided hooks and types to handle layout and drag-and-drop functionality.

  10. Understand PanEndEvent delta behavior

    main

    The PanEndEvent properties determine how the timeline's timeframe changes:

    • deltaX: Controls horizontal panning. It is translated into symmetrical changes to the start and end of the timeline's timeframe (accounting for rtl settings). This creates the effect of moving the view left or right.
    • deltaY: Controls zooming.
      • If clientX and clientY are not provided: The zoom is mirrored (symmetrical addition/subtraction to start and end).
      • If clientX and clientY are provided: The zoom is asymmetrical and biased by the cursor position. The zoom will be directed towards the point where the cursor is located relative to the timeline.
  11. Create custom components using TimelineContext

    main
    Since dnd-timeline is a headless library, you can use its internal context to build custom UI components that interact with the timeline state. For example, you can build a Time Axis or a Time Cursor to indicate the current time and the range represented in the timeline.