Primitives React Components to build a Calendar
Okami is used in production, we consider evolving its API, but it works as expected!
It's a set of Primitives React Components to build a Calendar. Handle the logic so you can focus on UI/UX. Similar to downshift or selectless.
We use composition to construct the calendar, which simply means:
- A monthly calendar is composed of weekly calendar.
- A weekly calendar is composed of daily calendar.
This allow to have a lot fo flexibility without repeating the logic and avoid complexity.
We strongly recommend to use okami
with date-fns.
yarn add okami
npm install --save okami
For more examples, look at the storybook :)
import React from 'react'
import frLocale from 'date-fns/locale/fr'
import { Calendar, DailyCalendar, Navigation, HoursLabels } from 'okami'
import data from './stories/data'
const Container = props =>
<div style={{ display: 'flex', alignItems: 'stretch', justifyContent: 'center'}} {...props} />
const CalendarContainer = props =>
<div style={{ display: 'flex', alignItems: 'stretch', justifyContent: 'flex-start', width: '100%' }} {...props} />
export default props => (
<Calendar
data={data}
startingDay="monday"
dateFormat="ddd DD/MM"
hourFormat="HH"
startHour="PT3H"
endHour="PT22H"
locale={frLocale}
>
<DailyCalendar showNow {...props}>
{({calendar, dayEvents, rowHeight}) => (
<div style={{display: 'flex', flexDirection:'column'}}>
<div style={{display:'flex'}}>
<Navigation dateFormat="ddd DD MMMM">
{({next, prev, today, currentDate}) => (
<div style={{display:'flex'}}>
<button onClick={today}>Today</button>
<button onClick={prev}>Prev day</button>
<button onClick={next}>Next day</button>
<span>{currentDate}</span>
</div>
)}
</Navigation>
</div>
<Container>
<div style={{paddingTop: rowHeight * (dayEvents.length + 1)}}>
<HoursLabels renderChild={props => <span style={{height: rowHeight}} {...props} />} />
</div>
<CalendarContainer style={{flexDirection: 'column'}}>
{dayEvents.map(props => <Event {...props} />)}
{calendar.events.map(props => <Event {...props} />)}
</CalendarContainer>
</Container>
</div>
)}
</DailyCalendar>
</Calendar>
)
property | type | required | default | description |
---|---|---|---|---|
data |
array |
yes | - | List of events |
startHour |
string |
no | PT0H | Hour to start the calendar |
endHour |
string |
no | PT24H | Hour to end the calendar |
dateFormat |
string |
no | MM/DD/YYYY | Format of the date string |
hourFormat |
string |
no | HH:mm | Format of the hour string |
startingDay |
string |
no | sunday | Starting day of the week |
locale |
object |
no | en | Local from date-fns |
rowHeight |
number |
no | 30 | Height of a row |
To define property that will be use as duration like startHours
or endHour
.
We use ISO-8601 format for durations.
Every prop passed to Calendar can be overwritten as the sub component level.
<Calendar
data={data}
startingDay="monday"
dateFormat="ddd DD/MM"
hourFormat="HH"
startHour="PT3H"
endHour="PT22H"
>
<MonthlyCalendar>{...}</MonthlyCalendar>
<DailyCalendar dateFormat="DD-MM-YYYY">{...}</DailyCalendar>
</Calendar>
This will use the dateFormat
from <Calendar>
in <MonthlyCalendar>
but <DailyCalendar>
will use his custom dateFormat
when it renders.
Render a day.
property | type | default | description |
---|---|---|---|
start |
Date |
new Date() |
Day to show |
showNow |
boolean |
false |
Send props to show the time of day |
property | type | description |
---|---|---|
calendar |
Object |
events, label and date of the day |
start |
Date |
date of the day |
showNowProps |
Object |
if showNow is true, props to the showNow container (postion) |
hours |
Array |
Array of formatted string for hours labels |
rowHeight |
Number |
Height of the row |
dayEvents |
Array |
Array of the events that last more than a day of the all day |
calendar
property | type |
---|---|
date |
Date |
events |
Array |
dayEvents
property | type |
---|---|
key |
event.id |
event |
Object |
style |
Object |
The style object is passed only if the getColumnProp
is called and we have a ref available.
methods | description |
---|---|
nextDay |
Go to next day |
prevDay |
Go to previous day |
gotoToday |
Go to today |
dateLabel |
get the date formatted |
getColumnProps |
get the props for the container of the events |
dateLabel
Allow you to render the current day with a special format.
- dateFormat: use the convention from
date-fns
format
getColumnProps
Allow to get the ref to your column element for calculation of the events position.
- refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getRootProps({refKey: 'innerRef'}) and your composite component would forward like: . By default, it will use the react default ref key
Render a week.
property | type | default | description |
---|---|---|---|
start |
Date |
new Date() |
Day of the week to show |
showNow |
boolean |
false |
Send props to show the time of day |
property | type | description |
---|---|---|
calendar |
Array |
list of day for the week |
end |
Date |
end of the week |
start |
Date |
start of the week |
showNowProps |
Object |
if showNow is true, props to the showNow container (postion) |
hours |
Array |
Array of formatted string for hours labels |
rowHeight |
Number |
Height of the row |
weekEvents |
Array |
Array of the events that last more than a day of the all week |
weekEvents
property | type |
---|---|
key |
event.id |
event |
Object |
style |
Object |
The style object is passed only if the getContainerProps
is called and we have a ref available.
methods | description |
---|---|
nextWeek |
Go to next week |
prevWeek |
Go to previous week |
gotoToday |
Go to today |
dateLabel |
get the date formatted |
getContainerProps |
get the props for the container of the events |
dateLabel
Allow you to render the current week with a special format.
- dateFormat: use the convention from
date-fns
format
getContainerProps
Allow to get the ref to your column element for calculation of the events position.
- refKey: if you're rendering a composite component, that component will need to accept a prop which it forwards to the root DOM element. Commonly, folks call this innerRef. So you'd call: getRootProps({refKey: 'innerRef'}) and your composite component would forward like: . By default, it will use the react default ref key.
Render a month.
property | type | default | description |
---|---|---|---|
start |
Date |
new Date() |
Date of the curent month |
property | type | description |
---|---|---|
calendar |
Array |
List of first day of the week for the month |
end |
Date |
end of the month |
start |
Date |
start of the month |
weeks |
Array |
List of the first day of week for the month |
rowHeight |
Number |
Height of the row |
methods | description |
---|---|
nextMonth |
Go to next month |
prevMonth |
Go to previous month |
gotoToday |
Go to today |
dateLabel |
get the date formatted |
dateLabel
Allow you to render the current month with a special format.
- dateFormat: use the convention from
date-fns
format
render the navigation for the calendar and the current label. The navigation and label are based on the type.
If you use this in a DailyCalendar
, next()
will jump to the next day but if you use it in MonthlyCalendar
, it will jump to the next month.
You also get access to the function to toggle weekends on the calendar.
If you force showWeekend on a sub component, don't forget to update manually this props.
property | type | description |
---|---|---|
dateFormat |
string |
format of the date |
property | type | description |
---|---|---|
type |
string |
Type of calendar ('monthly', 'weekly', 'daily') |
currentDate |
string |
Formatted date based on type |
methods | description |
---|---|
next |
Go to next based on type |
prev |
Go to previous based on type |
today |
Go to today |
toggleWeekend |
show/hide weekend on calendar |
toggleWeekend
Let you toggle the weekend in the calendar. If you don't pass a pamarater, it will toggle the prop. If you pass a boolean, it will force the value.
const showWeekend = true
toggleWeekend() // showWeekend = false
toggleWeekend(true) // showWeekend = true
toggleWeekend(true) // showWeekend = true
toggleWeekend(false) // showWeekend = false
[...]
render the days/hours labels for the calendar. You have 3 options to render those components:
- Child Callback function : allow full control
renderChild
props: pass a component that receive pre formatted props- The default render that let you style the
div
container.
property | type |
---|---|
renderChild |
`Element |
DaysLabels
property | type | description |
---|---|---|
weeks |
array |
Array of the formatted day labels |
HoursLabels
property | type | description |
---|---|---|
hours |
array |
Array of the formatted hour labels |
<HoursLabels>
{ hours => hours.map(h => <span>{h}</span>} }
</HoursLabels>
props from the HoursLabels/DaysLabels are passed down to the container and the labels are rendered with the component from renderChild
.
<HoursLabels renderChild={props => <span {...props} />} />
props from the HoursLabels/DaysLabels are passed down to the container.
<HoursLabels />
This is the render function used :
<div {...props}>
{formattedDays.map((h, idx) => (
<div key={`day_label_${idx}`} style={{height: rowHeight}}>
{h}
</div>
))}
</div>
data
is an array of objects, the object requires a few properties :
property | type | description |
---|---|---|
id |
ID |
Identifier of the event |
title |
string |
Title of the event |
allDay |
`boolean | Date` |
start |
Date |
Beggining of the event |
end |
Date |
End of the event |
allDay
has 3 states :
true
, the event is on multiple daysfalse
, the event is no longer than a dayDate
, the event last all day
if allDay
is a boolean, you need to provide a start
and end
properties.
If you want to create an event for the all day, you just put the date in allDay
.
// Event of 2 hours:
{
id: 0,
title: 'Diner',
allDay: false,
start : 'Wed Sep 06 2017 12:07:52 GMT+0200 (CEST)',
end: 'Wed Sep 06 2017 14:07:52 GMT+0200 (CEST)'
}
// Event of all day:
{
id: 0,
title: 'Diner',
allDay: 'Wed Sep 06 2017 12:07:52 GMT+0200 (CEST)'
}
// Event of 2 days:
{
id: 0,
title: 'Diner',
allDay: true,
start : 'Wed Sep 06 2017 12:07:52 GMT+0200 (CEST)',
end: 'Wed Sep 08 2017 14:07:52 GMT+0200 (CEST)'
}
MIT