A common use for a date picker displaying it when focusing on an input field, so it would be nice to have an example in the examples app showing how to implement it. The example may use react-overlays.
I recall @kblcuk talking about it in the gitter room, maybe he has already an example ready to use π
Pretty close to that -- in my case it's a button that displays date set; something like this
var datePicker = (<Popover id={'date-range-popover'}>
<a onClick={this.handleResetTouchTap}>Reset</a>
<DayPicker
onDayTouchTap={this.handleDayClick}
onDayClick={this.handleDayClick}
enableOutsideDays
/>
</Popover>);
return (<div>
<label htmlFor="date-selector">Timed to</label>
<OverlayTrigger trigger="click" rootClose placement="bottom" overlay={datePicker}>
<Button name="date-selector"
id="date-selector"
bsSize="xs"
>{this.state.dateLabel}
</Button>
</OverlayTrigger>
</div>);
I'll try to add an example at some point if I'll figure out how examples are done :)
Thanks @kblcuk π€
If you want to create a new example:
EXAMPLES arrayit should appear in the example site:
cd examples
npm install
npm start
open http://localhost:3000
(not sure if it will work with any node environment: I use node 5 on OSX)
@kblcuk I think the difficult part with this implementation is having the day picker disappear when clicking outside it β while it should stay visible when clicking inside it (e.g. to navigate between months).
I remember I had to fight _enough_ for making it working this way, using timeouts and attaching events to the body. It wasn't that easy. My understanding is that react-overlays may help.
Hm, I had similar issue related to react-bootstrap dropdown & input, but not with the date picker: https://gitter.im/react-bootstrap/react-bootstrap?at=5609585ba5b78d0e12a3e009
(Aside: also #100)
+1 I too would like to see an example of an overlay triggered through input + button combo. It is amazing how flexible and well written this package is. thank you.
considering to switch from http://jquense.github.io/react-widgets/docs/#/datetime-picker and would be great to see how easy it to have a drop down.
Thanks @bsr203! I _hope_ it will be easy :grin: I plan to work on it on the next days...
hi. modified your selectableDay example, and it worked like a charm. It need better styling and stuff, but the integration was trivial. we may need to extend it with an input box and an iconed button. but, happy that this library is easily adaptable. thanks again for your hard work. cheers.
import React from "react";
import {findDOMNode} from "react-dom";
import DayPicker, { DateUtils } from "react-day-picker";
import {Overlay} from "react-overlays";
import "react-day-picker/lib/style.css";
export default class OverlayPicker extends React.Component {
state = {
show: false,
selectedDay: null
}
handleDayClick(e, day, modifiers) {
this.setState({
selectedDay: modifiers.indexOf("selected") > -1 ? null : day
});
}
toggle() {
return this.setState({ show: !this.state.show });
}
render() {
const { selectedDay } = this.state;
return (
<div style={{position : "relative"}}>
<p>
Selected: { selectedDay && selectedDay.toLocaleDateString() }
</p>
<div>
<button ref="target" onClick={this.toggle.bind(this)}>
picker
</button>
<Overlay
show={this.state.show}
onHide={() => this.setState({ show: false })}
placement={this.state.placement}
container={this}
rootClose
target={ () => findDOMNode(this.refs.target)}
>
<DayPicker
style={{position : "absolute"}}
modifiers={{
selected: day => DateUtils.isSameDay(selectedDay, day)
}}
onDayClick={ this.handleDayClick.bind(this) }
/>
</Overlay>
</div>
</div>
);
}
}
Thanks @bsr203, looking great, and easy as I hoped π Can't wait to try it and add your example to the others. Just have some patience, as I'm a bit busy these days...
sure no problem. I just wanted to validate it, and glad to know that it works quite well.
One thing missing for me is to support a time picker too (as in here ) Do you strip off the time component, or is it possible to extend this library to have a time picker. thank you.
I was playing with react-overlays but it doesn't help to display (or hide) the overlay when focusing (or blurring) a text input :disappointed: Which is actually the goal of this issue.
There's an OverlayTrigger component but it is included in react-bootstrap: I'd prefer to avoid this big dependency. I've found pui-react-overlay-trigger which may help, need to try.
@bsr203 I don't plan to add a time picker, it goes out from the scope of this component.
I'm not actually using an overlay, just simple CSS (absolute daypicker; relative parent). Does that count? The only problem we had was unable to hide it when we focus on the DayPicker (which is why I sent #122); #123 fixes it.
I can submit an example if that counts.
Hey, just wanted to drop a note saying that the day picker works wonderfully well with react-simple-dropdown when the use-case is to click on a button to show the day picker π It was really quick and smooth to implement, simply dropping DayPicker inside DropdownContent (the component meant to contain the dropdown's contents); only had to make sure that clicks on DropdownContent didn't bubble since sometimes click targets are removed from the DOM (e.g. http://cl.ly/0u0q0Y250x2d), and react-simple-dropdown checks where the event target is in the DOM to decide whether to hide the dropdown's contents or not :)
I have a solution which no need to use another library. Just show date picker when we focus on input, and detect when we click in outside of input and date picker to hide it. It's work for me. Hope it helpful for someone :)
Here is source code:
import React from 'react';
import moment from 'moment';
import DayPicker, { DateUtils } from 'react-day-picker';
import 'react-day-picker/lib/style.css';
export default class InputField extends React.Component {
constructor(props) {
super(props);
this.handleDayClick = this.handleDayClick.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.showCurrentDate = this.showCurrentDate.bind(this);
this.handleFocus = this.handleFocus.bind(this); // handle focus on input
this.handleClick = this.handleClick.bind(this); // handle click event in document
this.state = {
value: moment().format('L'), // The value of the input field
month: new Date(), // The month to display in the calendar
show: false // The show to show/hide calander
};
}
showCurrentDate() {
this.refs.daypicker.showMonth(this.state.month);
}
handleInputChange(e) {
const { value } = e.target;
// Change the current month only if the value entered by the user
// is a valid date, according to the `L` format
if (moment(value, 'L', true).isValid()) {
this.setState({
month: moment(value, 'L').toDate(),
value,
}, this.showCurrentDate);
} else {
this.setState({ value }, this.showCurrentDate);
}
}
handleDayClick(e, day) {
this.setState({
value: moment(day).format('L'),
month: day,
});
}
handleFocus() {
this.showCurrentDate();
this.setState(
show: true
);
}
componentWillMount() {
document.addEventListener('click', this.handleClick, false);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClick, false);
}
handleClick(event) {
// detect where click event has occured
// if click on input or calendar (date picker wrapper) -> no need to hide date picker
// if click on out side element -> set state show = false -> to hide date piker
if (this.refs.input.contains(event.target) || this.refs.calendar.contains(event.target)) {
console.log('click inside');
} else {
console.log('click outside');
this.setState({
show: false
});
}
}
render() {
const selectedDay = moment(this.state.value, 'L', true).toDate();
return (
<div>
<p>
<input
ref="input"
type="text"
value={this.state.value}
placeholder="YYYY-MM-DD"
onChange={this.handleInputChange}
onFocus={this.handleFocus}
/>
</p>
<div ref="calendar" style={this.state.show ? {} : { display: 'none' }}>
<DayPicker
ref="daypicker"
initialMonth={this.state.month}
selectedDays={day => DateUtils.isSameDay(selectedDay, day)}
onDayClick={this.handleDayClick}
/>
</div>
</div>
);
}
}
Thanks @huonghk I'll make an example with your code so we can have a starting point! However how does it work with the blur event? It seems to me the layer would stay open until the calendar is clicked - right?
I think blur event can't resolve this problem. Imagine you set blur event for the Input to hide the Calendar. So it will work like this: when you try to move your mouse from the Input to the Calendar in order to select date, it will trigger blur event and hide Calendar immediately, and you have no way to select date. That's why in my solution above, I have to detect click event occurred in somewhere as long as is outside of the Input and the Calendar.
@huonghk yes this is the reason why this issue is still open :) I am looking for a way to hide the date picker on blur.
I believe your solution doesn't work when navigating the form via keyboard, right? When the input field is focused and the user presses the tab key, the calendar won't hide.
A solution that could working "meh" is to set a timeout when the calendar is clicked or focused, but I haven't had yet the time to try it..
I've been pretty busy lately, (worse, I haven't re-read the new updates in this thread. Pardon me π ).
IIRC, I simply checked if the focused element is either the date picker or the input. I also used react-gateway to mount (aka sub render) the whole thing. It worked pretty great.
Gist:
import React, {Component} from 'react';
import {findDOMNode} from 'react-dom';
import {Gateway} from 'react-gateway';
import DayPicker from 'react-day-picker';
export default class CalendarInput extends Component {
state = {
open: false
};
componentDidMount() {
document.addEventListener('click', this.handleClick);
}
componentWillUnmount() {
document.removeEventListener('click', this.handleClick);
}
render() {
return (
<div>
<input type="text" ref="input">
<Gateway>
{this.state.open ? <DayPicker ref="calendar" /> : null}
</Gateway>
</div>
);
}
handleClick = (evt) => {
const {open} = this.state;
const {calendar, input} = this.refs;
const active = document.activeElement;
if ( !open && active === input ) {
this.setState({ open: true });
}
else if ( open && (active === input || active === findDOMNode(calendar)) ) {
this.setState({ open: false });
}
}
}
I haven't verified if it's working. I just came up with this based on the pseudocode above.
One issue I can think of is the click handler executes before any value is assigned to activeElement (the focused element). But it shouldn't be hard to fix if ever.
Edit: My bad. I just re-read the whole thing, and the problem seems to be with the keyboard navigation. I've never attempted to solve this in the past since we used the day picker in a desktop touch screen.
Any updates on this?
@frandsaw work has been merged and published at http://www.gpbl.org/react-day-picker/examples/?overlay. The solution is a bit more complex from @srph's one, but IMHO it works better with keyboard's focus and blur events.
I think the example should be provided to be reused easily in new component, to be included in this library (see #213).
Thanks everybody for your invaluable contributions to this issue β€οΈ
I'm adding my 2 cents regarding this issue:
https://github.com/halt-hammerzeit/react-responsive-ui/blob/master/source/date%20picker.js
Based on the code published in the comment above.
See it in action on this demo page (scroll down):
https://halt-hammerzeit.github.io/react-responsive-ui/
For anyone interested, v5.5 includes a new DayPickerInput component that renders an input field interacting with the day picker in an overlay http://react-day-picker.js.org/examples/?input π
@gpbl Looks good.
Requires moment bundled (which is quite big) but still it's a good component.
Btw, when I enter "fasdfasdfas" in the input and then focus out it doesn't restore the previously selected date.
I guess that's because the example uses the LL format.
As for me, I ended up implementing the date parsing and formatting functions myself so that I don't bundle moment.js which is > 100 KiloBytes.
function format_date_custom(date, format)
{
if (!(date instanceof Date))
{
return
}
const day = date.getDate()
const month = date.getMonth() + 1
const year = date.getFullYear()
let text = format
.replace('DD', pad_with_zeroes(String(day), 2))
.replace('MM', pad_with_zeroes(String(month), 2))
if (text.indexOf('YYYY') >= 0)
{
return text.replace('YYYY', pad_with_zeroes(String(year), 4))
}
if (text.indexOf('YY') >= 0)
{
return text.replace('YY', pad_with_zeroes(String(year % 100), 2))
}
}
function parse_date_custom(string, format)
{
if (!string)
{
return
}
let year = extract(string, format, 'YYYY')
if (year === undefined)
{
year = extract(string, format, 'YY')
if (year !== undefined)
{
const current_year = new Date().getFullYear()
const current_year_century = current_year - current_year % 100
year += current_year_century
}
}
const month = extract(string, format, 'MM')
const day = extract(string, format, 'DD')
if (year === undefined || month === undefined || day === undefined)
{
return console.error(`Couldn't parse date, perhaps an unsupported format: ${format}. Only DD, MM, YY and YYYY are supported.`)
}
const date = new Date
(
year,
month - 1,
day
)
// If `new Date()` returns "Invalid Date"
// (sometimes it does)
if (isNaN(date.getTime()))
{
return
}
return date
}
// + a couple of utility methods, see `react-responsive-ui`
Still a lot of code but nowhere near the size of moment.
Thanks @halt-hammerzeit for the valid feedback!
I'm also not so happy about moment.js size. However, parsing a date from an input field that could be written in multiple language and/or in many formats is not easily done. We could use date-fns in the future instead. (here the idea of modularizing moment.js but it seems won't be done soon).
Btw, when I enter "fasdfasdfas" in the input and then focus out it doesn't restore the previously selected date.
Curious, why should it?
@gpbl Well, most of the users would assume the text field allows editing the date, but since it's in "LL" format there's now way really to parse it so maybe it shouldn't parse it on focus out but still it shouldn't retain any changes then.
I mean, a "text field" concept is "editing" so it should be a button maybe, but a button would look weird.
So, my point is that "LL" is not the best format for a date input field.
If it was, say, "DD.MM.YYYY" then a pro user could easily enter dates manually and they would get parsed on focus out (const m = moment(value, format, true);).
@halt-hammerzeit this applies to any format (that can be chosen with the format prop): the input is designed to allow the user type the date _or_ using the day picker. I've used that format in the example because it's the requirement for the app I'm working on :)
@gpbl - I personally think that you should de-couple the overlay code from the datepicker and have a separate dependency for another positioning lib like Popper which is a very strong one.
the reason is that it is highly probably developers already have a positioning script within their project and do not but the added bloat of having another positioning lib within react-day-picker itself (that handles the overlays) which i'm sure has a fair amount of code written there.
There should be a separate single-source positioning library for each front-end project and each 3rd party script should theoretically use the same 3rd-party position script.
it's highly wasteful for a project to install many packages, each having its own positioning script,
and I don't see a way to import react-day-picker without including it's _positioning-system_ script along the way.
@yairEO thanks for your feedback! I agree with you β but what should be the alternative? Write our own positioning system?
The alternative would to write in the installation instructions that if a developer wishes to show the datepicker as an overlay, then they should also install [X,Y,Z... you chosose] and then import your scripts and the position script as well, and you can provide an example of how to wrap your date picker in an overlay such as popper or react-overlays as someone had mention in the comments.
Personally i think popper is the best choice because it's the most powerful positioning system I know of and the code is in very high standards.
I am probably actually going to do this now:
A button will open an overlay which will render a date-picker component and also a pre-defined date ranges, such as "last 24h", "last month", "last 6 months" ans so on, and the user could click on those or choose their own custom date range via your script. All this will be within the overlay Popper will show.
I personally really do not want an input element to trigger an overlay, like the one you show in the demo page. why must an input field trigger the overlay? why not a button? anything else? I don't even need or want an input field in my html...
@yairEO you are right in many points. Thinking to move the input component to another package. https://github.com/gpbl/react-day-picker/issues/794
I donβt think it promotes a good UI pattern for the web. I know browser vendors failed to implement a picker so connected to an input field.
Most helpful comment
I have a solution which no need to use another library. Just show date picker when we focus on input, and detect when we click in outside of input and date picker to hide it. It's work for me. Hope it helpful for someone :)
Here is source code: