Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
3 changes: 2 additions & 1 deletion packages/@react-aria/calendar/src/useCalendarCell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,8 @@ export function useCalendarCell(props: AriaCalendarCellProps, state: CalendarSta
role: 'gridcell',
'aria-disabled': !isSelectable || undefined,
'aria-selected': isSelected || undefined,
'aria-invalid': isInvalid || undefined
'aria-invalid': isInvalid || undefined,
'aria-current': isDateToday ? 'date' : undefined
},
buttonProps: mergeProps(pressProps, {
onFocus() {
Expand Down
9 changes: 6 additions & 3 deletions packages/@react-spectrum/calendar/src/CalendarCell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,12 @@ import {useLocale} from '@react-aria/i18n';
interface CalendarCellProps extends AriaCalendarCellProps {
state: CalendarState | RangeCalendarState,
currentMonth: CalendarDate,
firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat',
/** Optional function to provide a custom className for a given date. */
getDateClassName?: (date: CalendarDate) => string | undefined
}

export function CalendarCell({state, currentMonth, firstDayOfWeek, ...props}: CalendarCellProps): JSX.Element {
export function CalendarCell({state, currentMonth, firstDayOfWeek, getDateClassName, ...props}: CalendarCellProps): JSX.Element {
let ref = useRef<HTMLElement>(null);
let {
cellProps,
Expand All @@ -54,6 +56,7 @@ export function CalendarCell({state, currentMonth, firstDayOfWeek, ...props}: Ca
let isRangeEnd = isSelected && (isLastSelectedBeforeDisabled || dayOfWeek === 6 || props.date.day === currentMonth.calendar.getDaysInMonth(currentMonth));
let {focusProps, isFocusVisible} = useFocusRing();
let {hoverProps, isHovered} = useHover({isDisabled: isDisabled || isUnavailable || state.isReadOnly});
let customClassName = getDateClassName ? getDateClassName(props.date) : undefined;

return (
<td
Expand All @@ -79,7 +82,7 @@ export function CalendarCell({state, currentMonth, firstDayOfWeek, ...props}: Ca
'is-hovered': isHovered,
'is-pressed': isPressed && !state.isReadOnly,
'is-invalid': isInvalid
})}>
}, customClassName)}>
<span className={classNames(styles, 'spectrum-Calendar-dateText')}>
<span>{formattedDate}</span>
</span>
Expand Down
8 changes: 8 additions & 0 deletions packages/@react-spectrum/s2/stories/Calendar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ export const MinValue: Story = {
}
};

export const CustomCellClassName = {
args: {
'aria-label': 'Calendar with Custom class of cell',
getDateClassName: (date) => date.day === 15 ? 'holiday' : undefined
},
name: 'Highlight 15th as Holiday with Custom Class'
};

function ControlledFocus(props: CalendarProps<DateValue>): ReactElement {
const defaultFocusedDate = props.focusedValue ?? new CalendarDate(2019, 6, 5);
let [focusedDate, setFocusedDate] = useState(defaultFocusedDate);
Expand Down
13 changes: 10 additions & 3 deletions packages/react-aria-components/src/Calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -496,13 +496,15 @@ export {CalendarGridBodyForwardRef as CalendarGridBody};

export interface CalendarCellProps extends RenderProps<CalendarCellRenderProps>, HoverEvents, GlobalDOMAttributes<HTMLTableCellElement> {
/** The date to render in the cell. */
date: CalendarDate
date: CalendarDate,
/** Optional function to provide a custom className for a given date. */
getDateClassName?: (date: CalendarDate) => string | undefined
}

/**
* A calendar cell displays a date cell within a calendar grid which can be selected by the user.
*/
export const CalendarCell = /*#__PURE__*/ (forwardRef as forwardRefType)(function CalendarCell({date, ...otherProps}: CalendarCellProps, ref: ForwardedRef<HTMLTableCellElement>) {
export const CalendarCell = /*#__PURE__*/ (forwardRef as forwardRefType)(function CalendarCell({date, getDateClassName, ...otherProps}: CalendarCellProps, ref: ForwardedRef<HTMLTableCellElement>) {
let calendarState = useContext(CalendarStateContext);
let rangeCalendarState = useContext(RangeCalendarStateContext);
let state = calendarState ?? rangeCalendarState!;
Expand Down Expand Up @@ -561,9 +563,14 @@ export const CalendarCell = /*#__PURE__*/ (forwardRef as forwardRefType)(functio

let DOMProps = filterDOMProps(otherProps, {global: true});

let customClassName = getDateClassName ? getDateClassName(date) : undefined;

return (
<td {...cellProps} ref={ref}>
<div {...mergeProps(DOMProps, buttonProps, focusProps, hoverProps, dataAttrs, renderProps)} ref={buttonRef} />
<div
{...mergeProps(DOMProps, buttonProps, focusProps, hoverProps, dataAttrs, renderProps)}
ref={buttonRef}
className={customClassName ? `${renderProps.className ?? ''} ${customClassName}` : renderProps.className} />
</td>
);
});
21 changes: 21 additions & 0 deletions packages/react-aria-components/stories/Calendar.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,24 @@ export const RangeCalendarExample: CalendarStory = {
</RangeCalendar>
)
};

export const CalendarWithCustomCellClass: CalendarStory = {
render: () => (
<Calendar style={{width: 220}}>
<style>
{'.holiday { background: #6f46ed; color: #fff; }'}
</style>
<div style={{display: 'flex', alignItems: 'center'}}>
<Button slot="previous">&lt;</Button>
<Heading style={{flex: 1, textAlign: 'center'}} />
<Button slot="next">&gt;</Button>
</div>
<CalendarGrid style={{width: '100%'}}>
{date => (<CalendarCell
date={date}
getDateClassName={d => d.day === 15 ? 'holiday' : undefined} />
)}
</CalendarGrid>
</Calendar>
)
};
35 changes: 35 additions & 0 deletions packages/react-aria-components/test/Calendar.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,4 +365,39 @@ describe('Calendar', () => {
await user.keyboard('[ArrowLeft][Enter]');
expect(calendar.getByLabelText(/selected/)).toBe(day16);
});

it('should set aria-current="date" on today’s cell', () => {
const today = new Date();
const day = today.getDate();
const {getAllByRole} = render(
<Calendar aria-label="Calendar" visibleDuration={{months: 1}}>
<CalendarGrid>
{date => <CalendarCell date={date} />}
</CalendarGrid>
</Calendar>
);
const cells = getAllByRole('gridcell');
const todayCell = cells.find(cell => {
const btn = cell.querySelector('div,span');
return btn && btn.textContent === String(day);
});
expect(todayCell).toHaveAttribute('aria-current', 'date');
});

it('should apply custom class on cell from getDateClassName', () => {
const specialDay = 15;
const {getAllByRole} = render(
<Calendar aria-label="Calendar" visibleDuration={{months: 1}}>
<CalendarGrid>
{date => <CalendarCell date={date} getDateClassName={d => d.day === specialDay ? 'holiday' : undefined} />}
</CalendarGrid>
</Calendar>
);
const cells = getAllByRole('gridcell');
const specialCell = cells.find(cell => {
const btn = cell.querySelector('div,span');
return btn && btn.textContent === String(specialDay);
});
expect(specialCell.querySelector('div,span').className).toMatch(/holiday/);
});
});