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

dynamic-schedule

v1.1.0

Published

A dynamic schedule component library for React with drag & drop functionality

Readme

Dynamic Schedule

Una biblioteca de componentes React para crear horarios dinámicos con funcionalidad drag & drop.

Características

  • Soporte para tipos genéricos para modelos de datos flexibles
  • Funcionalidad drag & drop con @dnd-kit
  • Estilos incluidos automáticamente (sin configuración adicional)
  • Compatible con Tailwind CSS y otros frameworks de estilos
  • Indicadores de scroll con soporte de auto-scroll
  • Soporte de multi-selección
  • Atajos de teclado (Ctrl para multi-selección, Escape para limpiar)
  • Soporte completo de TypeScript con definiciones de tipos

Instalación

npm install dynamic-schedule
# o
pnpm add dynamic-schedule
# o
yarn add dynamic-schedule

Dependencias Peer

Este paquete requiere que instales las siguientes dependencias en tu proyecto:

npm install react react-dom @dnd-kit/core @dnd-kit/modifiers zustand lucide-react

O con pnpm:

pnpm add react react-dom @dnd-kit/core @dnd-kit/modifiers zustand lucide-react

Versiones Compatibles

  • react: ^18.0.0 || ^19.0.0
  • react-dom: ^18.0.0 || ^19.0.0
  • @dnd-kit/core: ^6.0.0
  • @dnd-kit/modifiers: ^7.0.0 || ^8.0.0 || ^9.0.0
  • zustand: ^4.0.0 || ^5.0.0
  • lucide-react: ^0.400.0

Estilos

Los estilos están incluidos automáticamente

Este paquete incluye sus propios estilos CSS que se inyectan automáticamente cuando importas los componentes. No necesitas importar ningún archivo CSS adicional ni configurar nada especial.

// Solo importa y usa - los estilos ya están incluidos
import { DynamicSchedule } from 'dynamic-schedule'

Compatible con Tailwind CSS

El paquete es totalmente compatible con proyectos que usan Tailwind CSS u otros frameworks de estilos. Los estilos internos del paquete están aislados con prefijos ds- (dynamic-schedule) para evitar conflictos.

Puedes usar Tailwind CSS en tu proyecto sin problemas, como en las props de personalización:

<DynamicSchedule
  headerClassName="bg-gray-200"        // Usa tus clases de Tailwind
  containerClassName="border shadow-lg"
  // ... otras props
/>

Uso Básico

import { useState } from 'react'
import { DynamicSchedule } from 'dynamic-schedule'
import type { Column, Row, Item, DynamicScheduleOnChangeCallback } from 'dynamic-schedule'

interface MyData {
  name: string
  description: string
}

function App() {
  const columns: Column[] = [
    { id: '1', label: 'Lunes' },
    { id: '2', label: 'Martes' },
    { id: '3', label: 'Miércoles' },
  ]

  const rows: Row[] = [
    { id: '0', label: '08:00' },
    { id: '1', label: '09:00' },
    { id: '2', label: '10:00' },
    { id: '3', label: '11:00' },
  ]

  const [items, setItems] = useState<Item<MyData>[]>([
    {
      id: '1',
      columnId: '1',
      rowStart: 1,    // Índice de fila inicial
      rowSpan: 2,     // Cantidad de filas que ocupa
      original: {
        name: 'Reunión de equipo',
        description: 'Standup diario'
      }
    },
    // ... más items
  ])

  const handleChange: DynamicScheduleOnChangeCallback<MyData> = async ({ items }) => {
    // Actualiza los items después del drag & drop
    setItems((prevItems) => {
      const newItems = [...prevItems]
      items.forEach(({ newScheduleItem }) => {
        const index = newItems.findIndex((i) => i.id === newScheduleItem.id)
        if (index !== -1) {
          newItems[index] = newScheduleItem
        }
      })
      return newItems
    })
  }

  return (
    <DynamicSchedule
      columns={columns}
      rows={rows}
      items={items}
      firstColumnWidth={64}
      headerHeight={40}
      rowHeight={100}
      minColumnWidth={300}
      firstColumnText="Horarios"
      onChange={handleChange}
      ScheduleItemComponent={({ original }) => (
        <div>
          <h3>{original.name}</h3>
          <p>{original.description}</p>
        </div>
      )}
      headerClassName="bg-gray-200"
      firstColumnClassName="bg-gray-100"
    />
  )
}

Props del Componente

DynamicScheduleProps

Props Requeridas

| Prop | Tipo | Descripción | |------|------|-------------| | columns | Column[] | Array de columnas del schedule | | rows | Row[] | Array de filas del schedule | | items | Item<T>[] | Array de items a mostrar | | ScheduleItemComponent | Component<ScheduleItemComponentProps<T>> | Componente para renderizar cada item | | firstColumnWidth | number | Ancho en pixels de la primera columna (columna fija) | | headerHeight | number | Altura en pixels del header | | rowHeight | number | Altura en pixels de cada fila | | minColumnWidth | number | Ancho mínimo en pixels de las columnas dinámicas |

Props Opcionales

| Prop | Tipo | Default | Descripción | |------|------|---------|-------------| | onChange | DynamicScheduleOnChangeCallback<T> | null | Callback cuando los items cambian (drag & drop). Si no se proporciona, el componente funcionará en modo solo lectura | | VoidItemComponent | Component<ScheduleVoidItemComponentProps> | - | Componente para celdas vacías (clickeables para crear nuevos items) | | scrollIndicator | ScrollIndicator | null | Configuración del indicador de scroll automático | | firstColumnText | string | - | Texto a mostrar en la celda del header de la primera columna | | getItemCanDragOnX | (itemId: string) => boolean | - | Función para determinar si un item puede moverse horizontalmente (entre columnas) | | getItemCanBeSelected | (itemId: string) => boolean | - | Función para determinar si un item puede ser seleccionado en modo multi-selección | | headerClassName | string | - | Clase CSS personalizada para el header | | containerClassName | string | - | Clase CSS personalizada para el contenedor principal | | containerStyle | React.CSSProperties | - | Estilos inline para el contenedor principal | | firstColumnClassName | string | - | Clase CSS personalizada para la primera columna (columna fija) | | currentLineClassName | string | - | Clase CSS personalizada para la línea de tiempo actual |

Tipos

Column

interface Column {
  id: string
  label: string
}

Row

interface Row {
  id: string
  label: string
}

Item

interface Item<T> {
  id: string
  columnId: string  // ID de la columna donde está el item
  rowStart: number  // Índice de la fila inicial (0-based)
  rowSpan: number   // Cantidad de filas que ocupa el item
  original: T       // Tus datos personalizados
}

ScrollIndicator

interface ScrollIndicator {
  quantity: number    // Cantidad de pixels a scrollear
  autoScroll: boolean // Si debe hacer auto-scroll al arrastrar cerca de los bordes
}

DynamicScheduleOnChangeCallback

type DynamicScheduleOnChangeCallback<T> = (
  input: DynamicScheduleOnChangeCallbackInput<T>
) => Promise<void>

interface DynamicScheduleOnChangeCallbackInput<T> {
  items: {
    newScheduleItem: Item<T>
    newColumnId: string
    newRowId: string
  }[]
}

ScheduleItemComponentProps

interface ScheduleItemComponentProps<T> {
  original: T  // Tus datos personalizados del item
  draggableProps?: {
    attributes: DraggableAttributes
    listeners: SyntheticListenerMap | undefined
  }
}

ScheduleVoidItemComponentProps

interface ScheduleVoidItemComponentProps {
  row: Row      // Fila de la celda vacía
  column: Column // Columna de la celda vacía
}

Características Avanzadas

Multi-selección

Mantén presionado Ctrl (o Cmd en Mac) mientras haces clic en los items para seleccionar múltiples items. Los items seleccionados se pueden arrastrar juntos.

Presiona Escape para limpiar la selección.

Indicadores de Scroll

Configura indicadores de scroll para desplazarse automáticamente cuando arrastres items cerca de los bordes:

<DynamicSchedule
  // ... otras props
  scrollIndicator={{
    quantity: 50,      // pixels a desplazar
    autoScroll: true,  // activar auto-scroll
  }}
/>

Celdas Vacías Personalizadas

Proporciona un componente personalizado para las celdas vacías (útil para permitir crear nuevos items):

const VoidCell = ({ row, column }: ScheduleVoidItemComponentProps) => (
  <div onClick={() => createNewItem(column.id, row.id)}>
    <p>Click para crear evento</p>
    <small>{row.label} - {column.label}</small>
  </div>
)

<DynamicSchedule
  // ... otras props
  VoidItemComponent={VoidCell}
/>

Controlar Movimiento Horizontal

Puedes controlar si un item puede moverse horizontalmente (entre columnas):

<DynamicSchedule
  // ... otras props
  getItemCanDragOnX={(itemId) => {
    // Por ejemplo, solo permitir mover ciertos items
    const item = items.find(i => i.id === itemId)
    return item?.original.movable === true
  }}
/>

Controlar Selección de Items

Puedes controlar qué items pueden ser seleccionados cuando se presiona Ctrl:

<DynamicSchedule
  // ... otras props
  getItemCanBeSelected={(itemId) => {
    // Por ejemplo, solo permitir seleccionar items pares
    const itemIdNumber = parseInt(itemId, 10)
    return itemIdNumber % 2 === 0
  }}
/>

Hooks Exportados

El paquete exporta hooks útiles para casos de uso avanzados:

import {
  useItemsByColumn,
  useCoincidencesByColumn,
  useDynamicScheduleStore,
  useDynamicScheduleScrollIndicatorStore,
  useDynamicScheduleSelectedItemsStore,
  useDynamicScheduleMovementStore,
} from 'dynamic-schedule'

Utilidades Exportadas

import {
  getCoincidences,
  // funciones de cálculo
  // constantes
} from 'dynamic-schedule'

Solución de Problemas

Error: Cannot find module '@dnd-kit/core'

Causa: Faltan peer dependencies.

Solución: Instala todas las peer dependencies:

npm install @dnd-kit/core @dnd-kit/modifiers zustand lucide-react

Los componentes no se ven correctamente

Causa: Es posible que falte alguna dependencia peer o haya un conflicto de estilos.

Solución:

  1. Verifica que todas las peer dependencies estén instaladas
  2. Si usas un bundler personalizado, asegúrate de que procese correctamente los módulos CSS
  3. Revisa que no haya estilos globales que sobrescriban los estilos del paquete

TypeScript

Esta biblioteca está escrita en TypeScript y proporciona definiciones de tipos completas. El componente principal es genérico y puede trabajar con cualquier tipo de datos:

interface MisDatosPersonalizados {
  titulo: string
  descripcion: string
  color: string
  // ... tus propiedades
}

const items: Item<MisDatosPersonalizados>[] = [
  {
    id: '1',
    columnId: '1',
    rowStart: 0,
    rowSpan: 2,
    original: {
      titulo: 'Mi evento',
      descripcion: 'Descripción del evento',
      color: '#3b82f6'
    }
  }
]

<DynamicSchedule<MisDatosPersonalizados>
  columns={columns}
  rows={rows}
  items={items}
  firstColumnWidth={64}
  headerHeight={40}
  rowHeight={100}
  minColumnWidth={300}
  ScheduleItemComponent={({ original }) => (
    <div style={{ backgroundColor: original.color }}>
      <h3>{original.titulo}</h3>
      <p>{original.descripcion}</p>
    </div>
  )}
/>

Licencia

MIT

Autor

Santiago Reynoso (@ssreynoso) https://github.com/ssreynoso

Contribuir

Las contribuciones son bienvenidas! Por favor, siéntete libre de enviar un Pull Request.