-
-
Notifications
You must be signed in to change notification settings - Fork 735
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Display the day picker in an overlay #98
Comments
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:
it 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 |
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 😁 I plan to work on it on the next days... |
hi. modified your 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 😞 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. |
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 |
I think |
@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 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 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 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: Based on the code published in the comment above. See it in action on this demo page (scroll down): |
For anyone interested, |
@gpbl Looks good. |
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 |
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).
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. |
@halt-hammerzeit this applies to any format (that can be chosen with the |
@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 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, |
@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 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: 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... |
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.
The text was updated successfully, but these errors were encountered: