Skip to content

Commit

Permalink
fix: today day is auto focused when available (#2174)
Browse files Browse the repository at this point in the history
  • Loading branch information
gpbl authored May 31, 2024
1 parent 9986fe2 commit 94c92b1
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 17 deletions.
5 changes: 3 additions & 2 deletions src/components/DayWrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {

import { UI, DayModifier } from "../UI";
import { CalendarDay } from "../classes/CalendarDay";
import { useCalendar } from "../contexts/calendar";
import { useFocus } from "../contexts/focus";
import { useModifiers } from "../contexts/modifiers";
import { useProps } from "../contexts/props";
Expand Down Expand Up @@ -60,8 +61,10 @@ export function DayWrapper(props: {
styles = {}
} = useProps();

const { isInteractive } = useCalendar();
const { setSelected } = useSelection();
const { getModifiers } = useModifiers();

const {
autoFocusTarget,
focusedDay,
Expand Down Expand Up @@ -196,8 +199,6 @@ export function DayWrapper(props: {
const isAutoFocusTarget = Boolean(autoFocusTarget?.isEqualTo(props.day));
const isFocused = Boolean(focusedDay?.isEqualTo(props.day));

const isInteractive = mode !== "default" || Boolean(onDayClick);

const style = getStyleForModifiers(modifiers, modifiersStyles, styles);

const classNameForModifiers = getClassNamesForModifiers(
Expand Down
11 changes: 11 additions & 0 deletions src/contexts/calendar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ export interface CalendarContext {
goToDay: (day: CalendarDay) => void;
/** Whether the given date is included in the displayed months. */
isDayDisplayed: (day: CalendarDay) => boolean;

/**
* Whether the calendar is interactive, i.e. DayPicker has a selection `mode`
* set or the `onDayClick` prop is set.
*/
isInteractive: boolean;
}

const calendarContext = createContext<CalendarContext | undefined>(undefined);
Expand Down Expand Up @@ -161,6 +167,9 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) {
return previousMonth ? goToMonth(previousMonth) : undefined;
}

const isInteractive =
props.mode !== "default" || props.onDayClick !== undefined;

const calendar: CalendarContext = {
dates,
months,
Expand All @@ -176,6 +185,8 @@ export function CalendarProvider(providerProps: { children?: ReactNode }) {
goToNextMonth,
goToPreviousMonth,
goToDay,

isInteractive,
isDayDisplayed,

dropdownOptions: {
Expand Down
40 changes: 40 additions & 0 deletions src/contexts/focus.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { gridcell } from "@/test/elements";
import { renderHook } from "@/test/renderHook";
import { user } from "@/test/user";

import { useFocus } from "./focus";

const month = new Date(2020, 0, 1);
const today = new Date(2020, 0, 14);

describe("autoFocusTarget", () => {
describe("when not in interactive", () => {
test("the auto focus target is undefined", () => {
const { result } = renderHook(useFocus, { month, today });
expect(result.current.autoFocusTarget).toBeUndefined();
});
});
describe("when in selection mode", () => {
test("the autofocus target should be today", () => {
const { result } = renderHook(useFocus, {
month,
today,
mode: "single"
});
expect(result.current.autoFocusTarget?.date).toEqual(
new Date(2020, 0, 14)
);
});
describe("if today is disabled", () => {
test("the autofocus target should be the first focusable day (the 1st of month)", () => {
const { result } = renderHook(useFocus, {
month,
today,
mode: "multiple",
disabled: [today]
});
expect(result.current.autoFocusTarget?.date).toEqual(month);
});
});
});
});
50 changes: 35 additions & 15 deletions src/contexts/focus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import React, {
useState
} from "react";

import { DayModifier } from "../UI";
import type { CalendarDay } from "../classes";
import { getNextFocus } from "../helpers/getNextFocus";

Expand Down Expand Up @@ -69,23 +70,44 @@ export interface FocusContext {
const focusContext = createContext<FocusContext | undefined>(undefined);

/** @private */
export function FocusProvider(props: { children: ReactNode }): JSX.Element {
const { goToDay, isDayDisplayed } = useCalendar();
export function FocusProvider({
children
}: {
children: ReactNode;
}): JSX.Element {
const { goToDay, isDayDisplayed, days, isInteractive } = useCalendar();

const { autoFocus = false, ...dayPickerProps } = useProps();
const { calendarModifiers } = useModifiers();
const { autoFocus = false, ...props } = useProps();
const { calendarModifiers, getModifiers } = useModifiers();

const [focused, setFocused] = useState<CalendarDay | undefined>();
const [lastFocused, setLastFocused] = useState<CalendarDay | undefined>();
const [initiallyFocused, setInitiallyFocused] = useState(false);

const autoFocusTarget =
focused ?? (lastFocused && isDayDisplayed(lastFocused))
? lastFocused
: calendarModifiers.selected[0] ?? // autofocus the first selected day
// TOFIX: possible bug here selecting a today date when disabled
// calendarModifiers.today[0] ?? // autofocus today
calendarModifiers.focusable[0]; // otherwise autofocus the first focusable day;
const today = calendarModifiers.today[0];

let autoFocusTarget: CalendarDay | undefined;

const isValidFocusTarget = (day: CalendarDay) => {
return isDayDisplayed(day) && !getModifiers(day)[DayModifier.disabled];
};

if (isInteractive) {
if (focused) {
autoFocusTarget = focused;
} else if (lastFocused) {
autoFocusTarget = lastFocused;
} else if (
calendarModifiers.selected[0] &&
isValidFocusTarget(calendarModifiers.selected[0])
) {
autoFocusTarget = calendarModifiers.selected[0];
} else if (today && isValidFocusTarget(today)) {
autoFocusTarget = today;
} else if (calendarModifiers.focusable[0]) {
autoFocusTarget = calendarModifiers.focusable[0];
}
}

// Focus the focus target when autoFocus is passed in
useEffect(() => {
Expand All @@ -103,7 +125,7 @@ export function FocusProvider(props: { children: ReactNode }): JSX.Element {

function moveFocus(moveBy: MoveFocusBy, moveDir: MoveFocusDir) {
if (!focused) return;
const nextFocus = getNextFocus(moveBy, moveDir, focused, dayPickerProps);
const nextFocus = getNextFocus(moveBy, moveDir, focused, props);
if (!nextFocus) return;

goToDay(nextFocus);
Expand All @@ -129,9 +151,7 @@ export function FocusProvider(props: { children: ReactNode }): JSX.Element {
};

return (
<focusContext.Provider value={value}>
{props.children}
</focusContext.Provider>
<focusContext.Provider value={value}>{children}</focusContext.Provider>
);
}

Expand Down

0 comments on commit 94c92b1

Please sign in to comment.