Skip to content
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

feat: Add i18n support to DatePicker component #990

Merged
merged 13 commits into from
Apr 20, 2021
Merged
29 changes: 24 additions & 5 deletions src/components/forms/DatePicker/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export const Calendar = ({
rangeDate,
setStatuses,
focusMode,
dayOfWeekTranslations,
dayOfWeekShortTranslations,
monthTranslations,
selectedDateTranslation,
}: {
date?: Date
selectedDate?: Date
Expand All @@ -61,6 +65,10 @@ export const Calendar = ({
rangeDate?: Date
setStatuses: (statuses: string[]) => void
focusMode: FocusMode
dayOfWeekTranslations?: string[]
dayOfWeekShortTranslations?: string[]
monthTranslations?: string[]
selectedDateTranslation?: string
}): React.ReactElement => {
const prevYearEl = useRef<HTMLButtonElement>(null)
const prevMonthEl = useRef<HTMLButtonElement>(null)
Expand Down Expand Up @@ -97,7 +105,9 @@ export const Calendar = ({
const focusedMonth = dateToDisplay.getMonth()
const focusedYear = dateToDisplay.getFullYear()

const monthLabel = MONTH_LABELS[parseInt(`${focusedMonth}`)]
let monthLabel = MONTH_LABELS[parseInt(`${focusedMonth}`)]
if (monthTranslations)
monthLabel = monthTranslations[parseInt(`${focusedMonth}`)]

useEffect(() => {
calendarWasHidden = false
Expand Down Expand Up @@ -137,8 +147,10 @@ export const Calendar = ({

if (calendarWasHidden) {
const newStatuses = [`${monthLabel} ${focusedYear}`]
if (selectedDate && isSameDay(focusedDate, selectedDate))
newStatuses.unshift('Selected date')
if (selectedDate && isSameDay(focusedDate, selectedDate)) {
const selectedDateText = selectedDateTranslation || 'Selected date'
newStatuses.unshift(selectedDateText)
}
setStatuses(newStatuses)
}
}, [dateToDisplay])
Expand All @@ -150,6 +162,7 @@ export const Calendar = ({
minDate={minDate}
maxDate={maxDate}
handleSelectMonth={handleSelectMonth}
monthTranslations={monthTranslations}
/>
)
} else if (mode === CalendarModes.YEAR_PICKER) {
Expand Down Expand Up @@ -325,11 +338,17 @@ export const Calendar = ({
withinRangeEndDate
)
}
dayOfWeekTranslations={dayOfWeekTranslations}
monthTranslations={monthTranslations}
/>
)
dateIterator = addDays(dateIterator, 1)
}

const dayOfWeekShortLabels =
dayOfWeekShortTranslations || DAY_OF_WEEK_SHORT_LABELS
const dayOfWeekLabels = dayOfWeekTranslations || DAY_OF_WEEK_LABELS

return (
// Ignoring error: "Static HTML elements with event handlers require a role."
// Ignoring because this element does not have a role in the USWDS implementation (https://github.com/uswds/uswds/blob/develop/src/js/components/date-picker.js#L1042)
Expand Down Expand Up @@ -413,11 +432,11 @@ export const Calendar = ({
<table className="usa-date-picker__calendar__table" role="presentation">
<thead>
<tr>
{DAY_OF_WEEK_SHORT_LABELS.map((d, i) => (
{dayOfWeekShortLabels.map((d, i) => (
<th
className="usa-date-picker__calendar__day-of-week"
scope="col"
aria-label={DAY_OF_WEEK_LABELS[parseInt(`${i}`)]}
aria-label={dayOfWeekLabels[parseInt(`${i}`)]}
key={`day-of-week-${d}-${i}`}>
{d}
</th>
Expand Down
31 changes: 31 additions & 0 deletions src/components/forms/DatePicker/DatePicker.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,34 @@ export const withRangeDate = (): React.ReactElement => (
rangeDate="2021-01-08"
/>
)

export const withLocalizations = (): React.ReactElement => (
<DatePicker
id="birthdate"
name="birthdate"
dayOfWeekTranslations={[
'domingo',
'lunes',
'martes',
'miércoles',
'jueves',
'viernes',
'sábado',
]}
dayOfWeekShortTranslations={['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa']}
monthTranslations={[
'enero',
'febrero',
'marzo',
'abril',
'mayo',
'junio',
'julio',
'agosto',
'septiembre',
'octubre',
'noviembre',
'diciembre',
]}
/>
)
42 changes: 42 additions & 0 deletions src/components/forms/DatePicker/DatePicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,48 @@ describe('DatePicker component', () => {
})
})

describe('with localization props', () => {
const esDaysAbbreviations = ['Do', 'Lu', 'Ma', 'Mi', 'Ju', 'Vi', 'Sa']
const esMonths = [
'enero',
'febrero',
'marzo',
'abril',
'mayo',
'junio',
'julio',
'agosto',
'septiembre',
'octubre',
'noviembre',
'diciembre',
]

it('displays abbreviated translations for days of the week', () => {
const { getByText, getByTestId } = render(
<DatePicker
{...testProps}
dayOfWeekShortTranslations={esDaysAbbreviations}
/>
)
userEvent.click(getByTestId('date-picker-button'))
esDaysAbbreviations.forEach((translation) => {
expect(getByText(translation)).toBeInTheDocument()
})
})
it('displays translation for month', () => {
const { getByText, getByTestId } = render(
<DatePicker
{...testProps}
monthTranslations={esMonths}
defaultValue="2020-02-01"
/>
)
userEvent.click(getByTestId('date-picker-button'))
expect(getByText('febrero')).toBeInTheDocument()
})
})

describe('selecting a date', () => {
it('clicking a date button selects that date and closes the calendar and focuses the external input', () => {
const mockOnChange = jest.fn()
Expand Down
19 changes: 17 additions & 2 deletions src/components/forms/DatePicker/DatePicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ interface DatePickerProps {
onBlur?: (
event: React.FocusEvent<HTMLInputElement> | React.FocusEvent<HTMLDivElement>
) => void
statusTranslations?: string[]
selectedDateTranslation?: string
dayOfWeekTranslations?: string[]
dayOfWeekShortTranslations?: string[]
monthTranslations?: string[]
}

export enum FocusMode {
Expand All @@ -59,6 +64,11 @@ export const DatePicker = (
rangeDate,
onChange,
onBlur,
statusTranslations,
selectedDateTranslation,
dayOfWeekTranslations,
dayOfWeekShortTranslations,
monthTranslations,
...inputProps
} = props

Expand Down Expand Up @@ -185,7 +195,7 @@ export const DatePicker = (
setCalendarDisplayValue(displayDate)
setCalendarPosY(datePickerEl?.current?.offsetHeight)

const statuses = [
const statuses = statusTranslations || [
'You can navigate by day using left and right arrows',
'Weeks by using up and down arrows',
'Months by using page up and page down keys',
Expand All @@ -195,7 +205,8 @@ export const DatePicker = (

const selectedDate = parseDateString(internalValue)
if (selectedDate && isSameDay(selectedDate, addDays(displayDate, 0))) {
statuses.unshift('Selected date')
const selectedDateText = selectedDateTranslation || 'Selected date'
statuses.unshift(selectedDateText)
}

setStatuses(statuses)
Expand Down Expand Up @@ -318,6 +329,10 @@ export const DatePicker = (
selectedDate={parseDateString(internalValue)}
setStatuses={setStatuses}
focusMode={focusMode}
dayOfWeekTranslations={dayOfWeekTranslations}
dayOfWeekShortTranslations={dayOfWeekShortTranslations}
monthTranslations={monthTranslations}
selectedDateTranslation={selectedDateTranslation}
/>
)}
</div>
Expand Down
12 changes: 10 additions & 2 deletions src/components/forms/DatePicker/Day.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const Day = forwardRef(
isRangeStart = false,
isRangeEnd = false,
isWithinRange = false,
dayOfWeekTranslations,
monthTranslations,
}: {
date: Date
onClick: (value: string) => void
Expand All @@ -38,6 +40,8 @@ export const Day = forwardRef(
isRangeStart?: boolean
isRangeEnd?: boolean
isWithinRange?: boolean
dayOfWeekTranslations?: string[]
monthTranslations?: string[]
},
ref: React.ForwardedRef<HTMLButtonElement>
): React.ReactElement => {
Expand All @@ -62,8 +66,12 @@ export const Day = forwardRef(
'usa-date-picker__calendar__date--within-range': isWithinRange,
})

const monthStr = MONTH_LABELS[parseInt(`${month}`)]
const dayStr = DAY_OF_WEEK_LABELS[parseInt(`${dayOfWeek}`)]
let monthStr = MONTH_LABELS[parseInt(`${month}`)]
if (monthTranslations) monthStr = monthTranslations[parseInt(`${month}`)]

let dayStr = DAY_OF_WEEK_LABELS[parseInt(`${dayOfWeek}`)]
if (dayOfWeekTranslations)
dayStr = dayOfWeekTranslations[parseInt(`${dayOfWeek}`)]

const handleClick = (): void => {
onClick(formattedDate)
Expand Down
30 changes: 30 additions & 0 deletions src/components/forms/DatePicker/MonthPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -212,4 +212,34 @@ describe('MonthPicker', () => {
expect(getByText('January')).toHaveFocus()
})
})

describe('with localization props', () => {
const esMonths = [
'enero',
'febrero',
'marzo',
'abril',
'mayo',
'junio',
'julio',
'agosto',
'septiembre',
'octubre',
'noviembre',
'diciembre',
]

it('displays month translations', () => {
const { getByText } = render(
<MonthPicker
{...testProps}
date={new Date('January 20 2021')}
monthTranslations={esMonths}
/>
)
esMonths.forEach((translation) => {
expect(getByText(translation)).toBeInTheDocument()
})
})
})
})
6 changes: 5 additions & 1 deletion src/components/forms/DatePicker/MonthPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ export const MonthPicker = ({
minDate,
maxDate,
handleSelectMonth,
monthTranslations,
}: {
date: Date
minDate: Date
maxDate?: Date
handleSelectMonth: (value: number) => void
monthTranslations?: string[]
}): React.ReactElement => {
const selectedMonth = date.getMonth()
const [monthToDisplay, setMonthToDisplay] = useState(selectedMonth)
Expand Down Expand Up @@ -92,7 +94,9 @@ export const MonthPicker = ({
event.preventDefault()
}

const months = MONTH_LABELS.map((month, index) => {
const monthNames = monthTranslations || MONTH_LABELS

const months = monthNames.map((month, index) => {
const monthToCheck = setMonth(date, index)
const isDisabled = isDatesMonthOutsideMinOrMax(
monthToCheck,
Expand Down