Skip to content

AcuityScheduling/acuity-calendar

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Acuity Calendar

This package is not production ready. It will be changing extensively. Use at your own risk.

Acuity calendar is an open source set of react calendar components used in the Acuity Scheduling app.


Table of contents


Installation

Currently, this package is only hosted on Github and can be installed by invoking

npm i github:AcuityScheduling/acuity-calendar#<commit sha>

In the future, we will be migrating to a semvar system and using git tagging to reference releases.


Anatomy

Before we dig into the exposed components and data types, it's important to establish a shared visual languages In this library all our views are based on two base layouts - the DayGrid and the TimeGrid.

DayGrid views (Monthly)

DayGrid anatomy

  1. A cell. Custom render function available through the renderCell prop.
  2. A cell date. Clicking triggers onSelectDate prop if specified.
  3. A header. Custom render function availble through the renderHeader prop.
  4. An event
  5. The "More event" button. Triggers onSelectMoreEvents prop if specified.

TimeGrid views (Daily/Weekly)

TimeGrid anatomy

  1. The stepMinute subdivision between each step in the grid
  2. The stepHeight spacing between each step
  3. A grid header. Custom render function available through the renderHeader prop.
  4. A grid column. Custom render function available through the renderColumns prop.
  5. An event
  6. The current time indicator
  7. A stepDetail
  8. The selectMinute subdivision for selecting on the calendar
  9. The Select Slot indicator. Custom render function available through renderSelectSlotIndicator

Datatypes

The Acuity calendar is a generalized event calendar with support for read and update-like operations. We define a few key datatypes for working within the event calendar space as presented below.

Date

Naturally when working with events we are also working with representations of date and time. To allow maximum flexibility, most date fields, inputs and props in the calendar accepts data in one of the following formats

const DATE_TYPE = PropTypes.oneOfType([
  PropTypes.instanceOf(Date),
  PropTypes.instanceOf(moment),
  PropTypes.string,
]);

meaning that the plain old JavaScript Date object instance as well as moment instances are accepted. Any moment parsable date-time string is also accepted.

Event

An event represents a single unit appointment within the calendar. Events are required to be of the following simple structure

const EVENT_TYPE = PropTypes.shape({
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
  group_id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  title: PropTypes.string,
  start: DATE_TYPE.isRequired,
  end: DATE_TYPE.isRequired,
});

The id field is required to be a unique idenfier for a particular event. The optional group_id field ties multiple event to a bundle, batch or specific calendar. The optional title of the event is what will be displayed on the rendered calendar event. Finally, the start and end dates are naturally required.

View

A view is a particular calendar representation of a date range. Think of these as the daily, weekly and monthly views found on most digital calendars.

Custom views are specifiable if you, say, want to represent seperate columns for a list of appointment groups or columns for different timezones.

Views are defined by the following structure

const VIEWS_TYPE = PropTypes.arrayOf(
  PropTypes.oneOfType([
    PropTypes.oneOf(['groups', 'week', 'month']),
    PropTypes.shape({
      view: PropTypes.string.isRequired,
      grid: PropTypes.oneOf(['time', 'day']),
      render: PropTypes.func,
      displayName: PropTypes.string,
    }),
  ])
);

The built-in views consists of groups, week and month, where the latter two should be self-explanatory. The groups view can be considered a generalized "day" view in which one or more columns can display events for a single date in time. Note that the calender currently does not support a year view.

For custom views, a view "template" has to be provided via the view field. The grid field in a matrix representation using nested arrays to represent how and where appointments should be positioned. The render field can be used to specify a custom render functions for events. The function is supplied a specific set of props depending on which type of grid is selected, either time or day.

For day grids the list render function would look like:

const render = ({
  isEventDraggable,
  onDragEnd,
  onSelectEvent,
  onSelectSlot,
  events,
  selectedDate,
  firstDay,
  visibleEventGroups,
  onSelectMore,
  onSelectDate,
  forceSixWeeks,
  renderCell,
  renderEvent,
}) => {
  return <div>...</div>
}

and for time grids the render function would look like

const render = ({
  isEventDraggable,
  onDragEnd,
  onSelectEvent,
  onSelectSlot,
  events,
  selectedDate,
  firstDay,
  visibleEventGroups,
  isEventExtendable,
  onExtendEnd,
  onCurrentTimeChange,
  onSelectRangeEnd,
  stepDetails,
  minWidthColumn,
  minWidthColumnEmpty,
  renderCorner,
  renderEventPaddingBottom,
  renderEventPaddingTop,
  renderSelectRange,
  renderStepDetail,
  selectMinutes,
  stepHeight,
  stepMinutes,
  renderSelectSlotIndicator,
  renderEvent,
  scrollToTime,
  withGroups,
}) => {
  return <div>...</div>
}

Step details

Step details can be considered generic blocks that render on the calendar. These blocks can span over a number of time steps and contain details, hence the name "step details". Think of these as abstractions for blocking off periods of time on a calendar with an optional note.

Semantically, step details and events are similar, but functionally you can consider step details as a non-interactable event, i.e. not extendable, dragable or selectable.

The data model for step details is as follows:

export const STEP_DETAILS_TYPE = PropTypes.shape({
  id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
  group_id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
  start: DATE_TYPE.isRequired,
  end: DATE_TYPE.isRequired,
});

Usage

In general, a fully wired calendar is available as the default export

import Calendar from 'acuity-calendar';

for a description of its props and configuration, please see FullCalendar.

A number of other components are available as named exports via

import {
  CalendarGroups,
  CalendarMonth,
  CalendarMonthHeatmap,
  CalendarWeek,
  DayList,
  DayGrid,
  TimeGrid,
  Toolbar,
} from 'acuity-calendar';

For descriptions and props please see each individual component. These components will generally be used to build custom calendar views.


Components

The following components are exposed in this package. For usage examples please refer to the supplied Storybook

CalendarGroups

import { CalendarGroups } from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
  { id: 1, group_id: 4, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
  { id: 2, group_id: 5, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
  { id: 3, group_id: 6, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

const eventGroups = [
  { id: 4, title: 'Group A' },
  { id: 5, title: 'Group B' },
  { id: 6, title: 'Group C' },
];

<CalendarGroups
  events={events}
  eventGroups={eventGroups}
  visibleEventGroups={[4, 5, 6]}
  selectedDate={now}
/>

The CalendarGroups component makes it easy to render a daily view for a list of events where each column represents a common identifier for the events, like their date or group id. If the shared identifier is the date, this view will simply be a daily view found on most calendars.

Within our data model, event groups are not formally defined, but must at least have an id and a title. As with the CalendarWeek component, the CalendarGroups is a shorthand for instantiating a partially preconfigured TimeGrid instance.

Prop Type Default value Description
events array [] Array of Events
eventGroups array undefined Required. Array of event groups. An event group must at minimum contain { id, title }.
renderHeader func null Custom render function for grid headers. Given { group, events }.
selectedDate Date, moment or string new Date() The currently selected date. See Date
visibleEventGroups number[] null Array of visible group_id. Controls which groups are rendered on the view.
...rest any Any additional prop will be passed to the inner TimeGrid component

CalendarMonth

import { CalendarMonth } from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
  { id: 1, group_id: 1, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
  { id: 2, group_id: 1, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
  { id: 3, group_id: 1, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

// Memoized handlers
const handlers = useMemo(() => {
  return {
    isEventDraggable: ({ event }) => { ... },
    onDragEnd: ({ e, event }) => { ... },
    onSelectDate: ({ e, date, isInRange }) => { ... },
    onSelectEvent: ({ e, event}) => { ... },
    onSelectMore: ({ e, events, eventsMore }) => { ... },
    onSelectSlot: ({ e, date, isInRange }) => { ... },
  }
}, []);

<CalendarMonth 
  events={events}
  {...handlers}
/>

The CalendarMonth is along with the DayList a shorthand to render a specified DayGrid, in this case for a monthly view in a grid structure. Besides controlling start of week via firstDay and the number of week via forceSixWeeks, most of the configuration consists of various interaction event handlers.

Prop Type Default value Description
events array [] Array of Events
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
forceSixWeeks bool false Forces the 6 weeks in the calendar for short months for consistant height while navigating
isEventDraggable func () => true Callback to determine if an even is draggable. Given { event }.
onDragEnd func () => null Callback when a drag event ends. Given { e, event }, where e is the synthetic event
onSelectDate func () => null Callback when a date is clicked. Given {e, date, isInRange }, where isInRange denotes if the date is visible in the current view.
onSelectEvent func () => null Callback when a event is clicked. Given {e, date, isInRange }, where isInRange denotes if the event is visible in the current view.
onSelectMore func () => null Callback when the "see more events" text is clicked. Given { e, events, eventsMore }, where eventsMore is a boolean denoting if there are more events after clicking
onSelectSlot func () => null Callback for when a cell is clicked. Given { e, date, isInRange }, where isInRange denotes if the slot is visible in the current view.
renderCell func null Custom render function for cell contents. Given { date, isInRange, events }, where isInRange denotes if the cell is visible in the current view.
renderHeader func null Custom render function for grid headers. Given { date }.
selectedDate Date, moment or string new Date() The currently selected date. See Date
visibleEventGroups number[] null Array of visible group_id. Controls which groups are rendered on the view.

CalendarMonthHeatmap

import { CalendarMonthHeatMap } from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
  { id: 1, group_id: 1, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
  { id: 2, group_id: 1, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
  { id: 3, group_id: 1, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

const counts = useMemo(() => {
  return events.reduce((acc, event) => {
    if (!acc[event.start])a acc[event.start] = 0
    acc[event.start] = (acc[event.start] + 1) % 100
    return acc;
  }, {})
}, [events])

<CalendarMonthHeatMap 
  counts={counts}
  events={events}
  selectedDate={events[0].start}
/>

The CalendarMonthHeatMap visuelizes the weight of each day in a monthly view. The weight of day is specified by passing the counts object mapping YYYY-MM-DD formatted dates to a value between 0 and 100. The heavier the weight, the deeper the day will be colored.

Prop Type Default value Description
counts object {} Object mapping the "heat" a YYYY-MM-DD formatted date to an opacity value (0-100).
events array [] Array of Events
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
forceSixWeeks bool true Forces the 6 weeks in the calendar for short months for consistant height while navigating
onSelectCell func () => null Callback when selecting a date. Given { e, date, isInRange, weight, count }.
renderCell func undefined Custom render function for cells. Given { date, isInRange, events, weight, count }.
renderHeader func undefined Custom render function for headers. Given { date }.
selectedDate Date, moment or string new Date() The currently selected date. See Date

CalendarWeek

import { CalendarWeek } from 'acuity-calendar';

const timeGridProps = { ... }

const renderHeader = useCallback(({  date, events }) => {
  ...
}, []);

<CalendarWeek
  renderHeader={renderHeader}
  {...timeGridProps}
/>

Analogous to the DayList component, the CalendarWeek is a shorthand for initializing a TimeGrid. Note that the renderHeader custom render function differs from that of CalendarMonth as it also is given the full list of events. This is useful if, say, you want to render the amount of appointments in each column directly in the column header.

Prop Type Default value Description
renderHeader func null Callback to render the header. Given { date, events }. See Column for more details on configuration of the internal Column component
...rest any Any additional prop will be passed to the inner TimeGrid component

Column

const handlers = useMemo(() => {
  return {
    getUpdatedDraggedEvent: ({ event, start, end, columnMoves }) => { ... },
    isEventDraggable: ({ event }) => { ... },
    isEventExtendable: ({ event }) => { ... },
    onDragEnd: ({ e, event }) => { ... },
    onExtendEnd: ({ e, ui, event }) => { ... },
    onSelectEvent: ({ e, event }) => { ... },
    onSelectRangeEnd: ({ e, start, end, column }) => { ... },
    onSelectSlot: ({ e, date, column }) => { ... },
  }
}, []);

const renderers = useMemo(() => {
  return {
    renderEvent: event => { ... },
    renderEventPaddingTop: event => { ... },
    renderEventPaddingBottom: event => { ... },
    renderSelectRange: ({ start, end }) => { ... },
    renderSelectSlotIndicator: ({ time, column }) => { ... },
    renderStepDetail: stepDetail => { ... },
  }
}, []);

const events = {
  '2021-01-01': [ ... ]
}

<Column
  columnId={1}
  columnIndex={1}
  columnWidths={[190]}
  { ...handlers }
  { ...renderers }
  events={events}
  date="2021-01-01"
/>

While the Column component is not publically exposed in this package, it is an important component for when custom render functions are defined for for TimeGrid utilizing views, such as the weekly and group views. In particular, the renderHeader and renderColumns prop on TimeGrid are required to define their markup via an instanced Column.

Prop Type Default value Description
columnId Date, number or string undefined Required. The unique identifier for the column
columnIndex number undefined Required. The column index for the current column group
columnWidths number[] undefined Required. Array mapping columnIndex to a pixel width
currentTime moment null The current time as a moment instance
date Date or string new Date() The date which the column represents
showCurrentTimeIndicator bool true Determines if the current time indicator is rendered
events object {} Unlike the exposed components in this package, this events prop is a map 'YYYY-MM-DD' formatted date keys to grid cells
getUpdatedDraggedEvent func Callback to obtain the event after a drag event. Given { event, start, end, columnMoves }
gridHeight number Required. The height of the grid
isEventDraggable func () => trur Callback to determine if an event is dragable. Given { event }
isEventExtendable func () => true Callback to determine if an event is extendable. Given { event }
minWidth number 190 The width a non-empty column should expand to
minWidthEmpty number 100 The min width an empty column should expand to
onDragEnd func () => null Callback when a drag event ends. Given { e, event }
onExtendEnd func () => null Callback when an extend event ends. Given event
onSelectEvent func () => null Callback when a select event occurs. Given { e, event }
onSelectRangeEnd func () => null Callback when a range selection event ends. Given { e, start, end, column }
onSelectSlot func () => null Callback when selecting a slot. Given { e, date, column }
renderEvent func null Render function for events. Given event
renderEventPaddingTop func () => null Render function to render the padding above events in the column. Given event
renderEventPaddingBottom func () => null Render function to render the padding below events in the column. Given event
renderSelectRange func null Render function for the range selection. Given { start, end }
renderSelectSlotIndicator func null Render function for the selected slot indicator. Given { time, column }
renderStepDetail func () => null Render function for Step details. Given stepDetail.
selectMinutes number 15 Must be one of [5, 10, 15, 20, 30, 60]. The minute interval in which selecting clicking the calendar should occur in
stepDetails array [] Array of StepDetails
stepHeight number null The pixel value height between each step in the TimeGrid
stepMinutes number 30 Must be one of [5, 10, 15, 20, 30, 60]. The minute interval between each horizontal line in the column

FullCalendar

import FullCalendar from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
  { id: 1, group_id: 4, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
  { id: 2, group_id: 5, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
  { id: 3, group_id: 6, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

const eventGroups = [
  { id: 4, title: 'Group A' },
  { id: 5, title: 'Group B' },
  { id: 6, title: 'Group C' },
];

const stepDetails = [
  { id: 1, group_id: 6, title: 'Blocked off time', start: addMinutes(now, 40), end: addMinutes(now, 50)},
];

const customRenderers = useMemo(() => {
  return {
    renderTimeGridEvent: event => { ... },
    renderStepDetail: stepDetail => { ... },
  }
}, [])

const handlers = useMemo(() => {
  return {
    isEventDraggable: ({ event }) => { ... },
    isEventExtendable: ({ event }) => { ... }, 
    onCurrentTimeChange: date => { ... },
    onDragEnd: ({ e, event }) => { ... },
    onExtendEnd: ({ e, ui, event }) => { ... },
    onFetchEvents: ({ fetchMoreRange, fullRange, initialFetch, outsideRange }) => { ... },
    onNavigate: date => { ... },
    onSelectDate: ({ e, date, isInRange }) => { ... },
    onSelectEvent: ({ e, event}) => { ... },
    onSelectMore: ({ e, events, eventsMore }) => { ... },
    onSelectRangeEnd: ({ e, start, end, column }) => { ... },
    onSelectSlot: ({ e, date, isInRange }) => { ... },
    onViewChange: view => { ... },
  }
}, []);

<FullCalendar
  currentView="month"
  eventGroups={eventGroups}
  events={events}
  selectedDate={now}
  stepDetails={stepDetails}
  views={['month', 'week', { view: 'groups', displayName: 'Day' }]}
  visibleEventGroups={[4, 5, 6]}
  {...handlers}
  {...customRenderers}
/>

The FullCalendar is a composite component, providing a fully fledged calendar consisting of a Toolbar and three default views; monthly, weekly and daily. Under the hood it is orchestrates wiring between the lowest level of exposed components in this library, in particular the TimeGrid and DayGrid.

Prop Type Default value Description
currentView string undefined Required. The currently showing view
eventGroups array undefined Required. Array of event groups. An event group must at minimum contain { id, title }.
events array [] Array of Events
fetchEventInitialFullRange object null Passed to the inner Toolbar
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
forceSixWeeks bool false Passed to the inner DayGrid
isEventDraggable func () => true Passed to the inner DayGrid and TimeGrid
isEventExtendable func () => true Passed to the inner TimeGrid
minWidthColumn number 190 Passed to the inner TimeGrid
minWidthColumnEmpty number 100 Passed to the inner TimeGrid
onCurrentTimeChange func () => null Passed to the inner TimeGrid
onDragEnd func () => null Passed to the inner DayGrid and TimeGrid
onExtendEnd func () => null Passed to the inner TimeGrid
onFetchEvents func () => null Passed to the inner Toolbar
onNavigate func null Passed to the inner Toolbar
onSelectDate func undefined Passed to the inner DayGrid
onSelectEvent func () => null Passed to the inner DayGrid and TimeGrid
onSelectMore func () => null Passed to the inner DayGrid
onSelectRangeEnd func () => null Passed to the inner TimeGrid
onSelectSlot func () => null Passed to the inner DayGrid and TimeGrid
onViewChange func () => null Passed to the inner Toolbar
renderCell func null Passed to the inner DayGrid
renderCorner func () => null Passed to the inner TimeGrid
renderEventPaddingBottom func () => null Passed to the inner TimeGrid
renderEventPaddingTop func () => null Passed to the inner TimeGrid
renderGroupsHeader func null Custom daily view header render function. See renderHeader prop for CalendarGroups
renderMonthHeader func null Custom monthly view header render function. See renderHeader prop for CalendarMonth
renderSelectRange func null Passed to the inner TimeGrid
renderSelectSlotIndicator func null Passed to the inner TimeGrid
renderStepDetail func () => null Passed to the inner TimeGrid
renderTimeGridEvent func null Passed to the inner TimeGrid
renderToolbar func null Custom Toolbar render function. See children prop for Toolbar
renderWeekHeader func null Custom weekly view header render function. See renderHeader prop for CalendarWeek
scrollToTime string, moment or Date 'firstEvent' Passed to the inner TimeGrid
selectMinutes number 15 Passed to the inner TimeGrid
selectedDate Date, moment or string new Date() The currently selected date. See Date
stepDetails array null Array of Step details
stepHeight number null Passed to the inner TimeGrid
stepMinutes number 30 Passed to the inner TimeGrid
style object {} Object to pass custom styles to the calendar
views array ['month', 'week', 'groups'] The list of views supported by the calendar instance. See View
visibleEventGroups number[] null Passed to the inner DayGrid and TimeGrid

DayList

import { DayList } from 'acuity-calendar';

const dayGridProps = {
  events: [],
  ...
};

<DayList
  firstDay={2}
  totalDays={5}
  renderCell={({ full, small, min, int }) => <div>Cell</div>}
  renderHeader={({ full, small, min, int }) => <div>Header</div>}
  {...dayGridProps}
/>

The DayList renders a grid view of at least one row where each cell represents a day, which commonly is used for monthly calendar views or weekly views where events aren't ordered horizontally with respect to their starting time. This component can be considered shorthand syntactic sugar for initializing a DayGrid with an easier interface for controlling the first day of the week and the number of cells in the grid.

Prop Type Default value Description
renderCell func () => null Callback to render the cell contents. The callback is given the following object { full: date.format('dddd'), small: date.format('ddd'), min: date.format('dd'), int: Number(date.format('d')) }. See moment.js formatting and Anatomy for more details.
renderHeader func null Callback to render the header. Given the same object as renderCell. See Anatomy for more details
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
totalDays number 7 The total days in the week
...rest any Any additional prop will be passed to the inner DayGrid component

DayGrid

import { DayGrid } from 'acuity-calendar';
import { format, addMinutes } from 'date-fns';

const now = new Date();

const GROUP_1 = 1;
const GROUP_2 = 2;
const GROUP_3 = 3;

const events = [
  { id: 1, group_id: GROUP_1, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
  { id: 2, group_id: GROUP_2, title: 'Some other event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
  { id: 3, group_id: GROUP_3, title: 'Some third event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

const SUNDAY_INDEX = 0;

const grid = {
  firstDate: moment().day(SUNDAY_INDEX),
  lastDate: moment().day(SUNDAY_INDEX).add(7, 'days'),
  totalColumns: 7,
};

const renderCell = useCallback(({ date, isInRange, events }) => (
  <div>
    {events.map(event => isInRange && (
       <div key={event.id}>
        <small>{event.start.format('HH:mm')}:</small> {event.title}
      </div>}
    ))}
  </div>
), []);

<DayGrid 
  events={events}
  isEventDraggable={({ event }) => { ... }}
  grid={grid}
  renderCell={renderCell}
  renderHeader={({ date }) => <div>{format(date, 'dddd')}</div>}
  onDragEnd={({ e, event }) => { ... }}
  onSelectSlot={({ e, date, isInRange }) => { ... }}
  onSelectDate={({ e, date, isInRange }) => { ... }}
  onSelectMoreEvents={({ e, events, eventsMore }) => { ... }}
  onSelectEvent={({ e, event}) => { ... }}
  visibleEventGroups={[ GROUP_1, GROUP_2, GROUP_3 ]}
/>

The DayGrid renders a fully customizable grid calendar view in which each cells represents a day. Individual handlers can be specified to handle day/event selection and dragging. See Anatomy for more information about grids, headers, cells and events.

Prop Type Default value Description
events array [] Array of Events
grid array[] or object undefined Required. Accepts a grid constisting of an array of arrays of { isInRange, date} or a grid configuration in the shape of { firstDate, lastDate, totalColumns, allowPartialRows, getExcludedDates, getIsInRange}
isEventDraggable func () => true Callback to determine if an even is draggable. Given { event }.
onDragEnd func () => null Callback when a drag event ends. Given { e, event }, where e is the synthetic event
onSelectDate func null Callback when a date is clicked. Given {e, date, isInRange }.
onSelectEvent func () => null Callback when a event is clicked. Given {e, date, isInRange }.
onSelectMoreEvents func () => null Callback when the "see more events" text is clicked. Given { e, events, eventsMore }, where eventsMore is a boolean denoting if there are more events after clicking the button
onSelectSlot func () => null Callback for when a cell is clicked. Given { e, date, isInRange }.
renderCell func null Custom render function for cell contents. Given { date, isInRange, events }.
renderHeader func null Custom render function for grid headers. Given { date }.
visibleEventGroups number[] null Array of visible group_id. Controls which groups are rendered on the view.

TimeGrid

import { TimeGrid } from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
    { id: 1, group_id: 1, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
    { id: 2, group_id: 1, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
    { id: 3, group_id: 1, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];

const Header = ({ ColumnComponent, week, events }) => {
  return (
    {week.map(day => (
      <ColumnComponent 
        key={day.toISOString()}
        totalEventColumns={week.length}  
        date={day.toISOString()}
        columnClass="week"
      >
        <div>
          <div className={`time-grid__week-header__date`}>
            {moment(date).format('M/D')}
          </div>
          <div className={`time-grid__week-header__day`}>
            {moment(date).format('dddd')}
          </div>
        </div>
      </ColumnComponent>
    ))}
  ):
);

const Columns = ({ ColumnComponent, week, events, stepDetails }) => {
  return week.map((day, i) => {
    const columnId = moment(day).format("YYYY-MM-DD");
    return (
      <ColumnComponent
        key={columnId}
        eventsForColumn={events[columnId]}
        eventsForColumn={stepDetails[columnId]}
        date={day.toISOString()}
        columnKey={columnId}
        columnIndex={index}
        columnId={columnId}
      />
    );
  });
};

const handlers = useMemo(() => {
  return {
    isEventDraggable: ({ event }) => { ... },
    isEventExtendable: ({ event }) => { ... },
    onCurrentTimeChange: date => { ... },
    onDragEnd: ({ e, event }) => { ... },
    onExtendEnd: ({ e, ui, event }) => { ... },
    onSelectEvent: ({ e, event }) => { ... },
    onSelectRangeEnd: ({ e, start, end, column }) => { ... },
    onSelectSlot: ({ e, date, column }) => { ... },
  }
}, []);

const renderers = useMemo(() => {
  return {
    renderColumns: ({ ColumnComponent, week, events, eventsWithGroups, stepDetails, stepDetailsWithGroups }) => { ... },
    renderCorner: { currentTime } => { ... },
    renderEvent: event => { ... },
    renderEventPaddingBottom: event => { ... },
    renderEventPaddingTop: event => { ... },
    renderHeaders: ({ ColumnComponent, week, events, eventsWithGroups, stepDetails, stepDetailsWithGroups }) => { ... },
    renderSelectRange: ({ start, end }) => { ... },
    renderSelectSlotIndicator: ({ time, column }) => { ... },
    renderStepDetail: stepDetails => { ... },
  }
}, []);


<TimeGrid events={events} renderHeader={Header} renderColumns={Columns} {...handlers} {...renderers} />

The TimeGrid component renders a grid consisting of time "steps" on the y-axis and any number of columns representing anything, depending on function defintions. These columns could represent days, calendars, user groups, etc. The grid allows user to completely customize time-related views by defining callback functions to render headers and column based on event data input. Additionally, it's configuration allows for complete control on how selection, dragging, extension and events are handled and rendered.

Prop Type Default value Description
showCurrentTimeIndicator bool true Determines if the horizontal current time indicator is shown in the calendar. Useful to disable if a TimeGrid view represents multiple timezones across different column and a "current time" does not make sense to show
events array [] Array of Events
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
isEventDraggable func () => null Callback to determine if an event is draggable. The callback is given the event
isEventExtendable func () => null Callback to determine if an event is extendable. The callback is given the event
minWidthColumn number 190 The minimum width in pixels that a column should span
minWidthColumnEmpty number 100 The minimum width in pixels that an empty column should span
onCurrentTimeChange func () => null Callback for when the current time changes. The callback is given an instance of Date
onDragEnd func () => null Callback for when a drag event ends. The callback is given the the event
onExtendEnd func () => null Callback for when an extend event ends. The callback is given the event
onSelectEvent func () => null Callback for when an event is selected. The callback is given { e, event }
onSelectRangeEnd func () => null Callback for when a range is selected in a column. The callback is given { e, start, end, column }
onSelectSlot func () => null Callback for when an open slot in a column is clicked. The callback is given { e, date, column }
renderColumns func undefined Required. Callback function to render each column. The callback function is given the object: { ColumnComponent, week, events, eventsWithGroups, stepDetails, stepDetailsWithGroups }
renderCorner func () => null Callback to render something, like a clock, in the top left corner of the time grid. The callback is given { currentTime }
renderEvent func null Function to override the default render method for events. If passed, the callback is given event
renderEventPaddingBottom func () => null Callback to render bottom padding to an event. The callback is given event.
renderEventPaddingTop func () => null Callback to render top padding to an event. The callback is given event.
renderHeaders func undefined Required. Callback function to render the header of each column. The callback function is given the object: { ColumnComponent, week, events, eventsWithGroups, stepDetails, stepDetailsWithGroups }
renderSelectRange func null Function to override the way selecting a range is rendered. The callback is given { start, end }
renderSelectSlotIndicator func null Function to override the way the selected slot indicator is rendered. Given { time, column }
renderStepDetail func () => null Custom function to render StepDetails. Given stepDetails
scrollToTime 'firstEvent', string, moment or Date 'firstEvent' Either 'firstEvent' or Date type. Scrolls to either the first event or a particular moment in time
selectedDate Date, moment or string new Date() The currently selected date. See Date
selectMinutes number 15 Must be one of [5, 10, 15, 20, 30, 60]. The minute interval in which selecting clicking the calendar should occur in
stepDetails array null Array of StepDetails
stepHeight number null The height of each row in grid
stepMinutes number 15 Must be one of [5, 10, 15, 20, 30, 60]. The minute interval between each horizontal line in the column
visibleEventGroups number[] null Determines which provided columns are rendered or hidden
withColumns Bool true If false, the grid will simply put group events by day. If true, it will respect column_id to group events

Toolbar

import { Toolbar } from 'acuity-calendar';
import { addMinutes } from 'date-fns';

const now = new Date();

const events = [
    { id: 1, group_id: 1, title: 'Some event', start: addMinutes(now, 10), end: addMinutes(now, 20)},
    { id: 2, group_id: 1, title: 'Some event', start: addMinutes(now, 20), end: addMinutes(now, 30)},
    { id: 3, group_id: 1, title: 'Some event', start: addMinutes(now, 30), end: addMinutes(now, 40)},
];


const handlers = useMemo(() => {
  return {
    onFetchEvents: ({ fetchMoreRange, fullRange, initialFetch, outsideRange }) => { ... },
    onNavigate: date => { ... },
    onViewChange: view => { ... },
  }
}, []);

<Toolbar 
  currentView="month" 
  views={['month', 'week', 'day']} 
  events={events} 
  {...handlers}
/>

The Toolbar component provides control over the calendar by allowing users to switch between views and change the current date selection. A custom Toolbar can be implemented via the children prop like so:

import { Toolbar } from 'acuity-calendar';

const MyCustomToolbar = ({onNext, onPrev, onToday, title, eventsForView}) => {
    return (
        <header>
            <button onClick={onNext}>Prev</button>
            <button onClick={onToday}>Today</button>
            <button onClick={onPrev}>Next</button>
            <h1>{title}</h1>
        </header>
    );
}

<Toolbar currentView="month" views={['month', 'week', 'day']} children={MyCustomToolbar} />
Prop Type Default value Description
children func null Optional. If no custom Toolbar is passed a default will be rendered
currentView string undefined Specifies the current view. Should be member of the provided views
events array [] Array of Events
fetchEventInitialFullRange object null Object containing start and end Dates key. Specifies the range of which the first full fetch should span over
fetchEventPadding number 1 The padding before and after each event. Useful if you're trying to block off small amounts of time between events
firstDay number 0 Must be one of [0, 1, 2, 3, 4, 5, 6]. The first day of the week in numerical form, starting with Sunday as 0
onFetchEvents func () => null Callback triggered when toolbar navigation is exceeds currently fetched window and we should fetch more events. Given { fetchMoreRange, fullRange, initialFetch, outsideRange }
onNavigate func undefined Callback triggered when the current date is changed. Given the new date
onViewChange func undefined Callback triggered the view is changed. Given the new view
selectedDate Date, moment or string undefined The selected date. See Date
views string[] or object[] undefined The list of views supported by the calendar instance. See View

Storybook

Run storybook with npm run storybook

Testing

Tests are written using jest and can be run by invoking

npm test

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •