Skip to content

Commit

Permalink
Merge branch 'main' of github.com:adobe/react-spectrum into calendar-…
Browse files Browse the repository at this point in the history
…cleanup

# Conflicts:
#	packages/@react-aria/calendar/src/types.ts
#	packages/@react-aria/calendar/src/useCalendarBase.ts
#	packages/@react-aria/calendar/src/useCalendarGrid.ts
#	packages/@react-aria/calendar/src/useRangeCalendar.ts
#	packages/@react-spectrum/calendar/src/CalendarBase.tsx
#	packages/@react-spectrum/calendar/src/CalendarMonth.tsx
#	packages/@react-spectrum/calendar/test/CalendarBase.test.js
  • Loading branch information
devongovett committed Apr 21, 2022
2 parents 9b99c24 + 86ac34a commit 2b7bef1
Show file tree
Hide file tree
Showing 16 changed files with 175 additions and 231 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,10 +166,6 @@ governing permissions and limitations under the License.

cursor: default;

transition: background var(--spectrum-global-animation-duration-100) ease-in-out,
color var(--spectrum-global-animation-duration-100) ease-in-out,
border-color var(--spectrum-global-animation-duration-100) ease-in-out;

/* compute the "auto" margin to center the date text manually rather than using the keyword to ensure consistent rounding. */
--margin: calc((100% - var(--spectrum-calendar-day-width)) / 2);

Expand Down Expand Up @@ -326,7 +322,3 @@ governing permissions and limitations under the License.
}
}
}

.spectrum-Calendar-body.is-range-selecting .spectrum-Calendar-date {
transition: none;
}
25 changes: 8 additions & 17 deletions packages/@react-aria/calendar/docs/useCalendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ function Calendar(props) {
return (
<div {...calendarProps} ref={ref} className="calendar">
<div className="header">
<Button {...prevButtonProps}>&lt;</Button>
<h2>{title}</h2>
<Button {...prevButtonProps}>&lt;</Button>
<Button {...nextButtonProps}>&gt;</Button>
</div>
<CalendarGrid state={state} />
Expand All @@ -140,12 +140,11 @@ The `CalendarGrid` component will be responsible for rendering an individual mon

```tsx example render=false export=true
import {useCalendarGrid} from '@react-aria/calendar';
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {startOfWeek, getWeeksInMonth} from '@internationalized/date';

function CalendarGrid({state, ...props}) {
let {locale} = useLocale();
let {gridProps, weekDays} = useCalendarGrid(props, state);
let {gridProps, headerProps, weekDays} = useCalendarGrid(props, state);

// Find the start date of the grid, which is the beginning
// of the week the month starts in. Also get the number of
Expand All @@ -155,20 +154,11 @@ function CalendarGrid({state, ...props}) {

return (
<table {...gridProps}>
<thead>
<thead {...headerProps}>
<tr>
{weekDays.map((day, index) => {
return (
<th key={index}>
{/* Make sure screen readers read the full day name,
but we show an abbreviation visually. */}
<VisuallyHidden>{day.long}</VisuallyHidden>
<span aria-hidden="true">
{day.narrow}
</span>
</th>
);
})}
{weekDays.map((day, index) =>
<th key={index}>{day}</th>
)}
</tr>
</thead>
<tbody>
Expand Down Expand Up @@ -240,12 +230,13 @@ That's it! Now we can render an example of our `Calendar` component in action.
.header {
display: flex;
align-items: center;
gap: 4px;
margin: 0 8px;
}

.header h2 {
flex: 1;
margin: 0;
text-align: center;
}

.calendar table {
Expand Down
29 changes: 10 additions & 19 deletions packages/@react-aria/calendar/docs/useRangeCalendar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ function RangeCalendar(props) {
return (
<div {...calendarProps} ref={ref} className="calendar">
<div className="header">
<Button {...prevButtonProps}>&lt;</Button>
<h2>{title}</h2>
<Button {...prevButtonProps}>&lt;</Button>
<Button {...nextButtonProps}>&gt;</Button>
</div>
<CalendarGrid state={state} />
Expand All @@ -140,35 +140,25 @@ The `CalendarGrid` component will be responsible for rendering an individual mon

```tsx example render=false export=true
import {useCalendarGrid} from '@react-aria/calendar';
import {VisuallyHidden} from '@react-aria/visually-hidden';
import {startOfWeek, getWeeksInMonth} from '@internationalized/date';

function CalendarGrid({state, ...props}) {
let {locale} = useLocale();
let {gridProps, weekDays} = useCalendarGrid(props, state);
let {gridProps, headerProps, weekDays} = useCalendarGrid(props, state);

// Find the start date of the grid, which is the beginning
// of the week the month starts in. Also get the number of
// weeks in the month so we can render the proper number of rows.
let startDate = startOfWeek(state.visibleRange.start, locale);
let monthStart = startOfWeek(state.visibleRange.start, locale);
let weeksInMonth = getWeeksInMonth(state.visibleRange.start, locale);

return (
<table {...gridProps}>
<thead>
<thead {...headerProps}>
<tr>
{weekDays.map((day, index) => {
return (
<th key={index}>
{/* Make sure screen readers read the full day name,
but we show an abbreviation visually. */}
<VisuallyHidden>{day.long}</VisuallyHidden>
<span aria-hidden="true">
{day.narrow}
</span>
</th>
);
})}
{weekDays.map((day, index) =>
<th key={index}>{day}</th>
)}
</tr>
</thead>
<tbody>
Expand All @@ -178,7 +168,7 @@ function CalendarGrid({state, ...props}) {
<CalendarCell
key={dayIndex}
state={state}
date={startDate.add({weeks: weekIndex, days: dayIndex})} />
date={monthStart.add({weeks: weekIndex, days: dayIndex})} />
))}
</tr>
))}
Expand Down Expand Up @@ -240,12 +230,13 @@ That's it! Now we can render an example of our `RangeCalendar` component in acti
.header {
display: flex;
align-items: center;
gap: 4px;
margin: 0 8px;
}

.header h2 {
flex: 1;
margin: 0;
text-align: center;
}

.calendar table {
Expand Down
29 changes: 14 additions & 15 deletions packages/@react-aria/calendar/src/useCalendarBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@

import {announce} from '@react-aria/live-announcer';
import {AriaButtonProps} from '@react-types/button';
import {calendarIds, useSelectedDateDescription, useVisibleRangeDescription} from './utils';
import {CalendarPropsBase} from '@react-types/calendar';
import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
import {DOMProps} from '@react-types/shared';
import {filterDOMProps, mergeProps, useDescription, useId, useSlotId, useUpdateEffect} from '@react-aria/utils';
import {filterDOMProps, mergeProps, useLabels, useSlotId, useUpdateEffect} from '@react-aria/utils';
import {hookData, useSelectedDateDescription, useVisibleRangeDescription} from './utils';
import {HTMLAttributes, useRef} from 'react';
// @ts-ignore
import intlMessages from '../intl/*.json';
Expand All @@ -37,7 +37,6 @@ export interface CalendarAria {

export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: CalendarState | RangeCalendarState): CalendarAria {
let formatMessage = useMessageFormatter(intlMessages);
let calendarId = useId(props.id);
let domProps = filterDOMProps(props);

let title = useVisibleRangeDescription(state.visibleRange.start, state.visibleRange.end, state.timeZone, false);
Expand All @@ -60,12 +59,12 @@ export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: Cale
// handle an update to the caption that describes the currently selected range, to announce the new value
}, [selectedDateDescription]);

let descriptionProps = useDescription(visibleRangeDescription);
let errorMessageId = useSlotId([Boolean(props.errorMessage), props.validationState]);

// Label the child grid elements by the group element if it is labelled.
calendarIds.set(state, {
calendarId: props['aria-label'] || props['aria-labelledby'] ? calendarId : null,
// Pass the label to the child grid elements.
hookData.set(state, {
ariaLabel: props['aria-label'],
ariaLabelledBy: props['aria-labelledby'],
errorMessageId
});

Expand All @@ -84,16 +83,16 @@ export function useCalendarBase(props: CalendarPropsBase & DOMProps, state: Cale
state.setFocused(true);
}

let labelProps = useLabels({
id: props['id'],
'aria-label': [props['aria-label'], visibleRangeDescription].filter(Boolean).join(', '),
'aria-labelledby': props['aria-labelledby']
});

return {
calendarProps: mergeProps(domProps, {
calendarProps: mergeProps(domProps, labelProps, {
role: 'group',
id: calendarId,
'aria-label': props['aria-label'],
'aria-labelledby': props['aria-labelledby'],
'aria-describedby': [
props['aria-describedby'],
descriptionProps['aria-describedby']
].filter(Boolean).join(' ') || undefined
'aria-describedby': props['aria-describedby'] || undefined
}),
nextButtonProps: {
onPress: () => state.focusNextPage(),
Expand Down
4 changes: 2 additions & 2 deletions packages/@react-aria/calendar/src/useCalendarCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@
*/

import {CalendarDate, isEqualDay, isSameDay, isToday} from '@internationalized/date';
import {calendarIds} from './utils';
import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
import {focusWithoutScrolling, useDescription} from '@react-aria/utils';
import {getInteractionModality, usePress} from '@react-aria/interactions';
import {hookData} from './utils';
import {HTMLAttributes, RefObject, useEffect, useMemo, useRef} from 'react';
// @ts-ignore
import intlMessages from '../intl/*.json';
Expand Down Expand Up @@ -286,7 +286,7 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
});

let formattedDate = useMemo(() => cellDateFormatter.format(nativeDate), [cellDateFormatter, nativeDate]);
let {errorMessageId} = calendarIds.get(state);
let {errorMessageId} = hookData.get(state);

return {
cellProps: {
Expand Down
36 changes: 15 additions & 21 deletions packages/@react-aria/calendar/src/useCalendarGrid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
*/

import {CalendarDate, startOfWeek} from '@internationalized/date';
import {calendarIds, useSelectedDateDescription, useVisibleRangeDescription} from './utils';
import {CalendarState, RangeCalendarState} from '@react-stately/calendar';
import {hookData, useSelectedDateDescription, useVisibleRangeDescription} from './utils';
import {HTMLAttributes, KeyboardEvent, useMemo} from 'react';
import {mergeProps, useDescription, useLabels} from '@react-aria/utils';
import {useDateFormatter, useLocale} from '@react-aria/i18n';
Expand All @@ -35,15 +35,10 @@ export interface AriaCalendarGridProps {
export interface CalendarGridAria {
/** Props for the date grid element (e.g. `<table>`). */
gridProps: HTMLAttributes<HTMLElement>,
/** A list of week days formatted for the current locale, typically used in column headers. */
weekDays: WeekDay[]
}

interface WeekDay {
/** A short name (e.g. single letter) for the day. */
narrow: string,
/** The full day name. If not displayed visually, it should be used as the accessiblity name. */
long: string
/** Props for the grid header element (e.g. `<thead>`). */
headerProps: HTMLAttributes<HTMLElement>,
/** A list of week day abbreviations formatted for the current locale, typically used in column headers. */
weekDays: string[]
}

/**
Expand Down Expand Up @@ -120,28 +115,22 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta
let descriptionProps = useDescription(selectedDateDescription);
let visibleRangeDescription = useVisibleRangeDescription(startDate, endDate, state.timeZone, true);

let {calendarId, errorMessageId} = calendarIds.get(state);
let {ariaLabel, ariaLabelledBy, errorMessageId} = hookData.get(state);
let labelProps = useLabels({
'aria-label': visibleRangeDescription,
'aria-labelledby': calendarId
'aria-label': [ariaLabel, visibleRangeDescription].filter(Boolean).join(', '),
'aria-labelledby': ariaLabelledBy
});

let dayFormatter = useDateFormatter({weekday: 'narrow', timeZone: state.timeZone});
let dayFormatterLong = useDateFormatter({weekday: 'long', timeZone: state.timeZone});
let {locale} = useLocale();
let weekDays = useMemo(() => {
let weekStart = startOfWeek(state.visibleRange.start, locale);
return [...new Array(7).keys()].map((index) => {
let date = weekStart.add({days: index});
let dateDay = date.toDate(state.timeZone);
let narrow = dayFormatter.format(dateDay);
let long = dayFormatterLong.format(dateDay);
return {
narrow,
long
};
return dayFormatter.format(dateDay);
});
}, [state.visibleRange.start, locale, state.timeZone, dayFormatter, dayFormatterLong]);
}, [state.visibleRange.start, locale, state.timeZone, dayFormatter]);

return {
gridProps: mergeProps(labelProps, {
Expand All @@ -157,6 +146,11 @@ export function useCalendarGrid(props: AriaCalendarGridProps, state: CalendarSta
onFocus: () => state.setFocused(true),
onBlur: () => state.setFocused(false)
}),
headerProps: {
// Column headers are hidden to screen readers to make navigating with a touch screen reader easier.
// The day names are already included in the label of each cell, so there's no need to announce them twice.
'aria-hidden': true
},
weekDays
};
}
8 changes: 2 additions & 6 deletions packages/@react-aria/calendar/src/useRangeCalendar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,14 @@ import {CalendarAria, useCalendarBase} from './useCalendarBase';
import {DateValue, RangeCalendarProps} from '@react-types/calendar';
import {RangeCalendarState} from '@react-stately/calendar';
import {RefObject, useRef} from 'react';
import {useEvent, useId} from '@react-aria/utils';
import {useEvent} from '@react-aria/utils';

/**
* Provides the behavior and accessibility implementation for a range calendar component.
* A range calendar displays one or more date grids and allows users to select a contiguous range of dates.
*/
export function useRangeCalendar<T extends DateValue>(props: RangeCalendarProps<T>, state: RangeCalendarState, ref: RefObject<HTMLElement>): CalendarAria {
let res = useCalendarBase(props, state);
res.nextButtonProps.id = useId();
res.prevButtonProps.id = useId();

// We need to ignore virtual pointer events from VoiceOver due to these bugs.
// https://bugs.webkit.org/show_bug.cgi?id=222627
Expand Down Expand Up @@ -54,9 +52,7 @@ export function useRangeCalendar<T extends DateValue>(props: RangeCalendarProps<
let body = document.getElementById(res.calendarProps.id);
if (
body.contains(document.activeElement) &&
(!body.contains(target) || !target.closest('[role="button"]')) &&
!document.getElementById(res.nextButtonProps.id)?.contains(target) &&
!document.getElementById(res.prevButtonProps.id)?.contains(target)
(!body.contains(target) || !target.closest('button, [role="button"]'))
) {
state.selectFocusedDate();
}
Expand Down
2 changes: 1 addition & 1 deletion packages/@react-aria/calendar/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {FormatMessage, useDateFormatter, useMessageFormatter} from '@react-aria/
import intlMessages from '../intl/*.json';
import {useMemo} from 'react';

export const calendarIds = new WeakMap<CalendarState | RangeCalendarState, {calendarId: string, errorMessageId: string}>();
export const hookData = new WeakMap<CalendarState | RangeCalendarState, {ariaLabel: string, ariaLabelledBy: string, errorMessageId: string}>();

export function useSelectedDateDescription(state: CalendarState | RangeCalendarState) {
let formatMessage = useMessageFormatter(intlMessages);
Expand Down
Loading

0 comments on commit 2b7bef1

Please sign in to comment.