DateTime Picker
See original GitHub issue🚀 Feature request
Motivation
Inspired by #353, I’ve been working on a date picker implementation for the past couple of days and wanted to share my current level of progress and API ideas. I’ve been pulling inspiration from a number of libraries, but here is my idea for implementing some of the examples from gpbl/react-day-picker .
Note that all of the screenshots come from http://react-day-picker.js.org/examples/basic. So far I’ve done most of the stateful work and am currently working on getting the DOM output right. Only once that is done, I’ll start working on implementing style in reakit-system-bootstrap
. Right now I’m really looking for feedback on the proposed API for a calendar component which can be composed into more complex examples such as a real date/time picker.
Examples
Simple Calendar
![Screen Shot 2019-06-03 at 10 32 31 PM](https://user-images.githubusercontent.com/8162598/58849336-91502d80-864f-11e9-990f-ab01312e62fd.png)
Originally weekdays
and days
were of type Iterable
as that seemed to be
a clean way to allow for lazy computation. This API was then used as
[...calendar.days].map(day => ... )
. I now think that exposing them more
as prop-getters is a better approach as it is more beginner-friendly and allows
for passing options that determine the prop-getter output.
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Outside Days
visibleWhenOutside
in this case is passed directly as a prop to
CalendarMonthViewDay
but could just as easily be implemented by passing it as
an option to days
. I don’t have much of a preference in which way it is
implemented.
![Screen Shot 2019-06-03 at 10 35 26 PM](https://user-images.githubusercontent.com/8162598/58849433-f0ae3d80-864f-11e9-9718-8faa9e5ebb2b.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay visibleWhenOutside {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Week numbers
I imagine that most styling solutions will implement CalendarMonthView
as a
column-oriented flexbox. This will then necessitate a wrapper div
around
CalendarMonthViewWeeks
and CalendarMonthViewDays
so that they are
horizontally oriented. Another potential issue with this API is that some
options passed to days
need to be mirrored to weeks
such as fixedWeeks
or else they will render different numbers of rows.
![Screen Shot 2019-06-03 at 10 36 20 PM](https://user-images.githubusercontent.com/8162598/58849461-0a4f8500-8650-11e9-99af-56c9ed7109f0.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewWeeks,
CalendarMonthViewWeeksWeek,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<div>
<CalendarMonthViewWeeks {...calendar}>
{calendar.weeks().map(week => (
<CalendarMonthViewWeeksWeek {...week} {...calendar} />
))}
</CalendarMonthViewWeeks>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</div>
</CalendarMonthView>
</Calendar>
);
}
Fixed Weeks
![Screen Shot 2019-06-03 at 10 37 12 PM](https://user-images.githubusercontent.com/8162598/58849495-25ba9000-8650-11e9-965f-3201919e66d0.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days({ fixedWeeks: true }).map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Today Button
Is CalendarTodayButton
too specific when it’s really just a shorthand for
<Button onClick={() => calendar.setValue(new Date())}>Go to Today</Button>
![Screen Shot 2019-06-03 at 10 37 56 PM](https://user-images.githubusercontent.com/8162598/58849522-3e2aaa80-8650-11e9-94e8-5ffda25d187b.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay,
CalendarTodayButton
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
<CalendarTodayButton {...calendar} />
</Calendar>
);
}
Change the initial month
Although I can’t imagine that recalculating value
each render is that expensive
should useCalendarState
support allowing its options object and/or value
be
passed as factory functions.
![Screen Shot 2019-06-03 at 10 38 44 PM](https://user-images.githubusercontent.com/8162598/58849553-5b5f7900-8650-11e9-869b-4d7e8bfd8ddc.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState({ value: new Date(2015, 8) });
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Prevent Months Navigation
Preventing months navigation is as simple as not rendering the navigation controls
![Screen Shot 2019-06-03 at 10 39 33 PM](https://user-images.githubusercontent.com/8162598/58849583-77fbb100-8650-11e9-9ab8-46ff48b9bcca.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState();
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days().map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Restrict Months Navigation
In the example from react-day-picker
the navigation controls
are hidden when the user can no longer navigate in a direction.
This can be implemented by composing the navigation controls from
the Hidden
component. An equally valid use-case would be to disable
the buttons when the user can no longer navigate. Should one of these
be the default and what would be the best API to allow for the alternative?
![Screen Shot 2019-06-03 at 10 40 03 PM](https://user-images.githubusercontent.com/8162598/58849601-8944bd80-8650-11e9-80dd-1ac89d094591.png)
import React from "react";
import {
useCalendarState,
Calendar,
CalendarNavigation,
CalendarNavigationLabel,
CalendarNavigationNext,
CalendarNavigationPrev,
CalendarMonthView,
CalendarMonthViewWeekdays,
CalendarMonthViewWeekdaysWeekday,
CalendarMonthViewDays,
CalendarMonthViewDay
} from "reakit/Calendar";
function Example() {
const calendar = useCalendarState({
value: new Date(2081, 8),
from: new Date(2018, 8),
to: new Date(2018, 11)
});
return (
<Calendar {...calendar}>
<CalendarNavigation {...calendar}>
<CalendarNavigationLabel {...calendar} />
<CalendarNavigationPrev {...calendar} />
<CalendarNavigationNext {...calendar} />
</CalendarNavigation>
<CalendarMonthView {...calendar}>
<CalendarMonthViewWeekdays {...calendar}>
{calendar.weekdays().map(weekday => (
<CalendarMonthViewWeekdaysWeekday {...weekday} {...calendar} />
))}
</CalendarMonthViewWeekdays>
<CalendarMonthViewDays {...calendar}>
{calendar.days({ fixedWeeks: true }).map(day => (
<CalendarMonthViewDay {...day} {...calendar} />
))}
</CalendarMonthViewDays>
</CalendarMonthView>
</Calendar>
);
}
Issue Analytics
- State:
- Created 4 years ago
- Reactions:4
- Comments:12 (5 by maintainers)
I started working on an API based on @jtmthf’s ideas:
https://twitter.com/diegohaz/status/1295513457001988096
It’s a lot simpler and probably doesn’t address all the use cases brought up by @jtmthf (at least, not directly, but the user could probably achieve them with some customization), but I think it’s a good start.
It would use the Grid component underneath.
@Guria The calendar is a
Grid
component with datetime features. We’re going to have grid soon (I’m working on it). But this can be already made on top of Composite. There are some examples here: https://codesandbox.io/s/composite-gcqs2