diff --git a/.size-limit b/.size-limit index d9b8e44aa..9ec95f421 100644 --- a/.size-limit +++ b/.size-limit @@ -3,6 +3,6 @@ name: "Fundamental-React Size", webpack: true, path: "lib/index.js", - limit: "185 KB" + limit: "190 KB" } ] diff --git a/package-lock.json b/package-lock.json index cef2771f3..913696bcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10249,8 +10249,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -10271,14 +10270,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -10293,20 +10290,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -10423,8 +10417,7 @@ "inherits": { "version": "2.0.4", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -10436,7 +10429,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -10451,7 +10443,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -10459,14 +10450,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.9.0", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -10485,7 +10474,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -10575,8 +10563,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -10588,7 +10575,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -10674,8 +10660,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -10711,7 +10696,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10731,7 +10715,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10775,14 +10758,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -10827,9 +10808,9 @@ "dev": true }, "fundamental-styles": { - "version": "0.8.0-rc.10", - "resolved": "https://registry.npmjs.org/fundamental-styles/-/fundamental-styles-0.8.0-rc.10.tgz", - "integrity": "sha512-v2psYypqNyCO/UzGZSM4Zhvx3zbBxWTbIDGpvITWxTKKqUC/4cNapDlvwr2mCNbzRAY8IPgX/kQ8LC8yRg5Izg==" + "version": "0.8.0-rc.16", + "resolved": "https://registry.npmjs.org/fundamental-styles/-/fundamental-styles-0.8.0-rc.16.tgz", + "integrity": "sha512-HO8R4YPVUs2Hq5qJ3WBzXfoZDH5n1iCvZhm0Mb/K7ndOxgypyyNSe5Z/M3JO5PpkTXYsksYmAhij/44Q/58nNA==" }, "fuse.js": { "version": "3.4.6", diff --git a/package.json b/package.json index 982d3f795..998ddd16f 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@babel/runtime": "^7.8.0", "chain-function": "^1.0.1", "classnames": "^2.2.6", - "fundamental-styles": "0.8.0-rc.10", + "fundamental-styles": "0.8.0-rc.16", "keycode": "^2.2.0", "moment": "^2.24.0", "prop-types": "^15.7.1", diff --git a/src/Calendar/Calendar.Component.js b/src/Calendar/Calendar.Component.js index cc61b308a..9e2fb2925 100644 --- a/src/Calendar/Calendar.Component.js +++ b/src/Calendar/Calendar.Component.js @@ -1,8 +1,21 @@ import { Calendar } from '../'; +import moment from 'moment'; import path from 'path'; import React from 'react'; import { ComponentPage, Example } from '../_playground'; +const tomorrow = moment().add(1, 'day').endOf('day').format('YYYYMMDD'); +const nextDay = moment().add(2, 'day').endOf('day').format('YYYYMMDD'); +const dayAfter = moment().add(3, 'day').endOf('day').format('YYYYMMDD'); +const oneWeek = moment().add(7, 'day').endOf('day').format('YYYYMMDD'); + +const specialDays = { + [tomorrow]: 1, + [nextDay]: 2, + [dayAfter]: 3, + [oneWeek]: 4 +}; + export const CalendarComponent = () => { return ( { disableWeekday={['Monday', 'Tuesday']} /> + + + + diff --git a/src/Calendar/Calendar.js b/src/Calendar/Calendar.js index ce48fd2e3..f7adbde85 100644 --- a/src/Calendar/Calendar.js +++ b/src/Calendar/Calendar.js @@ -35,7 +35,10 @@ class Calendar extends Component { arrSelectedDates: props.enableRangeSelection ? selectedDateOrDates : [], selectedDate: !props.enableRangeSelection ? selectedDateOrDates : null, showMonths: false, - showYears: false + showYears: false, + currentFocusDay: moment().startOf('day'), + currentFocusYear: currentDateDisplayed.year(), + currentFocusMonth: currentDateDisplayed.month() }; this.tableRef = React.createRef(); @@ -244,9 +247,11 @@ class Calendar extends Component { const monthCells = setOfMonths.map((month, subIndex) => { const shortenedNameMonth = moment.localeData(this.props.locale).monthsShort()[subIndex + (4 * index)]; const isSelected = months[this.state.currentDateDisplayed.month()] === month; + const isFocused = this.state.currentFocusMonth === month; const calendarItemClasses = classnames( 'fd-calendar__item', { + 'is-focus': isFocused, 'is-selected': isSelected, 'fd-calendar__item--current': months[this.state.todayDate.month()] === month } @@ -255,7 +260,8 @@ class Calendar extends Component { return ( this.changeMonth(month)}> + onClick={() => this.changeMonth(month)} + onFocus={this.handleMonthFocus(month)}> this.onKeyDownDay(e, this.changeMonth.bind(this, month))} role='button'> {shortenedNameMonth} @@ -297,9 +303,11 @@ class Calendar extends Component { const listOfYears = years.map((rowOfYears, index) => { const yearCells = rowOfYears.map(element => { const isSelected = this.state.currentDateDisplayed.year() === element; + const isFocused = this.state.currentFocusYear === element; const yearClasses = classnames( 'fd-calendar__item', { + 'is-focus': isFocused, 'is-selected': isSelected, 'fd-calendar__item--current': this.state.todayDate.year() === element } @@ -309,7 +317,8 @@ class Calendar extends Component { this.changeYear(element)}> + onClick={() => this.changeYear(element)} + onFocus={this.handleYearFocus(element)}> this.onKeyDownDay(e, this.changeYear.bind(this, element))} role='button'> {element} @@ -359,6 +368,18 @@ class Calendar extends Component { } } + handleDayFocus = date => () => { + this.setState({ currentFocusDay: date }); + } + + handleMonthFocus = month => () => { + this.setState({ currentFocusMonth: month }); + } + + handleYearFocus = year => () => { + this.setState({ currentFocusYear: year }); + } + dateClick = (day, isRangeEnabled) => { let selectedDates = []; if (typeof isRangeEnabled !== 'undefined' && isRangeEnabled) { @@ -397,6 +418,26 @@ class Calendar extends Component { return blockedDates[0].isBefore(date, 'day') && blockedDates[1].isAfter(date, 'day'); } + isWeekend = (date) => { + return [0, 6].includes(date.day()); + } + + isFocusedDay = (date) => { + return this.state.currentFocusDay.isSame(date); + } + + isFocusedMonth = (date) => { + return this.state.currentFocusDay.isSame(date); + } + + isFocusedYear = (date) => { + return this.state.currentFocusDay.isSame(date); + } + + specialDayType = (date) => { + return this.props.specialDays[date.format('YYYYMMDD')] ? this.props.specialDays[date.format('YYYYMMDD')] : null; + } + generateNavigation = () => { const months = moment.localeData(this.props.locale).months(); const previousButtonLabel = this.state.showYears ? @@ -459,8 +500,8 @@ class Calendar extends Component { for (let index = 0; index < 7; index++) { weekDays.push( - - + + {daysName[index]} ); @@ -493,6 +534,7 @@ class Calendar extends Component { const isDisabled = !isEnabledDate(day, this.props); const isBlocked = isDateBetween(day, blockedDates); const ariaLabel = copyDate.format(moment.localeData(this.props.locale).longDateFormat('LL')); + const specialDayType = this.specialDayType(day); if (isDisabled || isBlocked) { ariaLabel += ' ' + moment.localeData(this.props.locale).invalidDate(); } @@ -502,12 +544,13 @@ class Calendar extends Component { { 'fd-calendar__item--other-month': !day.isSame(currentDateDisplayed, 'month'), 'fd-calendar__item--current': todayDate.isSame(copyDate), - 'is-selected': this.isSelected(day), - 'is-selected-range-first': this.isSelectedRangeFirst(day), - 'is-selected-range-last': this.isSelectedRangeLast(day), - 'is-selected-range': this.isInSelectedRange(day), + 'fd-calendar__item--weekend': this.isWeekend(day), + 'fd-calendar__item--range': this.isInSelectedRange(day), + [`fd-calendar__special-day--${specialDayType}`]: !!specialDayType, + 'is-active': this.isSelected(day) || this.isSelectedRangeFirst(day) || this.isSelectedRangeLast(day), 'is-disabled': isDisabled, - 'is-blocked': isBlocked + 'is-blocked': isBlocked, + 'is-focus': this.isFocusedDay(day) } ); @@ -519,6 +562,7 @@ class Calendar extends Component { data-is-focused={day.isSame(currentDateDisplayed)} key={copyDate} onClick={isEnabledDate(day, this.props) ? () => this.dateClick(copyDate, enableRangeSelection) : null} + onFocus={this.handleDayFocus(day)} role='gridcell'> this.onKeyDownCalendar(e)}> - {this.generateNavigation()} -
- {this._renderContent(monthListProps, yearListProps, tableProps, tableHeaderProps, tableBodyProps)} + <> +
this.onKeyDownCalendar(e)}> + {this.generateNavigation()} +
+ {this._renderContent(monthListProps, yearListProps, tableProps, tableHeaderProps, tableBodyProps)} +
-
+
+ {localizedText.calendarInstructions} +
+ ); } @@ -620,11 +670,13 @@ Calendar.basePropTypes = { disableWeekday: PropTypes.arrayOf(PropTypes.string), disableWeekends: PropTypes.bool, localizedText: CustomPropTypes.i18n({ + calendarInstructions: PropTypes.string, nextMonth: PropTypes.string, previousMonth: PropTypes.string, show12NextYears: PropTypes.string, show12PreviousYears: PropTypes.string - }) + }), + specialDays: PropTypes.object }; Calendar.propTypes = { @@ -640,12 +692,14 @@ Calendar.propTypes = { Calendar.defaultProps = { locale: 'en', localizedText: { + calendarInstructions: 'Use arrow keys to move between dates.', nextMonth: 'Next month', previousMonth: 'Previous month', show12NextYears: 'Show 12 next years', show12PreviousYears: 'Show 12 previous years' }, - onChange: () => { } + onChange: () => { }, + specialDays: {} }; Calendar.propDescriptions = { @@ -659,12 +713,14 @@ Calendar.propDescriptions = { disableWeekends: 'Set to **true** to disables dates that match a weekend.', focusOnInit: 'Set to **true** to focus the calendar grid upon being mounted', localizedTextShape: { + calendarInstructions: 'Localized string informing screen reader users the calendar can be navigated by arrow keys.', nextMonth: 'aria-label for next button', previousMonth: 'aria-label for previous button', show12NextYears: 'aria-label for next button when years are displayed', show12PreviousYears: 'aria-label for previous button when years are displayed' }, monthListProps: 'Additional props to be spread to the month\'s `` element.', + specialDays: 'Object with special dates and special date types in shape of `{\'YYYYMMDD\': type}`. Type must be a number between 1-20.', tableBodyProps: 'Additional props to be spread to the `` element.', tableHeaderProps: 'Additional props to be spread to the `` element.', tableProps: 'Additional props to be spread to the `
` element.', diff --git a/src/Calendar/Calendar.test.js b/src/Calendar/Calendar.test.js index b80546b61..3c1966d36 100644 --- a/src/Calendar/Calendar.test.js +++ b/src/Calendar/Calendar.test.js @@ -416,7 +416,7 @@ describe('', () => { const element = mount(); expect( - element.getDOMNode().attributes['data-sample'].value + element.find('.fd-calendar').getDOMNode().attributes['data-sample'].value ).toBe('Sample'); }); diff --git a/src/DatePicker/DatePicker.test.js b/src/DatePicker/DatePicker.test.js index f4480b150..0976dc846 100644 --- a/src/DatePicker/DatePicker.test.js +++ b/src/DatePicker/DatePicker.test.js @@ -320,7 +320,7 @@ describe('', () => { const element = mount(); element.find('button.fd-button--transparent.sap-icon--calendar').simulate('click'); - element.find('.fd-calendar__text').at(1).simulate('click'); + element.find('.fd-calendar__text').at(8).simulate('click'); expect(datePickerClose).toHaveBeenCalledWith(expect.objectContaining({ formattedDate: '2020-03-02' })); expect(datePickerClose).toHaveBeenCalledTimes(1); }); @@ -346,7 +346,7 @@ describe('', () => { dateFormat: 'MM/DD/YYYY' }); element.find('button.fd-button--transparent.sap-icon--calendar').simulate('click'); - element.find('.fd-calendar__text').at(1).simulate('click'); + element.find('.fd-calendar__text').at(8).simulate('click'); expect(change).toHaveBeenCalledWith(expect.objectContaining({ formattedDate: '03/02/2020' })); });