From a91406a57701d37568f5d83c08cbd4b68524d7ee Mon Sep 17 00:00:00 2001 From: John Coburn Date: Tue, 29 Jun 2021 14:22:48 -0500 Subject: [PATCH 1/3] add static default region list, apply day offset to calendar --- .storybook/preview.js | 7 +- lib/Commander/keyboardShortcuts.js | 1 + lib/Datepicker/Calendar.js | 52 ++++++-- lib/Datepicker/staticLangCountryCodes.js | 151 +++++++++++++++++++++++ lib/Datepicker/stories/DatepickerDemo.js | 1 + 5 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 lib/Datepicker/staticLangCountryCodes.js diff --git a/.storybook/preview.js b/.storybook/preview.js index cbc9413c8..f01038725 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -22,6 +22,9 @@ import frTranslations from '../translations/stripes-components/fr.json'; import huTranslations from '../translations/stripes-components/hu.json'; import itTranslations from '../translations/stripes-components/it_IT.json'; import ptTranslations from '../translations/stripes-components/pt_BR.json'; +import ruTranslations from '../translations/stripes-components/ru.json'; +import svTranslations from '../translations/stripes-components/sv.json'; + // mimics the StripesTranslationPlugin in @folio/stripes-core function prefixKeys(obj) { @@ -44,11 +47,13 @@ const messages = { hu: prefixKeys(huTranslations), it: prefixKeys(itTranslations), pt: prefixKeys(ptTranslations), + ru: prefixKeys(ruTranslations), + sv: prefixKeys(svTranslations), }; // Set intl configuration setIntlConfig({ - locales: ['ar', 'ca', 'da', 'de', 'en', 'es', 'fr', 'hu', 'it', 'pt'], + locales: ['ar', 'ca', 'da', 'de', 'en', 'es', 'fr', 'hu', 'it', 'pt', 'ru', 'sv'], defaultLocale: 'en', getMessages: (locale) => messages[locale] }); diff --git a/lib/Commander/keyboardShortcuts.js b/lib/Commander/keyboardShortcuts.js index 693d705e4..499ae0efe 100644 --- a/lib/Commander/keyboardShortcuts.js +++ b/lib/Commander/keyboardShortcuts.js @@ -1,3 +1,4 @@ +import React from 'react'; import { FormattedMessage } from 'react-intl'; import mapValues from 'lodash/mapValues'; diff --git a/lib/Datepicker/Calendar.js b/lib/Datepicker/Calendar.js index df772b5fd..ee8b23ffb 100644 --- a/lib/Datepicker/Calendar.js +++ b/lib/Datepicker/Calendar.js @@ -16,10 +16,11 @@ import MonthSelect from './MonthSelect'; import css from './Calendar.css'; import staticFirstWeekday from './staticFirstWeekDay'; +import staticRegions from './staticLangCountryCodes'; const moment = extendMoment(Moment); -function getCalendar(year, month) { +function getCalendar(year, month, offset) { const startDate = moment([year, month]); const firstDay = moment(startDate).startOf('month'); const endDay = moment(startDate).endOf('month'); @@ -36,8 +37,8 @@ function getCalendar(year, month) { if (weeks.indexOf(ref) < 0) { weeks.push(mo.week()); const endClone = moment(mo); - rowStartArray.push(mo.weekday(0)); - rowEndArray.push(endClone.weekday(6)); + rowStartArray.push(mo.weekday(offset + 0)); + rowEndArray.push(endClone.weekday(offset + 6)); } }); @@ -119,7 +120,7 @@ class Calendar extends React.Component { constructor(props) { super(props); - moment.locale(this.props.locale); + moment.locale(this.props.intl.locale || this.props.locale); const { selectedDate, dateFormat } = this.props; @@ -135,6 +136,24 @@ class Calendar extends React.Component { cursorDate = new moment(); // eslint-disable-line new-cap } + // if the stripes locale has no region (only 2 letters), it needs to be normalized to a common form of language-region + // we adjust it by mapping from a set of common default regions (staticRegions); + // the first weekday in calendar rendering is derived from region. Hopefully it will be implemented in browser Intl API soon.. + // but until then... + let dayOffset = 0; + let adjustedLocale = props.intl.locale || props.locale; + if (adjustedLocale.length === 2) { + const regionDefault = staticRegions[adjustedLocale]; + adjustedLocale = `${adjustedLocale}-${regionDefault}`; + } + + // if moment doesn't have the requested locale from above (intl/stripes), it falls back to 'en'. If this + // is the case, we need to set an offset value for correct calendar day rendering - + // otherwise, the calendar columns will be off, resulting misaligned weekdays/calendar days. + if (moment.locale() === 'en') { + dayOffset = getFirstWeekday(adjustedLocale); + } + const base = new moment(cursorDate); // eslint-disable-line new-cap const month = base.month(); const year = base.year(); @@ -147,17 +166,22 @@ class Calendar extends React.Component { date: this.selectedMoment, month, year, - calendar: getCalendar(year, month) + calendar: getCalendar(year, month), + dayOffset, }; this.weekDays = getWeekDays(props.intl); this.months = getMonths(props.intl); - this.firstWeekDay = getFirstWeekday(props.intl.locale); + this.firstWeekDay = getFirstWeekday(adjustedLocale); this.firstField = props.firstFieldRef; this.calendarGrid = React.createRef(); } static getDerivedStateFromProps(nextProps, prevState) { + const { + dayOffset + } = prevState; + // When the selected date has changed, update the state with it let stateUpdate; if (nextProps.selectedDate !== prevState.selectedDate) { @@ -172,7 +196,7 @@ class Calendar extends React.Component { date: moDate, month, year, - calendar: getCalendar(year, month), + calendar: getCalendar(year, month, dayOffset), selectedDate: nextProps.selectedDate, dateFormat: nextProps.dateFormat, }; @@ -185,7 +209,7 @@ class Calendar extends React.Component { date: fallbackDate, month, year, - calendar: getCalendar(year, month), + calendar: getCalendar(year, month, dayOffset), selectedDate: nextProps.selectedDate, dateFormat: nextProps.dateFormat, }; @@ -195,7 +219,7 @@ class Calendar extends React.Component { if (stateUpdate) { const newState = prevState; Object.assign(newState, stateUpdate); - newState.calendar = getCalendar(newState.year, newState.month); + newState.calendar = getCalendar(newState.year, newState.month, dayOffset); return newState; } else { return null; @@ -271,7 +295,7 @@ class Calendar extends React.Component { newState.month = newState.cursorDate.month(); newState.year = newState.cursorDate.year(); if (newState.year !== oldState.year || newState.month !== oldState.month) { - newState.calendar = getCalendar(newState.year, newState.month); + newState.calendar = getCalendar(newState.year, newState.month, oldState.dayOffset); } return newState; }, () => { @@ -325,6 +349,7 @@ class Calendar extends React.Component { const month = parseInt(e.target.value, 10); this.setState(curState => { + const { dayOffset } = curState; let cursorDate = ''; if (month === this.selectedMoment?.month()) { cursorDate = this.selectedMoment; @@ -333,7 +358,7 @@ class Calendar extends React.Component { } return { month, - calendar: getCalendar(curState.year, month), + calendar: getCalendar(curState.year, month, dayOffset), cursorDate, }; }); @@ -345,7 +370,7 @@ class Calendar extends React.Component { if (new moment(year, 'YYYY', true).isValid()) { // eslint-disable-line new-cap this.setState(curState => ({ year, - calendar: getCalendar(year, curState.month), + calendar: getCalendar(year, curState.month, curState.dayOffset), })); } } @@ -359,6 +384,7 @@ class Calendar extends React.Component { moveDate = (op, unit) => { this.setState(curState => { + const { dayOffset } = curState; const newDate = curState.date[op](1, unit); const month = newDate.month(); const year = newDate.year(); @@ -366,7 +392,7 @@ class Calendar extends React.Component { date: newDate, month, year, - calendar: getCalendar(year, month) + calendar: getCalendar(year, month, dayOffset) }; }); } diff --git a/lib/Datepicker/staticLangCountryCodes.js b/lib/Datepicker/staticLangCountryCodes.js new file mode 100644 index 000000000..e6e7458f6 --- /dev/null +++ b/lib/Datepicker/staticLangCountryCodes.js @@ -0,0 +1,151 @@ +// Export a 'default' mapping using ISO-396-1 culture codes. +// This is used to convert 2-letter locales (language-only) to 4-letter locales. +// containing region information (used to derive first-day-of-week) + +export default { + af: 'ZA', + sq: 'AL', + gsw: 'FR', + am: 'ET', + ar: 'SA', + hy: 'AM', + as: 'IN', + az: 'AZ', + bn: 'IN', + ba: 'RU', + eu: 'ES', + be: 'BY', + bs: 'BA', + br: 'FR', + bg: 'BG', + my: 'MM', + ca: 'ES', + ku: 'IQ', + chr: 'US', + zh: 'CN', + co: 'FR', + hr: 'HR', + cs: 'CZ', + da: 'DK', + prs: 'AF', + dv: 'MV', + nl: 'NL', + dz: 'BT', + bin: 'NG', + en: 'US', + et: 'EE', + fo: 'FO', + fil: 'PH', + fi: 'FI', + fr: 'FR', + fy: 'NL', + ff: 'NG', + gl: 'ES', + ka: 'GE', + de: 'DE', + el: 'GR', + kl: 'GL', + gn: 'PY', + gu: 'IN', + ha: 'NG', + haw: 'US', + he: 'IL', + hi: 'IN', + hu: 'HU', + ibb: 'NG', + is: 'IS', + ig: 'NG', + id: 'ID', + iu: 'CA', + ga: 'IE', + xh: 'ZA', + zu: 'ZA', + it: 'IT', + ja: 'JP', + kn: 'IN', + kr: 'NG', + ks: 'IN', + kk: 'KZ', + km: 'KH', + quc: 'GT', + rw: 'RW', + sw: 'KE', + kok: 'IN', + ko: 'KR', + ky: 'KG', + lo: 'LA', + la: '001', + lv: 'LV', + lt: 'LT', + dsb: 'DE', + lb: 'LU', + mk: 'MK', + ms: 'MY', + ml: 'IN', + mt: 'MT', + mni: 'IN', + mi: 'NZ', + arn: 'CL', + mr: 'IN', + moh: 'CA', + mn: 'MN', + ne: 'NP', + nb: 'NO', + nn: 'NO', + oc: 'FR', + or: 'IN', + om: 'ET', + pap: '029', + ps: 'AF', + fa: 'IR', + pl: 'PL', + pt: 'PT', + pa: 'IN', + quz: 'BO', + ro: 'RO', + rm: 'CH', + ru: 'RU', + sah: 'RU', + smn: 'FI', + smj: 'SE', + se: 'NO', + sms: 'FI', + sma: 'SE', + sa: 'IN', + gd: 'GB', + sr: 'RS', + nso: 'ZA', + tn: 'ZA', + sd: 'PK', + si: 'LK', + sk: 'SK', + sl: 'SI', + so: 'SO', + st: 'ZA', + es: 'ES', + sv: 'SE', + syr: 'SY', + tg: 'TJ', + tzm: 'DZ', + ta: 'IN', + tt: 'RU', + te: 'IN', + th: 'TH', + bo: 'CN', + ti: 'ER', + ts: 'ZA', + tr: 'TR', + tk: 'TM', + uk: 'UA', + hsb: 'DE', + ur: 'PK', + ug: 'CN', + uz: 'UZ', + ve: 'ZA', + vi: 'VN', + cy: 'GB', + wo: 'SN', + ii: 'CN', + yi: '001', + yo: 'NG' +}; diff --git a/lib/Datepicker/stories/DatepickerDemo.js b/lib/Datepicker/stories/DatepickerDemo.js index d810781f9..3b57aebb5 100644 --- a/lib/Datepicker/stories/DatepickerDemo.js +++ b/lib/Datepicker/stories/DatepickerDemo.js @@ -49,6 +49,7 @@ class DatepickerDemo extends React.Component { label="Date" value={this.state.day1} onChange={(e) => { this.onChangeDate(e.target.value, 'day1'); }} + locale="en-SE" /> Date: Tue, 29 Jun 2021 14:27:21 -0500 Subject: [PATCH 2/3] remove locale from datepicker story, lint --- lib/Datepicker/Calendar.js | 9 ++++++--- lib/Datepicker/stories/DatepickerDemo.js | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/Datepicker/Calendar.js b/lib/Datepicker/Calendar.js index ee8b23ffb..f6469a8e0 100644 --- a/lib/Datepicker/Calendar.js +++ b/lib/Datepicker/Calendar.js @@ -136,10 +136,13 @@ class Calendar extends React.Component { cursorDate = new moment(); // eslint-disable-line new-cap } - // if the stripes locale has no region (only 2 letters), it needs to be normalized to a common form of language-region - // we adjust it by mapping from a set of common default regions (staticRegions); - // the first weekday in calendar rendering is derived from region. Hopefully it will be implemented in browser Intl API soon.. + // if the stripes locale has no region (only 2 letters), it needs to be normalized to a + // common form of {language}-{region} ex: "en-SE" (english spoken in Sweden) + // We adjust it by mapping from a set of common default regions (staticRegions); + // the first weekday in calendar rendering is derived from region. + // Hopefully it will be implemented in browser Intl API soon.. // but until then... + let dayOffset = 0; let adjustedLocale = props.intl.locale || props.locale; if (adjustedLocale.length === 2) { diff --git a/lib/Datepicker/stories/DatepickerDemo.js b/lib/Datepicker/stories/DatepickerDemo.js index 3b57aebb5..d810781f9 100644 --- a/lib/Datepicker/stories/DatepickerDemo.js +++ b/lib/Datepicker/stories/DatepickerDemo.js @@ -49,7 +49,6 @@ class DatepickerDemo extends React.Component { label="Date" value={this.state.day1} onChange={(e) => { this.onChangeDate(e.target.value, 'day1'); }} - locale="en-SE" /> Date: Thu, 8 Jul 2021 15:27:21 -0500 Subject: [PATCH 3/3] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b0d8ce638..6a832e0b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Add link icon. Refs STCOM-852. * `` add ability to focus component if content data is empty. Refs STCOM-851. * Expose getLocaleDateFormat Datepicker util. Refs STCOM-854. +* Fix issue with misaligned dates/weekdays in Datepicker Calendar. Refs STCOM-849 ## [9.2.0](https://github.com/folio-org/stripes-components/tree/v9.2.0) (2021-06-08) [Full Changelog](https://github.com/folio-org/stripes-components/compare/v9.1.0...v9.2.0)