Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
5245fa7
feat: gridmanager, intial arrow key movement
jacobdevera Jan 21, 2020
9f4eedd
feat: row and col wrapping
jacobdevera Jan 22, 2020
f67eac3
fix: enter and space handling
jacobdevera Jan 22, 2020
cb4f411
fix: button labels, navigation fixes
jacobdevera Jan 22, 2020
4d97ec7
fix: first focusable element
jacobdevera Jan 22, 2020
d3b5341
feat: skip disabled and block dates
jacobdevera Jan 23, 2020
e783f40
fix: grid boundary logic
jacobdevera Jan 23, 2020
53e19d4
fix: date labels
jacobdevera Jan 24, 2020
e0b8886
fix: focus on init logic
jacobdevera Jan 24, 2020
50803e1
feat: page up, page down, home, end handling
jacobdevera Jan 24, 2020
de77452
fix: header option, tests
jacobdevera Jan 24, 2020
59e78b3
chore: remove edit mode references
jacobdevera Jan 25, 2020
0870296
fix: datepicker state
jacobdevera Jan 25, 2020
0cc38fa
chore: cleanup
jacobdevera Jan 27, 2020
ba119fe
fix: disabled cells
jacobdevera Jan 27, 2020
daacb26
fix: test
jacobdevera Jan 27, 2020
42d1b1d
fix: prevent default
jacobdevera Jan 27, 2020
7bd09ea
fix: default for other keys
jacobdevera Jan 27, 2020
74631bb
Merge branch 'master' into feat/kb-calendar
jacobdevera Jan 27, 2020
44889b7
fix: page keys shouldn't select date
jacobdevera Jan 28, 2020
3dd52d7
fix: six weeks
jacobdevera Feb 11, 2020
c7fbf0a
Merge branch 'master' into feat/kb-calendar
jacobdevera Feb 11, 2020
2a2de97
fix: datepicker popover
jacobdevera Feb 11, 2020
0c3620e
fix: visual regression
jacobdevera Feb 11, 2020
0791a01
fix: button label, live area
jacobdevera Feb 12, 2020
ec9ce22
Merge branch 'master' into feat/kb-calendar
jacobdevera Feb 17, 2020
3c8d202
fix: merge
jacobdevera Feb 18, 2020
807d405
fix: element.matches check
jacobdevera Feb 18, 2020
c4f3840
fix: merge, null checks
jacobdevera Feb 18, 2020
6a466a4
fix: close after selecting
jacobdevera Feb 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
298 changes: 209 additions & 89 deletions src/Calendar/Calendar.js

Large diffs are not rendered by default.

3 changes: 0 additions & 3 deletions src/Calendar/Calendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,6 @@ describe('<Calendar />', () => {
const currentDateDisplayed = wrapper.state('currentDateDisplayed');

expect(currentDateDisplayed.month()).toEqual(3);

// check that first of month is selected
expect(currentDateDisplayed.date()).toEqual(1);
});

test('click month from list with date range', () => {
Expand Down
104 changes: 46 additions & 58 deletions src/DatePicker/DatePicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ import Button from '../Button/Button';
import Calendar from '../Calendar/Calendar';
import FormInput from '../Forms/FormInput';
import InputGroup from '../InputGroup/InputGroup';
import { isEnabledDate } from '../utils/dateUtils';
import moment from 'moment';
import Popover from '../Popover/Popover';
import PropTypes from 'prop-types';
import React, { Component } from 'react';

Expand All @@ -13,31 +15,15 @@ class DatePicker extends Component {
const formattedDate = props.defaultValue.length > 0 ?
moment(props.defaultValue, ISO_DATE_FORMAT).format(this.getLocaleDateFormat()) : '';
this.state = {
hidden: true,
selectedDate: formattedDate.length === 0 ? null : moment(formattedDate, this.getLocaleDateFormat()),
arrSelectedDates: [],
formattedDate
};

this.calendarRef = React.createRef();
this.popoverRef = React.createRef();
}

componentDidMount() {
if (!this.props.disableStyles) {
require('fundamental-styles/dist/popover.css');
}
}

openCalendar = type => {
if (type === 'input') {
if (this.state.hidden) {
this.setState({ hidden: !this.state.hidden });
}
} else {
this.setState({ hidden: !this.state.hidden });
}
};

modifyDate = (e) => {
this.setState({ formattedDate: e.target.value });
}
Expand All @@ -48,23 +34,8 @@ class DatePicker extends Component {
}
}

componentWillMount() {
document.addEventListener('mousedown', this.click, false);
}

componentWillUnmount() {
document.removeEventListener('mousedown', this.click, false);
}

click = e => {
if (this.component.contains(e.target)) {
return;
}
this.clickOutside();
};

isDateValid = (date) => {
return date.isValid() && this.calendarRef.current.isEnabledDate(date);
return date.isValid() && isEnabledDate(date, this.props);
}

validateDates = () => {
Expand Down Expand Up @@ -122,22 +93,30 @@ class DatePicker extends Component {

updateDate = (date) => {
const longDateFormat = this.getLocaleDateFormat();
let closeCalendar = false;

if (this.props.enableRangeSelection) {
let formatDate = date[0].format(longDateFormat);
if (!!date[1]) {
formatDate += '-' + date[1].format(longDateFormat);
closeCalendar = true;
}
this.setState({
arrSelectedDates: date,
formattedDate: formatDate
});
} else {
closeCalendar = true;
this.setState({
selectedDate: date,
formattedDate: date.format(longDateFormat)
});
}

if (closeCalendar) {
const popover = this.popoverRef && this.popoverRef.current;
popover && popover.handleEscapeKey();
}
}

getLocaleDateFormat = () => moment.localeData(this.props.locale).longDateFormat('L');
Expand All @@ -152,6 +131,7 @@ class DatePicker extends Component {
render() {
const {
blockedDates,
buttonLabel,
buttonProps,
className,
compact,
Expand All @@ -166,6 +146,7 @@ class DatePicker extends Component {
enableRangeSelection,
inputProps,
locale,
localizedText,
onBlur,
...props
} = this.props;
Expand All @@ -175,29 +156,8 @@ class DatePicker extends Component {
{...props}
className={className}
ref={component => (this.component = component)}>
<div className='fd-popover'>
<div className='fd-popover__control'>
<InputGroup compact={compact}>
<FormInput
{...inputProps}
onBlur={this._handleBlur}
onChange={this.modifyDate}
onClick={() => this.openCalendar('input')}
onKeyPress={this.sendUpdate}
placeholder={this.getLocaleDateFormat()}
value={this.state.formattedDate} />
<InputGroup.Addon isButton>
<Button {...buttonProps}
disableStyles={disableStyles}
glyph='calendar'
onClick={() => this.openCalendar()}
option='light' />
</InputGroup.Addon>
</InputGroup>
</div>
<div
aria-hidden={this.state.hidden}
className='fd-popover__body fd-popover__body--right fd-popover__body--no-arrow'>
<Popover
body={
<Calendar
blockedDates={blockedDates}
customDate={
Expand All @@ -214,11 +174,36 @@ class DatePicker extends Component {
disableWeekends={disableWeekends}
disabledDates={disabledDates}
enableRangeSelection={enableRangeSelection}
focusOnInit
locale={locale}
localizedText={localizedText}
onChange={this.updateDate}
ref={this.calendarRef} />
</div>
</div>
}
control={
<InputGroup compact={compact}>
<FormInput
{...inputProps}
onBlur={this._handleBlur}
onChange={this.modifyDate}
onKeyPress={this.sendUpdate}
placeholder={this.getLocaleDateFormat()}
value={this.state.formattedDate} />
<InputGroup.Addon isButton>
<Button {...buttonProps}
aria-label={buttonLabel}
disableStyles={disableStyles}
glyph='calendar'
option='light' />
</InputGroup.Addon>
</InputGroup>
}
disableKeyPressHandler
disableStyles={disableStyles}
noArrow
onClickOutside={this.clickOutside}
placement='bottom-end'
ref={this.popoverRef} />
</div>
);
}
Expand All @@ -228,6 +213,7 @@ DatePicker.displayName = 'DatePicker';

DatePicker.propTypes = {
...Calendar.basePropTypes,
buttonLabel: PropTypes.string,
buttonProps: PropTypes.object,
compact: PropTypes.bool,
defaultValue: PropTypes.string,
Expand All @@ -238,13 +224,15 @@ DatePicker.propTypes = {
};

DatePicker.defaultProps = {
buttonLabel: 'Choose date',
defaultValue: '',
locale: 'en',
onBlur: () => {}
};

DatePicker.propDescriptions = {
...Calendar.propDescriptions,
buttonLabel: 'aria-label for datepicker button',
defaultValue: 'Default value to be shown in the Datepicker. The only accepted format is the ISO format, i.e. YYYY-MM-DD',
enableRangeSelection: 'Set to **true** to enable the selection of a date range (begin and end).',
locale: 'Language code to set the locale.',
Expand Down
68 changes: 2 additions & 66 deletions src/DatePicker/DatePicker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,70 +25,6 @@ describe('<DatePicker />', () => {
mount(compactRangeDatepicker);
});

test('open/close by calendar icon button', () => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These essentially test opening/closing the calendar which should now be covered by using the Popover component itself.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔥

wrapper = mount(defaultDatePicker);
expect(wrapper.state('hidden')).toBeTruthy();

wrapper
.find('button.fd-button--light.sap-icon--calendar')
.simulate('click', { type: 'input' });

expect(wrapper.state('hidden')).toBeFalsy();
});

test('open/close calendar', () => {
wrapper = mount(defaultDatePicker);
// check to make sure calendar is hidden
expect(wrapper.state('hidden')).toBeTruthy();

// click to show calendar
wrapper.find('input[type="text"]').simulate('click', { type: 'input' });

// check to make sure calendar is shown
expect(wrapper.state('hidden')).toBeFalsy();

// clicking on input text should keep calendar displayed
wrapper.find('input[type="text"]').simulate('click', { type: 'input' });

// check to make sure calendar is shown
expect(wrapper.state('hidden')).toBeFalsy();

// click to show calendar
wrapper.find('input[type="text"]').simulate('click', { type: '' });

// check to make sure calendar is shown
expect(wrapper.state('hidden')).toBeFalsy();
});

test('open/close range calendar', () => {
wrapper = mount(rangeDatePicker);
//open date picker calendar
expect(wrapper.state('hidden')).toBeTruthy();

// click to show calendar
wrapper.find('input[type="text"]').simulate('click', { type: 'input' });

// check to make sure calendar is shown
expect(wrapper.state('hidden')).toBeFalsy();

wrapper.instance().componentWillMount();

// click on body element
let event = new MouseEvent('mousedown', {
target: document.querySelector('body')
});
document.dispatchEvent(event);

// check to make sure calendar is hidden
expect(wrapper.state('hidden')).toBeTruthy();

// show date picker, select date range then close
wrapper.find('input[type="text"]').simulate('click', { type: '' });

// check to make sure calendar is shown
expect(wrapper.state('hidden')).toBeFalsy();
});

test('start date and end date range', () => {
wrapper = mount(rangeDatePicker);
// set dates
Expand Down Expand Up @@ -306,7 +242,7 @@ describe('<DatePicker />', () => {
const blur = jest.fn();
const element = mount(<DatePicker onBlur={blur} />);

element.find('button.fd-button--light.sap-icon--calendar').simulate('click', { type: 'input' });
element.find('button.fd-button--light.sap-icon--calendar').simulate('click');

element.find('table.fd-calendar__table tbody.fd-calendar__group tr.fd-calendar__row td.fd-calendar__item:not(.fd-calendar__item--other-month)')
.at(0)
Expand All @@ -323,7 +259,7 @@ describe('<DatePicker />', () => {
const blur = jest.fn();
const element = mount(<DatePicker onBlur={blur} />);

element.find('input[type="text"]').simulate('click', { type: 'input' });
element.find('input[type="text"]').simulate('click');

let event = new MouseEvent('mousedown', { target: document.querySelector('body') });
document.dispatchEvent(event);
Expand Down
7 changes: 7 additions & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,10 @@ export const POPPER_SIZING_TYPES_DESCRIPTION = `<ul>
<li>"minTarget" - right edge aligns with target unless Popover content is bigger</li>
<li>"maxTarget" - right edge aligns with target unless Popover content is smaller</li>
</ul>`;

export const GridSelector = {
ROW: 'tr, [role="row"]',
HEADER: 'th',
CELL: 'td, [role="gridcell"]',
FOCUSABLE: 'input:enabled, select, textarea, a[href], button, [tabindex], [role="button"]'
};
46 changes: 46 additions & 0 deletions src/utils/dateUtils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import moment from 'moment';

export const isDateBetween = (date, blockedDates, isRangeEnabled) => {
if (typeof blockedDates === 'undefined' || typeof blockedDates[0] === 'undefined' || typeof blockedDates[1] === 'undefined') {
return false;
}
if (typeof isRangeEnabled !== 'undefined' || isRangeEnabled) {
if (blockedDates[0].isAfter(blockedDates[1])) {
return blockedDates[1].isBefore(date) && blockedDates[0].isAfter(date);
}
}
return blockedDates[0].isBefore(date, 'day') && blockedDates[1].isAfter(date, 'day');
};

export const isDisabledWeekday = (date, weekDays) => {
if (!weekDays) {
return false;
}

const daysName = moment.weekdays();

return weekDays.indexOf(daysName[date.day()]) > -1;
};

export const isEnabledDate = (day, dateProps) => {
const {
disableWeekends,
disableAfterDate,
disableBeforeDate,
blockedDates,
disableWeekday,
disablePastDates,
disableFutureDates,
disabledDates
} = dateProps;
return (
!isDisabledWeekday(day, disableWeekday) &&
!(disableWeekends && (day.day() === 0 || day.day() === 6)) &&
!(disableBeforeDate && day.isBefore(moment(disableBeforeDate))) &&
!(disableAfterDate && day.isAfter(moment(disableAfterDate))) &&
!(disablePastDates && day.isBefore(moment(), 'day')) &&
!(disableFutureDates && day.isAfter(moment(), 'day')) &&
!isDateBetween(day, blockedDates && blockedDates.map(date => moment(date))) &&
!isDateBetween(day, disabledDates && disabledDates.map(date => moment(date)))
);
};
Loading