---
title: "Custom Calendar Grid"
slug: "custom-calendar-grid"
category: "Widgets"
reactComponent: "ArcWidgetCalendarGrid"
reactTier: "premium"
reactSince: "0.1.0-alpha.5"
reactExample: "calendar_grid.tsx"
locale: "es"
hosts: "react"
---

# CalendarGrid en React

Muestra citas en una vista mensual y selecciona días o entradas.

Referencia para `@arcrider/arcwidgets-react@0.1.0-alpha.5`. [Instalación](/docs/react/intro/setup-react) · [Todos los componentes](/docs/react/intro/react-components).

## Ejemplo completo

```tsx
import { ArcWidgetCalendarGrid } from '@arcrider/arcwidgets-react';
import { useState } from 'react';

const lang = document.documentElement.lang.startsWith('en')
  ? 'en'
  : document.documentElement.lang.startsWith('es')
    ? 'es'
    : 'de';
const requested = new URLSearchParams(location.search).get('data');
const pack = requested === 'empty' || requested === 'busy' ? requested : 'demo';
const t = (de: string, en: string, es: string) =>
  lang === 'en' ? en : lang === 'es' ? es : de;
const locale = lang === 'en' ? 'en-US' : lang === 'es' ? 'es-ES' : 'de-DE';

const chrome = {
  backgroundColor: '#ffffff',
  fontColor: '#18181b',
  mutedColor: '#52525b',
  subtleColor: '#71717a',
  borders: { color: '#e4e4e7', outer: true, columns: true, rows: true },
};

const look = (backgroundColor: string, color: string) => ({
  backgroundColor,
  color,
  fontColor: color,
});

type Entry = {
  id: string;
  title: string;
  dateFrom: number;
  dateTo: number;
  timeFrom: number;
  timeTo: number;
  clickable: true;
  draggable: true;
  styles: ReturnType<typeof look>;
};

const event = (
  id: string,
  title: string,
  day: number,
  backgroundColor: string,
  color: string,
): Entry => ({
  id,
  title,
  dateFrom: new Date(2026, 8, day).getTime(),
  dateTo: new Date(2026, 8, day).getTime(),
  timeFrom: 9 * 3600000,
  timeTo: 11 * 3600000,
  clickable: true,
  draggable: true,
  styles: look(backgroundColor, color),
});

const demoEntries: Entry[] = [
  event(
    'travel-out',
    t('Anfahrt', 'Travel there', 'Ida'),
    16,
    '#99f6e4',
    '#115e59',
  ),
  event(
    'visit',
    t('Hauptarbeit', 'On site', 'Trabajo principal'),
    17,
    '#ccfbf1',
    '#115e59',
  ),
  event(
    'travel-back',
    t('Abfahrt', 'Travel back', 'Vuelta'),
    18,
    '#99f6e4',
    '#115e59',
  ),
  event(
    'review',
    t('Projekt-Review', 'Project review', 'Revisión'),
    21,
    '#ede9fe',
    '#5b21b6',
  ),
  event(
    'ship',
    t('Lieferung', 'Delivery', 'Entrega'),
    24,
    '#fef3c7',
    '#92400e',
  ),
];

const busyEntries: Entry[] = [
  ...demoEntries,
  event('kickoff', t('Kickoff', 'Kickoff', 'Kickoff'), 3, '#d1fae5', '#065f46'),
  event('fair', t('Messe', 'Fair', 'Feria'), 8, '#ffe4e6', '#9f1239'),
  event('retro', t('Retro', 'Retro', 'Retro'), 28, '#e0f2fe', '#075985'),
];

export default function Example() {
  const [month, setMonth] = useState(9);
  const [entries, setEntries] = useState(
    pack === 'empty' ? [] : pack === 'busy' ? busyEntries : demoEntries,
  );
  const [selected, setSelected] = useState(
    t(
      'Termin ziehen oder anklicken',
      'Drag or click an event',
      'Arrastra o pulsa un evento',
    ),
  );
  return (
    <>
      <button onClick={() => setMonth((m) => (m === 1 ? 12 : m - 1))}>
        {t('Vorheriger Monat', 'Previous month', 'Mes anterior')}
      </button>
      <button onClick={() => setMonth((m) => (m === 12 ? 1 : m + 1))}>
        {t('Nächster Monat', 'Next month', 'Mes siguiente')}
      </button>
      <ArcWidgetCalendarGrid
        id="month"
        data={{
          timeZoneBalance: 0,
          height: '480px',
          lang,
          styles: chrome,
          dateSettings: {
            month,
            year: 2026,
            day: { clickable: true },
          },
          timeEntries: entries,
        }}
        onDayClick={(date) =>
          setSelected(new Date(date).toLocaleDateString(locale))
        }
        onEntryClick={(index) => setSelected(entries[index]?.title ?? selected)}
        onEntryDragSave={(index, start) => {
          setEntries((previous) =>
            previous.map((entry, i) =>
              i === index
                ? {
                    ...entry,
                    dateFrom: Number(start.dateFrom ?? entry.dateFrom),
                    dateTo: Number(start.dateTo ?? entry.dateTo),
                  }
                : entry,
            ),
          );
          setSelected(t('Termin verschoben', 'Event moved', 'Evento movido'));
        }}
      />
      <p aria-live="polite">{selected}</p>
    </>
  );
}
```

## Props del componente

### id

ID opcional y estable de la instancia del widget. Sin especificarlo, React genera un ID.

### data

Configuración de tipo `ArcWidgetCalendarGridData`. Pasa los cambios como objetos nuevos; gestiona los datos de negocio y la selección en el estado de React.

### onAction

Fallback opcional `(type: string, ...args: unknown[]) => unknown`. El callback concreto tiene prioridad. Detalles: [Estado y eventos](/docs/react/patterns/react-state-events).

### onEntryClick

Índice de la cita, y en Timeline también la fila del recurso.

```typescript
onEntryClick?: (entry_index: number) => void;
```

### onDayClick

Fecha del día seleccionado como marca de tiempo en milisegundos.

```typescript
onDayClick?: (date_millis: number) => void;
```

### onEntryDragSave

Fin del arrastre con los datos originales. No hay horas de destino completas nuevas; consulta las notas sobre persistencia.

```typescript
onEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void;
```

### onDailyEntryDragSave

Fin del arrastre de una cita de todo el día con los datos originales.

```typescript
onDailyEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void;
```

## Configuración

Los siguientes campos pertenecen a `data`. Los tamaños se indican como strings, los números y valores de tiempo según su tipo respectivo.

### height

Altura del widget, p. ej. `"320px"`. Los valores porcentuales necesitan un contenedor padre con altura definida.

Tipo: `string`.

### timeZoneBalance

Corrección en horas; los ejemplos usan `0` y valores de día generados localmente.

Tipo: `number | string`.

### styles

Colores, espaciados y bordes comunes; los campos ofrecidos son específicos del widget.

Tipo: `ArcWidgetCalendarGridStyles | string`.

### dateSettings

Rango de fechas, cabeceras y marcados.

El tipo y los campos anidados están en `ArcWidgetCalendarGridData`, bajo tipos de datos.

### timeSettings

Rango horario y cuadrícula; las unidades son milisegundos.

Tipo: `Record<string, unknown>`.

### lang

Idioma de las etiquetas del calendario.

Tipo: `string`.

### timeEntries

Citas con valores de fecha, horas y contenidos.

El tipo y los campos anidados están en `ArcWidgetCalendarGridData`, bajo tipos de datos.

## Comportamiento y límites

- Para JSX en citas usa `customLayout`. Los campos estándar `title`, `subtitle` y `value` se procesan como texto o HTML.

- Los valores de fecha son marcas de tiempo en milisegundos; las horas son milisegundos desde el inicio del día. Consulta [Fecha y hora](/docs/react/patterns/react-dates).

- `clickable: true` activa la selección. Un índice de callback no es un ID de registro; resuélvelo mediante tu lista actual de entradas.

- Los callbacks de arrastre actuales contienen el estado original (`start_data`), pero no horas de destino completas nuevas. Por eso el ejemplo establece `draggable: false`. Edita las citas mediante el estado de React hasta que exista un contrato de persistencia completo para arrastrar y soltar.

- `dateSettings.month` empieza en uno: enero es `1`. Los marcados están en `dateSettings.highlights`.

Los campos `ArcWidgetSlot` admitidos permiten elementos JSX individuales, texto o descriptores de widget. Pasa texto externo como texto JSX; los strings HTML se interpretan como HTML. [Combinar contenidos](/docs/react/patterns/nested-widget-descriptors).

## Tipos de datos

Estos tipos relacionados con el widget se exportan desde el paquete. También están disponibles tipos comunes como `ArcWidgetSlot`, `ArcWidgetStyles` y `ArcReactWidgetProps`. Las firmas de índice permiten extensiones; no garantizan que el widget evalúe campos arbitrarios.

```typescript
export interface ArcWidgetCalendarGridDay {
	clickable?: boolean;
	height?: string;
	alignX?: string;
	backgroundColor?: string;
	hoverBackgroundColor?: string;
	padding?: string;
	[key: string]: unknown;
}

export interface ArcWidgetCalendarGridDragStart {
	dateFrom?: unknown;
	dateTo?: unknown;
	timeFrom?: unknown;
	timeTo?: unknown;
}

export type ArcWidgetCalendarGridBorderLine = boolean | {
	color?: string;
	width?: string;
	borderColor?: string;
	borderWidth?: string;
	borderSize?: string;
	enabled?: boolean;
};

export interface ArcWidgetCalendarGridBorders {
	color?: string;
	width?: string;
	outer?: ArcWidgetCalendarGridBorderLine;
	columns?: ArcWidgetCalendarGridBorderLine;
	rows?: ArcWidgetCalendarGridBorderLine;
}

export interface ArcWidgetCalendarGridStyles extends ArcWidgetStyles {
	borders?: ArcWidgetCalendarGridBorders;
	headerBackgroundColor?: string;
	mutedColor?: string;
	subtleColor?: string;
}

export interface ArcWidgetCalendarGridData extends Omit<ArcWidgetBaseData, 'actions'> {
	height?: string;
	timeZoneBalance?: number | string;
	styles?: ArcWidgetCalendarGridStyles | string;
	dateSettings?: {
		month?: number;
		year?: number;
		day?: ArcWidgetCalendarGridDay;
		highlights?: Array<{ date?: unknown; color?: string; [key: string]: unknown }>;
		[key: string]: unknown;
	};
	timeSettings?: Record<string, unknown>;
	lang?: string;
	timeEntries?: Array<{
		id?: string | number;
		title?: string;
		subtitle?: string;
		value?: string;
		customLayout?: ArcWidgetSlot;
		dateFrom?: unknown;
		dateTo?: unknown;
		timeFrom?: unknown;
		timeTo?: unknown;
		styles?: ArcWidgetStyles | string;
		clickable?: boolean;
		draggable?: boolean;
		[key: string]: unknown;
	}>;
}

export interface ArcWidgetCalendarGridProps extends ArcReactWidgetProps<ArcWidgetCalendarGridData> {
	onEntryClick?: (entry_index: number) => void;
	onDayClick?: (date_millis: number) => void;
	onEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void;
	onDailyEntryDragSave?: (entry_index: number, start_data: ArcWidgetCalendarGridDragStart) => void;
}
```
