Examples
Case 6: Calendar/Scheduler Interface

๐Ÿ—“๏ธ Calendar/Scheduler Interface

This demo showcases a calendar/scheduler UI built with GridSheet.

It demonstrates custom date cell rendering with event management.

Users can input events in "HH:MM Task" format, separated by newlines.

Implementation Guide

๐Ÿ“„ View Source Code

๐ŸŽจ renderCallback for Stable Layout

The outer cell layout (padding, flexbox container, date header) is defined in renderCallback, which wraps any rendered result regardless of the cell's value type. This prevents layout inconsistency when the cell holds different types of values (array, null, string, etc.).

renderCallback(rendered, { point }) {
  const { date } = getDateFromPosition(point.y, point.x);
  return (
    <div style={{ width: '100%', height: '100%', padding: 8, display: 'flex', flexDirection: 'column' }}>
      <div style={{ fontWeight: 700, fontSize: 14 }}>{date}</div>
      <div style={{ flex: 1, overflow: 'hidden' }}>{rendered}</div>
    </div>
  );
},

๐Ÿ“‹ renderArray for Task List Only

renderArray focuses solely on rendering the list of event items. It does not need to handle layout or the empty state โ€” those are delegated to renderCallback and renderNull respectively.

renderArray({ value }) {
  const events = Array.isArray(value) ? value : [];
  return (
    <>
      {events.map((event, i) => (
        <div key={i} style={{ background: '#3498db', color: '#fff', borderRadius: 3, padding: '2px 4px' }}>
          {event.time && <span>{event.time}</span>}
          <span>{event.task}</span>
        </div>
      ))}
    </>
  );
},

๐Ÿšซ renderNull for Empty State

When a cell has no events, its value is null. renderNull handles this case and renders the "No events" placeholder. This keeps the empty state display separate from the array rendering logic.

renderNull() {
  return (
    <span style={{ color: '#aaa', fontSize: 11, fontStyle: 'italic', textAlign: 'center' }}>
      No events
    </span>
  );
},

๐Ÿ’พ deserializeFirst โ€” Empty String โ†’ null

When a user clears a cell, the value becomes an empty string. deserializeFirst converts that to null so that renderNull (not renderArray) is invoked.

deserializeFirst: (value: string) => {
  if (!value || !value.trim()) {
    return { value: null };
  }
  const events = parseEvents(value);
  return { value: events };
},

๐Ÿ—‚๏ธ Initial Data Uses null for Empty Days

Cells with no events are initialized as null (not []), ensuring consistent behavior between the initial render and after a user clears a cell.

let events: EventType[] | null = null;
// only assign if there are events for this day
if (...) {
  events = parseEvents('...');
}
week.push(events);

โœ… Summary of Role Division

HookResponsibility
renderCallbackOuter layout (container, date header) โ€” always applied
renderArrayRenders event badges when value is an array
renderNullRenders "No events" placeholder when value is null
deserializeFirstConverts empty string input back to null