shaduler
Recipes

Full interactivity

Drag, resize, and drag-to-create combined via composeShadulerTaskProps.

The three opt-in hooks — useShadulerTaskResize, useShadulerTaskDrag, useShadulerRangeSelect — combined. Toggle each one off independently to see what it contributes.

Resource A
Resource B
Resource C
08:00
09:00
10:00
11:00
12:00
13:00
14:00
15:00
16:00
17:00
18:00
Morning sync
Workout
Design review
Lunch
Deep work

How the three hooks compose

Everything ships from the same @/components/ui/shaduler file:

import {
  ShadulerCells,
  ShadulerColumnHeader,
  ShadulerColumnsHeader,
  ShadulerCorner,
  ShadulerGrid,
  ShadulerTask,
  ShadulerTasksOverlay,
  ShadulerTimeColumn,
  calculateShadulerData,
  composeShadulerTaskProps,
  minutesToTime,
  useShadulerRangeSelect,
  useShadulerTaskDrag,
  useShadulerTaskResize,
} from '@/components/ui/shaduler'

Each hook returns a getXxxProps(task) function. composeShadulerTaskProps(...) merges their outputs — calling each handler in turn while keeping isDragging flags consistent.

const START_HOUR = 8
const END_HOUR = 19
const HOUR_HEIGHT_PX = 60
const TIME_INTERVAL_MIN = 15  // snap drag/resize to 15-min grid

const { getResizeProps } = useShadulerTaskResize({
  hourHeight: HOUR_HEIGHT_PX,
  timeInterval: TIME_INTERVAL_MIN,
  startHour: START_HOUR,
  endHour: END_HOUR,
  onResize: updateTask,
  onResizeEnd: updateTask,
})

const { getTaskDragProps } = useShadulerTaskDrag({
  gridRef,
  columns,
  hourHeight: HOUR_HEIGHT_PX,
  timeInterval: TIME_INTERVAL_MIN,
  startHour: START_HOUR,
  endHour: END_HOUR,
  onDrag: updateTask,
  onDragEnd: updateTask,
})

const { activeRange, isSelecting, getCellProps } = useShadulerRangeSelect({
  gridRef,
  hourHeight: HOUR_HEIGHT_PX,
  timeInterval: TIME_INTERVAL_MIN,
  startHour: START_HOUR,
  endHour: END_HOUR,
  onSelect: (range) => { /* create task */ },
})

// inside the overlay's render-prop:
const props = composeShadulerTaskProps(
  resizeOn && getResizeProps(pos.task),
  dragOn  && getTaskDragProps(pos.task),
  { isSelecting },
)
return <ShadulerTask {...props} task={pos.task} position={pos} ... />

getCellProps() is spread onto every cell so the underlying drag-to-create works:

<ShadulerCells
  hours={calc.hours}
  columns={columns}
  hourHeight={HOUR_HEIGHT_PX}
  cellProps={{ timeInterval: TIME_INTERVAL_MIN, ...getCellProps() }}
/>

The hooks ignore each other's pointer interactions — when a resize handle is dragged, drag-to-move and range-select stay quiet.

On this page