{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "timeline",
  "type": "registry:block",
  "title": "Timeline",
  "description": "A Gantt-chart style timeline with draggable time slots.",
  "dependencies": ["@radix-ui/react-slot", "@dnd-kit/core", "tunnel-rat"],
  "files": [
    {
      "path": "registry/abui/ui/timeline.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { useState, useRef, useEffect, createContext, useContext, useMemo } from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport {\n  DndContext,\n  DragOverlay,\n  useSensor,\n  useSensors,\n  PointerSensor,\n  closestCenter,\n  useDraggable,\n  useDroppable,\n} from \"@dnd-kit/core\"\nimport tunnel from \"tunnel-rat\"\nimport { cn } from \"@/lib/utils\"\n\n/* ============================================================================\n * TYPES & INTERFACES\n * ========================================================================== */\n\nexport interface TimelineSlotData {\n  id: string\n  rowId: string\n  startTime: string // \"14:30\"\n  duration: number // minutes\n  [key: string]: any // Additional custom data\n}\n\nexport interface TimelineRowData {\n  id: string\n  label: string\n  [key: string]: any // Additional custom data\n}\n\nexport interface TimelineConfig {\n  startHour: number\n  endHour: number\n  snapIntervalMinutes?: number\n  columnWidth?: number\n}\n\n/* ============================================================================\n * CONTEXT\n * ========================================================================== */\n\ntype TimelineContextValue = {\n  // Configuration\n  config: TimelineConfig\n  pixelsPerMinute: number\n  timelineWidth: number\n\n  // Refs\n  timelineRef: React.RefObject<HTMLDivElement | null>\n  columnRef: React.RefObject<HTMLDivElement | null>\n\n  // Tunnel for drag preview\n  dragPreviewTunnel: ReturnType<typeof tunnel>\n\n  // Callbacks\n  onSlotPositionChange?: (slotId: string, newTime: string, newRowId: string) => Promise<boolean>\n  onValidateDrop?: (slotId: string, newTime: string, newRowId: string) => boolean\n  onSlotClick?: (slotId: string) => void\n}\n\nconst TimelineContext = createContext<TimelineContextValue | null>(null)\n\nfunction useTimeline() {\n  const context = useContext(TimelineContext)\n  if (!context) {\n    throw new Error(\"Timeline components must be used within TimelineProvider\")\n  }\n  return context\n}\n\n/* ============================================================================\n * UTILITIES\n * ========================================================================== */\n\nexport const timeToMinutes = (time: string): number => {\n  const [hours, minutes] = time.split(\":\").map(Number)\n  return hours * 60 + minutes\n}\n\nexport const minutesToTime = (minutes: number): string => {\n  const hours = Math.floor(minutes / 60)\n  const mins = minutes % 60\n  return `${hours.toString().padStart(2, \"0\")}:${mins.toString().padStart(2, \"0\")}`\n}\n\n/* ============================================================================\n * TIMELINE PROVIDER\n * ========================================================================== */\n\ninterface TimelineProviderProps {\n  children: React.ReactNode\n  config: TimelineConfig\n  percentageInView?: number\n  onSlotPositionChange?: (slotId: string, newTime: string, newRowId: string) => Promise<boolean>\n  onValidateDrop?: (slotId: string, newTime: string, newRowId: string) => boolean\n  onSlotClick?: (slotId: string) => void\n  style?: React.CSSProperties\n  className?: string\n}\n\nexport function TimelineProvider({\n  children,\n  config,\n  percentageInView = 100,\n  onSlotPositionChange,\n  onValidateDrop,\n  onSlotClick,\n  style,\n  className,\n}: TimelineProviderProps) {\n  const [viewportWidth, setViewportWidth] = useState(0)\n\n  const timelineRef = useRef<HTMLDivElement>(null)\n  const columnRef = useRef<HTMLDivElement>(null)\n\n  // Create tunnel for drag preview (stable across renders)\n  const dragPreviewTunnel = useMemo(() => tunnel(), [])\n\n  const columnWidth = config.columnWidth || 112\n\n  // Measure viewport\n  useEffect(() => {\n    const measure = () => {\n      if (timelineRef.current) {\n        setViewportWidth(timelineRef.current.clientWidth - columnWidth)\n      }\n    }\n\n    measure()\n    window.addEventListener(\"resize\", measure)\n    const timeout = setTimeout(measure, 100)\n\n    return () => {\n      window.removeEventListener(\"resize\", measure)\n      clearTimeout(timeout)\n    }\n  }, [columnWidth])\n\n  // Calculate timeline dimensions\n  const totalMinutes = (config.endHour - config.startHour) * 60\n  const basePixelsPerMinute = viewportWidth > 0 ? viewportWidth / totalMinutes : 10\n  const pixelsPerMinute = basePixelsPerMinute * (100 / percentageInView)\n  const timelineWidth = totalMinutes * pixelsPerMinute\n\n  const contextValue: TimelineContextValue = {\n    config,\n    pixelsPerMinute,\n    timelineWidth,\n    timelineRef,\n    columnRef,\n    dragPreviewTunnel,\n    onSlotPositionChange,\n    onValidateDrop,\n    onSlotClick,\n  }\n\n  return (\n    <TimelineContext.Provider value={contextValue}>\n      <div\n        data-slot=\"timeline-wrapper\"\n        style={\n          {\n            \"--timeline-column-width\": `${columnWidth}px`,\n            \"--timeline-width\": `${timelineWidth}px`,\n            \"--timeline-pixels-per-minute\": pixelsPerMinute,\n            ...style,\n          } as React.CSSProperties\n        }\n        className={cn(\"relative w-full\", className)}\n      >\n        {children}\n      </div>\n    </TimelineContext.Provider>\n  )\n}\n\n/* ============================================================================\n * TIMELINE (Main Container with DnD)\n * ========================================================================== */\n\ninterface TimelineProps {\n  slots: TimelineSlotData[]\n  rows: TimelineRowData[]\n  children: React.ReactNode\n  className?: string\n}\n\nexport function Timeline({ slots, rows, children, className }: TimelineProps) {\n  const { config, pixelsPerMinute, timelineRef, dragPreviewTunnel, onSlotPositionChange, onValidateDrop } =\n    useTimeline()\n\n  const [mousePosition, setMousePosition] = useState<{ x: number; y: number } | null>(null)\n\n  const sensors = useSensors(\n    useSensor(PointerSensor, {\n      activationConstraint: { distance: 8 },\n    }),\n  )\n\n  const snapInterval = config.snapIntervalMinutes || 15\n  const columnWidth = config.columnWidth || 112\n\n  // Snapping utilities\n  const snapToInterval = (minutes: number): number => {\n    return Math.round(minutes / snapInterval) * snapInterval\n  }\n\n  const getSnappedDelta = (deltaX: number): number => {\n    const deltaMinutes = deltaX / pixelsPerMinute\n    const snappedDeltaMinutes = Math.round(deltaMinutes / snapInterval) * snapInterval\n    return snappedDeltaMinutes * pixelsPerMinute\n  }\n\n  const calculateNewTime = (originalTime: string, deltaX: number): string => {\n    const originalMinutes = timeToMinutes(originalTime)\n    const deltaMinutes = Math.round(deltaX / pixelsPerMinute)\n    const newMinutes = originalMinutes + deltaMinutes\n    const snappedMinutes = snapToInterval(newMinutes)\n    const clampedMinutes = Math.max(config.startHour * 60, Math.min((config.endHour - 1) * 60, snappedMinutes))\n    return minutesToTime(clampedMinutes)\n  }\n\n  // Mouse handlers\n  const handleMouseMove = (e: React.MouseEvent) => {\n    if (timelineRef.current) {\n      const rect = timelineRef.current.getBoundingClientRect()\n      setMousePosition({\n        x: e.clientX - rect.left + timelineRef.current.scrollLeft,\n        y: e.clientY - rect.top,\n      })\n    }\n  }\n\n  const handleMouseLeave = () => {\n    setMousePosition(null)\n  }\n\n  // Calculate mouse time\n  const mouseTime = mousePosition\n    ? minutesToTime(Math.floor((mousePosition.x - columnWidth) / pixelsPerMinute) + config.startHour * 60)\n    : null\n\n  // Drag handlers\n  const [localActiveSlot, setLocalActiveSlot] = useState<string | null>(null)\n  const [localOverRow, setLocalOverRow] = useState<string | null>(null)\n  const [localDraggedTime, setLocalDraggedTime] = useState<string | null>(null)\n  const [isValid, setIsValid] = useState(true)\n\n  const handleDragStart = (event: any) => {\n    setLocalActiveSlot(event.active.id)\n  }\n\n  const handleDragOver = (event: any) => {\n    if (event.over) {\n      setLocalOverRow(event.over.id)\n    } else {\n      setLocalOverRow(null)\n      setLocalDraggedTime(null)\n    }\n  }\n\n  const handleDragMove = (event: any) => {\n    const { over, active, delta } = event\n\n    if (over) {\n      const slot = slots.find((s: TimelineSlotData) => s.id === active.id)\n      if (slot) {\n        const newTime = calculateNewTime(slot.startTime, delta.x)\n        setLocalDraggedTime(newTime)\n\n        // Validate with host\n        const valid = onValidateDrop ? onValidateDrop(active.id, newTime, over.id) : true\n        setIsValid(valid)\n      }\n    }\n  }\n\n  const handleDragCancel = () => {\n    // Clean up all drag state when drag is cancelled (e.g., ESC key)\n    setLocalActiveSlot(null)\n    setLocalOverRow(null)\n    setLocalDraggedTime(null)\n    setIsValid(true)\n  }\n\n  const handleDragEnd = async (event: any) => {\n    const { active, over, delta } = event\n\n    setLocalActiveSlot(null)\n    setLocalOverRow(null)\n    setLocalDraggedTime(null)\n    setIsValid(true)\n\n    if (over) {\n      const slot = slots.find((s: TimelineSlotData) => s.id === active.id)\n      if (!slot) return\n\n      const newTime = calculateNewTime(slot.startTime, delta.x)\n      const newRowId = over.id\n\n      // Validate\n      if (onValidateDrop && !onValidateDrop(active.id, newTime, newRowId)) {\n        return\n      }\n\n      // Check if anything changed\n      if (slot.rowId === newRowId && slot.startTime === newTime) {\n        return\n      }\n\n      // Call position change handler\n      if (onSlotPositionChange) {\n        await onSlotPositionChange(active.id, newTime, newRowId)\n      }\n    }\n  }\n\n  return (\n    <DndContext\n      sensors={sensors}\n      collisionDetection={closestCenter}\n      onDragStart={handleDragStart}\n      onDragOver={handleDragOver}\n      onDragMove={handleDragMove}\n      onDragEnd={handleDragEnd}\n      onDragCancel={handleDragCancel}\n    >\n      <div className=\"relative w-full\">\n        {/* Mouse time indicator */}\n        {mousePosition && mouseTime && !localActiveSlot && (\n          <TimelineMouseIndicator mouseX={mousePosition.x} time={mouseTime} />\n        )}\n\n        <div\n          ref={timelineRef}\n          data-slot=\"timeline-grid\"\n          className={cn(\"relative overflow-auto border bg-background\", className)}\n          onMouseMove={handleMouseMove}\n          onMouseLeave={handleMouseLeave}\n        >\n          {React.Children.map(children, child => {\n            if (React.isValidElement(child)) {\n              return React.cloneElement(child as React.ReactElement<any>, {\n                slots,\n                rows,\n                activeSlotId: localActiveSlot,\n                overRowId: localOverRow,\n                draggedNewTime: localDraggedTime,\n                isValidDrop: isValid,\n                getSnappedDelta,\n                _showDropRegion: !!(localActiveSlot && localDraggedTime),\n                _dropRegionTime: localDraggedTime,\n              })\n            }\n            return child\n          })}\n        </div>\n\n        {/* Drag overlay - receives content from tunnel */}\n        <DragOverlay dropAnimation={null}>\n          {localActiveSlot &&\n            (() => {\n              const activeSlot = slots.find((s: TimelineSlotData) => s.id === localActiveSlot)\n              if (!activeSlot) return null\n\n              console.log(\"📺 DragOverlay rendering for slot:\", activeSlot.id)\n\n              return (\n                <div\n                  data-slot=\"timeline-drag-overlay\"\n                  style={{\n                    width: `${Math.max(activeSlot.duration * pixelsPerMinute, 60)}px`,\n                    height: \"54px\",\n                    position: \"relative\",\n                  }}\n                >\n                  {/* Receive tunneled content from the active slot */}\n                  <dragPreviewTunnel.Out />\n                </div>\n              )\n            })()}\n        </DragOverlay>\n      </div>\n    </DndContext>\n  )\n}\n\n/* ============================================================================\n * TIMELINE HEADER\n * ========================================================================== */\n\ninterface TimelineHeaderProps {\n  className?: string\n  columnLabel?: React.ReactNode\n}\n\nexport function TimelineHeader({ className, columnLabel = \"Row\" }: TimelineHeaderProps) {\n  const { config, pixelsPerMinute, columnRef } = useTimeline()\n\n  // Generate hour markers\n  const hourMarkers = []\n  for (let hour = config.startHour; hour < config.endHour; hour++) {\n    hourMarkers.push({\n      hour,\n      label: `${hour}:00`,\n      position: (hour - config.startHour) * 60 * pixelsPerMinute,\n    })\n  }\n\n  return (\n    <div data-slot=\"timeline-header\" className={cn(\"sticky top-0 z-10 bg-background border-b\", className)}>\n      <div className=\"flex h-12\">\n        <div\n          ref={columnRef}\n          data-slot=\"timeline-header-column\"\n          className=\"sticky left-0 w-[var(--timeline-column-width)] bg-background border-r flex items-center px-4 font-semibold text-sm z-1\"\n        >\n          {columnLabel}\n        </div>\n        <div data-slot=\"timeline-header-markers\" className=\"relative flex-1\">\n          {hourMarkers.map(marker => (\n            <div\n              key={marker.hour}\n              data-slot=\"timeline-hour-marker\"\n              className=\"absolute top-0 bottom-0 flex items-center pl-2 text-xs text-muted-foreground\"\n              style={{ left: `${marker.position}px` }}\n            >\n              {marker.label}\n            </div>\n          ))}\n        </div>\n      </div>\n    </div>\n  )\n}\n\n/* ============================================================================\n * TIMELINE ROW\n * ========================================================================== */\n\ninterface TimelineRowProps {\n  row: TimelineRowData\n  slots: TimelineSlotData[]\n  children: (slot: TimelineSlotData) => React.ReactNode\n  renderRowHeader?: (row: TimelineRowData) => React.ReactNode\n  renderDropGhost?: (slot: TimelineSlotData, newTime: string, isValid: boolean) => React.ReactNode\n  className?: string\n  asChild?: boolean\n}\n\nexport function TimelineRow({\n  row,\n  slots,\n  children,\n  renderRowHeader,\n  className,\n  asChild,\n  ...props\n}: TimelineRowProps & any) {\n  const { config, pixelsPerMinute, timelineWidth } = useTimeline()\n  const Comp = asChild ? Slot : \"div\"\n\n  const { setNodeRef, isOver } = useDroppable({\n    id: row.id,\n  })\n\n  const rowSlots = slots.filter((s: TimelineSlotData) => s.rowId === row.id)\n\n  // Check if this row has invalid drop (from props passed by Timeline)\n  const isValidDrop = props.isValidDrop !== false\n  const isHovered = isOver || props.overRowId === row.id // Try BOTH\n\n  // Generate grid markers\n  const hourMarkers = []\n  for (let hour = config.startHour; hour <= config.endHour; hour++) {\n    hourMarkers.push({\n      hour,\n      position: (hour - config.startHour) * 60 * pixelsPerMinute,\n    })\n  }\n\n  const quarterHourMarkers = []\n  const totalMinutes = (config.endHour - config.startHour) * 60\n  for (let minutes = 15; minutes < totalMinutes; minutes += 15) {\n    if (minutes % 60 !== 0) {\n      quarterHourMarkers.push({\n        position: minutes * pixelsPerMinute,\n      })\n    }\n  }\n\n  return (\n    <Comp\n      ref={setNodeRef}\n      data-slot=\"timeline-row\"\n      data-state={isHovered ? (isValidDrop ? \"hover-valid\" : \"hover-invalid\") : \"idle\"}\n      className={cn(\n        \"flex border-b h-12\",\n        isHovered && isValidDrop && \"ring-2 ring-inset ring-blue-500\",\n        isHovered && !isValidDrop && \"ring-2 ring-inset ring-red-500\",\n        className,\n      )}\n    >\n      {/* Row label column */}\n      <div\n        data-slot=\"timeline-row-label\"\n        className=\"sticky left-0 w-[var(--timeline-column-width)] bg-inherit border-r flex items-center px-0 z-[5]\"\n      >\n        {renderRowHeader ? renderRowHeader(row) : row.label}\n      </div>\n\n      {/* Timeline grid */}\n      <div data-slot=\"timeline-row-grid\" className=\"relative flex-1\" style={{ width: `${timelineWidth}px` }}>\n        {/* Grid lines */}\n        {quarterHourMarkers.map((marker, idx) => (\n          <div\n            key={`quarter-${idx}`}\n            data-slot=\"timeline-grid-line-quarter\"\n            className=\"absolute top-0 bottom-0 w-px bg-border/30\"\n            style={{ left: `${marker.position}px` }}\n          />\n        ))}\n        {hourMarkers.map(marker => (\n          <div\n            key={marker.hour}\n            data-slot=\"timeline-grid-line-hour\"\n            className=\"absolute top-0 bottom-0 w-px bg-border\"\n            style={{ left: `${marker.position}px` }}\n          />\n        ))}\n\n        {/* Slots */}\n        {rowSlots.map((slot: TimelineSlotData) => {\n          const slotElement = children(slot)\n          // Clone the element to inject activeSlotId prop\n          return (\n            <React.Fragment key={slot.id}>\n              {React.isValidElement(slotElement)\n                ? React.cloneElement(slotElement, { activeSlotId: props.activeSlotId } as any)\n                : slotElement}\n            </React.Fragment>\n          )\n        })}\n\n        {/* Drop ghost - shows where slot will land if dropped in this row */}\n        {isHovered && props.draggedNewTime && props.activeSlotId && (\n          <TimelineDropGhost\n            activeSlotId={props.activeSlotId}\n            allSlots={slots}\n            newTime={props.draggedNewTime}\n            isValid={isValidDrop}\n            pixelsPerMinute={pixelsPerMinute}\n            config={config}\n          />\n        )}\n      </div>\n    </Comp>\n  )\n}\n\n/* ============================================================================\n * TIMELINE SLOT (Draggable)\n * ========================================================================== */\n\ninterface TimelineSlotProps {\n  slot: TimelineSlotData\n  children: React.ReactNode\n  className?: string\n  asChild?: boolean\n}\n\nexport function TimelineSlot({ slot, children, className, asChild, ...props }: TimelineSlotProps & any) {\n  const timeline = useTimeline()\n  const { config, pixelsPerMinute, onSlotClick, dragPreviewTunnel } = timeline\n  const Comp = asChild ? Slot : \"div\"\n\n  const { attributes, listeners, setNodeRef, isDragging, transform } = useDraggable({\n    id: slot.id,\n  })\n\n  const startMinutes = timeToMinutes(slot.startTime)\n  const left = (startMinutes - config.startHour * 60) * pixelsPerMinute\n  const width = slot.duration * pixelsPerMinute\n\n  // Get activeSlotId from Timeline component via props (passed through TimelineRow)\n  const activeSlotIdFromTimeline = props.activeSlotId\n  const isActiveSlot = activeSlotIdFromTimeline === slot.id\n\n  // When dragging, show slot at its snapped target position (not at cursor)\n  // The DragOverlay will show what you're dragging at cursor position\n  const style =\n    transform && props.getSnappedDelta\n      ? {\n          left: `${left}px`,\n          width: `${Math.max(width, 60)}px`,\n          top: 0,\n          bottom: 0,\n          transform: `translate3d(${props.getSnappedDelta(transform.x)}px, ${transform.y}px, 0)`,\n        }\n      : {\n          left: `${left}px`,\n          width: `${Math.max(width, 60)}px`,\n          top: 0,\n          bottom: 0,\n        }\n\n  const slotContent = (\n    <Comp\n      data-slot=\"timeline-slot\"\n      data-state={isDragging ? \"dragging\" : \"idle\"}\n      data-active={isActiveSlot}\n      className={cn(\n        \"absolute inset-1 rounded cursor-move transition-all overflow-hidden\",\n        isDragging ? \"opacity-40 shadow-sm ring-2 ring-foreground/50\" : \"shadow-md\",\n        onSlotClick && \"cursor-pointer hover:ring-2 hover:ring-foreground/30\",\n        className,\n      )}\n      onClick={(e: React.MouseEvent) => {\n        if (onSlotClick && !isDragging) {\n          e.stopPropagation()\n          onSlotClick(slot.id)\n        }\n      }}\n      style={\n        {\n          \"--slot-start-time\": slot.startTime,\n          \"--slot-duration\": `${slot.duration}min`,\n        } as React.CSSProperties\n      }\n    >\n      {children}\n    </Comp>\n  )\n\n  return (\n    <div ref={setNodeRef} {...listeners} {...attributes} className=\"absolute\" style={style}>\n      {/* Always render slot content normally */}\n      {slotContent}\n\n      {/* When active and dragging, ALSO send content through tunnel for DragOverlay */}\n      {isActiveSlot && isDragging && (\n        <>\n          {console.log(\"🚇 Sending content through tunnel for slot:\", slot.id)}\n          <dragPreviewTunnel.In>\n            <Comp\n              data-slot=\"timeline-slot-preview\"\n              className={cn(\n                \"rounded cursor-move overflow-hidden shadow-lg\",\n                \"h-full w-full\", // Ensure it fills container\n                className,\n              )}\n              style={\n                {\n                  \"--slot-start-time\": slot.startTime,\n                  \"--slot-duration\": `${slot.duration}min`,\n                } as React.CSSProperties\n              }\n            >\n              {children}\n            </Comp>\n          </dragPreviewTunnel.In>\n        </>\n      )}\n    </div>\n  )\n}\n\n/* ============================================================================\n * TIMELINE SLOT PRIMITIVES (for content composition)\n * ========================================================================== */\n\ninterface TimelineSlotLabelProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean\n}\n\nexport function TimelineSlotLabel({ asChild, className, ...props }: TimelineSlotLabelProps) {\n  const Comp = asChild ? Slot : \"div\"\n  return <Comp data-slot=\"timeline-slot-label\" className={cn(\"font-medium truncate text-xs\", className)} {...props} />\n}\n\ninterface TimelineSlotContentProps extends React.ComponentProps<\"div\"> {\n  asChild?: boolean\n}\n\nexport function TimelineSlotContent({ asChild, className, ...props }: TimelineSlotContentProps) {\n  const Comp = asChild ? Slot : \"div\"\n  return <Comp data-slot=\"timeline-slot-content\" className={cn(\"text-xs\", className)} {...props} />\n}\n\n/* ============================================================================\n * TIMELINE INDICATORS\n * ========================================================================== */\n\nfunction TimelineMouseIndicator({ mouseX, time }: { mouseX: number; time: string }) {\n  return (\n    <div\n      data-slot=\"timeline-mouse-indicator\"\n      className=\"absolute top-0 bottom-0 pointer-events-none z-20\"\n      style={{ left: `${mouseX}px` }}\n    >\n      <div className=\"absolute top-0 bottom-0 w-px bg-accent left-0\" />\n      <div className=\"absolute top-0 left-1/2 -translate-x-1/2 bg-accent text-accent-foreground px-2 py-1 rounded text-xs font-semibold whitespace-nowrap shadow-md\">\n        {time}\n      </div>\n    </div>\n  )\n}\n\ninterface TimelineDropRegionProps {\n  startTime: string\n  duration: number\n}\n\nexport function TimelineDropRegion({ startTime, duration }: TimelineDropRegionProps) {\n  const { config, pixelsPerMinute } = useTimeline()\n  const columnWidth = config.columnWidth || 112\n\n  const startMinutes = timeToMinutes(startTime)\n  const endMinutes = startMinutes + duration\n  const endTime = minutesToTime(endMinutes)\n\n  const startPosition = (startMinutes - config.startHour * 60) * pixelsPerMinute + columnWidth\n  const endPosition = (endMinutes - config.startHour * 60) * pixelsPerMinute + columnWidth\n  const width = endPosition - startPosition\n\n  return (\n    <div\n      data-slot=\"timeline-drop-region\"\n      className=\"absolute top-0 bottom-0 pointer-events-none z-[12]\"\n      style={{ left: `${startPosition}px`, width: `${width}px` }}\n    >\n      <div className=\"absolute top-2 left-1/2 -translate-x-1/2 bg-accent text-accent-foreground px-3 py-1.5 rounded text-sm font-semibold whitespace-nowrap shadow-md\">\n        {startTime} - {endTime}\n      </div>\n      <div className=\"absolute top-0 bottom-0 left-0 w-0.5 bg-accent\" />\n      <div className=\"absolute top-0 bottom-0 right-0 w-0.5 bg-accent\" />\n      <div className=\"absolute inset-0 bg-accent/[0.07]\" />\n    </div>\n  )\n}\n\ninterface TimelineCurrentTimeProps {\n  className?: string\n  nowLabel?: string\n}\n\nexport function TimelineCurrentTime({ className, nowLabel = \"Now\" }: TimelineCurrentTimeProps) {\n  const { config, pixelsPerMinute } = useTimeline()\n  const columnWidth = config.columnWidth || 112\n\n  const [now, setNow] = useState(new Date())\n\n  useEffect(() => {\n    const interval = setInterval(() => setNow(new Date()), 60000) // Update every minute\n    return () => clearInterval(interval)\n  }, [])\n\n  const currentMinutes = now.getHours() * 60 + now.getMinutes()\n  const position = (currentMinutes - config.startHour * 60) * pixelsPerMinute + columnWidth\n\n  // Only show if within timeline range\n  if (currentMinutes < config.startHour * 60 || currentMinutes > config.endHour * 60) {\n    return null\n  }\n\n  return (\n    <div\n      data-slot=\"timeline-current-time\"\n      className={cn(\"absolute top-0 bottom-0 w-0.5 bg-secondary pointer-events-none z-[15]\", className)}\n      style={{ left: `${position}px` }}\n    >\n      <div className=\"absolute top-0 left-1/2 -translate-x-1/2 bg-secondary text-foreground px-2 py-1 rounded text-xs font-medium whitespace-nowrap shadow-md z-50\">\n        {nowLabel}: {minutesToTime(currentMinutes)}\n      </div>\n    </div>\n  )\n}\n\n/* ============================================================================\n * TIMELINE GRID (for custom layouts)\n * ========================================================================== */\n\ninterface TimelineGridProps {\n  children: React.ReactNode\n  className?: string\n}\n\nexport function TimelineGrid({ children, className, ...props }: TimelineGridProps & any) {\n  const { timelineWidth } = useTimeline()\n\n  // Get active slot data for drop region and ghost preview\n  const activeSlot =\n    props._showDropRegion && props.slots ? props.slots.find((s: TimelineSlotData) => s.id === props.activeSlotId) : null\n\n  return (\n    <div\n      data-slot=\"timeline-grid-container\"\n      className={cn(\"relative\", className)}\n      style={{ minWidth: `${timelineWidth + 200}px` }}\n    >\n      {React.Children.map(children, child => {\n        if (React.isValidElement(child)) {\n          return React.cloneElement(child as React.ReactElement<any>, props)\n        }\n        return child\n      })}\n\n      {/* Drop region highlight */}\n      {props._showDropRegion && props._dropRegionTime && activeSlot && (\n        <TimelineDropRegion startTime={props._dropRegionTime} duration={activeSlot.duration} />\n      )}\n    </div>\n  )\n}\n\n/* ============================================================================\n * DROP GHOST (Shows where slot will land)\n * ========================================================================== */\n\nfunction TimelineDropGhost({\n  activeSlotId,\n  allSlots,\n  newTime,\n  isValid,\n  config,\n  pixelsPerMinute,\n}: {\n  activeSlotId: string\n  allSlots: TimelineSlotData[]\n  newTime: string\n  isValid: boolean\n  config: TimelineConfig\n  pixelsPerMinute: number\n}) {\n  const slot = allSlots.find(s => s.id === activeSlotId)\n  if (!slot) return null\n\n  const startMinutes = timeToMinutes(newTime)\n  const left = (startMinutes - config.startHour * 60) * pixelsPerMinute\n  const width = slot.duration * pixelsPerMinute\n\n  if (!isValid) return null\n\n  return (\n    <div\n      data-slot=\"timeline-drop-ghost\"\n      className={cn(\"absolute rounded-md pointer-events-none\", \"bg-foreground/20\")}\n      style={{\n        left: `${left}px`,\n        width: `${Math.max(width, 60)}px`,\n        top: \"2px\",\n        bottom: \"2px\",\n        zIndex: 100,\n      }}\n    />\n  )\n}\n\n/* ============================================================================\n * EXPORTS\n * ========================================================================== */\n\nexport { useTimeline, TimelineMouseIndicator }\n",
      "type": "registry:component"
    }
  ]
}
