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

refactor(pickers): ♻️ improve state from dateValue to string #88

Merged
merged 5 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
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
6 changes: 4 additions & 2 deletions src/calendar/CalendarCellButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { createComponent, createHook } from "reakit-system";
import { ariaAttr, callAllHandlers } from "@chakra-ui/utils";
import { ButtonHTMLProps, ButtonOptions, useButton } from "reakit";

import { isInvalid } from "./__utils";
import { isInvalidDate } from "../utils";
anuraghazra marked this conversation as resolved.
Show resolved Hide resolved
import { CALENDAR_CELL_BUTTON_KEYS } from "./__keys";
import { CalendarStateReturn } from "./CalendarState";
import { RangeCalendarStateReturn } from "./RangeCalendarState";
Expand Down Expand Up @@ -56,7 +56,9 @@ export const useCalendarCellButton = createHook<
} = options;
const isCurrentMonth = date.getMonth() === month;
const isDisabled =
isDisabledOption || !isCurrentMonth || isInvalid(date, minDate, maxDate);
isDisabledOption ||
!isCurrentMonth ||
isInvalidDate(date, minDate, maxDate);
const truelyDisabled = disabled || isDisabled;

return { disabled: truelyDisabled, ...options };
Expand Down
58 changes: 31 additions & 27 deletions src/calendar/CalendarState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
addMonths,
addWeeks,
addYears,
endOfDay,
endOfMonth,
format,
getDaysInMonth,
Expand All @@ -29,49 +28,55 @@ import { useControllableState } from "@chakra-ui/hooks";
import { FocusableProps, InputBase, ValueBase } from "@react-types/shared";

import { useWeekStart } from "./useWeekStart";
import { RangeValueBase } from "../utils/types";
import { announce } from "../utils/LiveAnnouncer";
import { DateValue, RangeValueBase } from "../utils/types";
import { isInvalid, useWeekDays, generateDaysInMonthArray } from "./__utils";
import { isInvalidDate, parseDate, stringifyDate } from "../utils";
import { useWeekDays, generateDaysInMonthArray } from "./__utils";

export interface CalendarInitialState
extends FocusableProps,
InputBase,
ValueBase<DateValue>,
RangeValueBase<DateValue> {
ValueBase<string>,
RangeValueBase<string> {
id?: string;
}

export function useCalendarState(props: CalendarInitialState = {}) {
const {
value: initialValue,
defaultValue,
onChange,
minValue: initialMinValue,
maxValue: initialMaxValue,
value: initialDate,
defaultValue: defaultValueProp,
onChange: onChangeProp,
minValue: minValueProp,
maxValue: maxValueProp,
isDisabled = false,
isReadOnly = false,
autoFocus = false,
id,
} = props;

const { id: calendarId } = useId({ id, baseId: "calendar" });

const onChange = React.useCallback(
(date: Date) => {
return onChangeProp?.(stringifyDate(date));
},
[onChangeProp],
);

const [value, setControllableValue] = useControllableState({
value: initialValue,
defaultValue,
value: parseDate(initialDate),
defaultValue: parseDate(defaultValueProp),
onChange,
shouldUpdate: (prev, next) => prev !== next,
});

const dateValue = value ? new Date(value) : null;
const minValue = initialMinValue ? new Date(initialMinValue) : null;
const maxValue = initialMaxValue ? new Date(initialMaxValue) : null;
const minDate = minValue ? startOfDay(minValue) : null;
const maxDate = maxValue ? endOfDay(maxValue) : null;
const minValue = parseDate(minValueProp);
const maxValue = parseDate(maxValueProp);

const [isFocused, setFocused] = React.useState(autoFocus);

const initialMonth = dateValue ?? new Date();
const [currentMonth, setCurrentMonth] = React.useState(initialMonth); // TODO: does this need to be in state at all??
const initialMonth = value ?? new Date();
const [currentMonth, setCurrentMonth] = React.useState(initialMonth);
const [focusedDate, setFocusedDate] = React.useState(initialMonth);

const month = currentMonth.getMonth();
Expand All @@ -95,7 +100,7 @@ export function useCalendarState(props: CalendarInitialState = {}) {

// Sets focus to a specific cell date
function focusCell(date: Date) {
if (isInvalid(date, minDate, maxDate)) {
if (isInvalidDate(date, minValue, maxValue)) {
return;
}

Expand Down Expand Up @@ -125,16 +130,15 @@ export function useCalendarState(props: CalendarInitialState = {}) {
}, [currentMonth]);

useUpdateEffect(() => {
if (!dateValue) return;

announce(`Selected Date: ${format(dateValue, "do MMM yyyy")}`);
}, [dateValue]);
if (!value) return;
announce(`Selected Date: ${format(value, "do MMM yyyy")}`);
}, [value]);

return {
calendarId,
dateValue,
minDate,
maxDate,
dateValue: value,
minDate: minValue,
maxDate: maxValue,
month,
year,
weekStart,
Expand Down
36 changes: 23 additions & 13 deletions src/calendar/RangeCalendarState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,40 +13,50 @@ import * as React from "react";
import { format, isSameDay } from "date-fns";
import { useControllableState, useUpdateEffect } from "@chakra-ui/hooks";

import { DateValue } from "../utils/types";
import { makeRange } from "./__utils";
import { RangeValueBase } from "../utils/types";
import { announce } from "../utils/LiveAnnouncer";
import { useCalendarState } from "./CalendarState";
import { convertRange, makeRange } from "./__utils";
import { parseRangeDate, stringifyDate } from "../utils";

export interface RangeCalendarInitialState
extends FocusableProps,
InputBase,
ValueBase<RangeValue<DateValue>> {
minValue?: DateValue;
maxValue?: DateValue;
ValueBase<RangeValue<string>>,
RangeValueBase<string> {
id?: string;
}

export function useRangeCalendarState(props: RangeCalendarInitialState = {}) {
const {
value: initialValue,
defaultValue,
onChange,
value: initialDate,
defaultValue: defaultValueProp,
onChange: onChangeProp,
...calendarProps
} = props;

const [value, setValue] = useControllableState({
value: initialValue,
defaultValue,
const onChange = React.useCallback(
(date: RangeValue<Date>) => {
return onChangeProp?.({
start: stringifyDate(date.start),
end: stringifyDate(date.end),
});
},
[onChangeProp],
);

const [value, setValue] = useControllableState<RangeValue<Date>>({
value: parseRangeDate(initialDate),
defaultValue: parseRangeDate(defaultValueProp),
onChange,
shouldUpdate: (prev, next) => prev !== next,
});

const dateRange = value != null ? convertRange(value) : null;
const dateRange = value != null ? value : null;
const [anchorDate, setAnchorDate] = React.useState<Date | null>(null);
const calendar = useCalendarState({
...calendarProps,
value: value && value.start,
value: value && stringifyDate(value.start),
});

const highlightedRange = anchorDate
Expand Down
16 changes: 7 additions & 9 deletions src/calendar/__tests__/Calendar.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import { subWeeks, addWeeks } from "date-fns";
import { axe, render, press } from "reakit-test-utils";
import { subWeeks, addWeeks, format, addDays } from "date-fns";

import {
CalendarCell,
Expand Down Expand Up @@ -71,17 +71,15 @@ export const CalendarComp: React.FC<CalendarInitialState> = props => {

describe("Calendar", () => {
it("should render correctly", () => {
const { getByTestId: testId } = render(
<CalendarComp defaultValue={"10-7-2020"} />,
);
const { getByTestId: testId } = render(<CalendarComp />);

expect(testId("weekDays").children).toHaveLength(7);
expect(testId("current-year")).toHaveTextContent("October 2020");
});

it("should have proper calendar header keyboard navigation", () => {
const { getByTestId: testId, getByText: text } = render(
<CalendarComp defaultValue={"10-7-2020"} />,
<CalendarComp defaultValue={"2020-10-07"} />,
);

expect(testId("current-year")).toHaveTextContent("October 2020");
Expand All @@ -105,7 +103,7 @@ describe("Calendar", () => {

it("should proper grid navigation", () => {
const { getByTestId: testId, getByLabelText: label } = render(
<CalendarComp defaultValue={"10-7-2020"} />,
<CalendarComp defaultValue={"2020-10-07"} />,
);

expect(testId("current-year")).toHaveTextContent("October 2020");
Expand Down Expand Up @@ -136,9 +134,9 @@ describe("Calendar", () => {
test("should have min/max values", async () => {
const { getByLabelText: label } = render(
<CalendarComp
defaultValue={new Date(2020, 10, 7)}
minValue={subWeeks(new Date(2020, 10, 7), 1)}
maxValue={addWeeks(new Date(2020, 10, 7), 1)}
defaultValue={format(new Date(2020, 10, 7), "yyyy-MM-dd")}
minValue={format(subWeeks(new Date(2020, 10, 7), 1), "yyyy-MM-dd")}
maxValue={format(addWeeks(new Date(2020, 10, 7), 1), "yyyy-MM-dd")}
/>,
);

Expand Down
6 changes: 3 additions & 3 deletions src/calendar/__tests__/RangeCalendar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ describe("RangeCalendar", () => {
it("should render correctly", () => {
const { getByTestId: testId } = render(
<RangeCalendarComp
defaultValue={{ start: "10-7-2020", end: "10-30-2020" }}
defaultValue={{ start: "2020-10-07", end: "2020-10-30" }}
/>,
);

Expand All @@ -91,7 +91,7 @@ describe("RangeCalendar", () => {
it("should have proper initial start and end ranges", () => {
const { getByLabelText: label, baseElement } = render(
<RangeCalendarComp
defaultValue={{ start: "10-7-2020", end: "10-30-2020" }}
defaultValue={{ start: "2020-10-07", end: "2020-10-30" }}
/>,
);

Expand All @@ -109,7 +109,7 @@ describe("RangeCalendar", () => {
it("should be able to select ranges with keyboard navigation", () => {
const { getByLabelText: label, getByTestId: testId, baseElement } = render(
<RangeCalendarComp
defaultValue={{ start: "10-7-2020", end: "10-30-2020" }}
defaultValue={{ start: "2020-10-07", end: "2020-10-30" }}
/>,
);

Expand Down
19 changes: 0 additions & 19 deletions src/calendar/__utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,6 @@ import { endOfDay, setDay } from "date-fns";
import { RangeValue } from "@react-types/shared";
import { useDateFormatter } from "@react-aria/i18n";

import { DateValue } from "../utils/types";

export function isInvalid(
date: Date,
minDate: Date | null,
maxDate: Date | null,
) {
return (
(minDate != null && date < minDate) || (maxDate != null && date > maxDate)
);
}

export function useWeekDays(weekStart: number) {
const dayFormatter = useDateFormatter({ weekday: "short" });
const dayFormatterLong = useDateFormatter({ weekday: "long" });
Expand Down Expand Up @@ -61,10 +49,3 @@ export function makeRange(start: Date, end: Date): RangeValue<Date> {

return { start: start, end: endOfDay(end) };
}

export function convertRange(range: RangeValue<DateValue>): RangeValue<Date> {
return {
start: new Date(range.start),
end: new Date(range.end),
};
}
37 changes: 20 additions & 17 deletions src/calendar/stories/Calendar.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import * as React from "react";
import { Meta } from "@storybook/react";
import { addDays, addWeeks, subWeeks } from "date-fns";
import { addWeeks, format, subWeeks } from "date-fns";

import "./index.css";
import { DateValue } from "../../utils/types";
import { CalendarComponent } from "./CalendarComponent";

export default {
Expand All @@ -12,39 +11,43 @@ export default {

export const Default = () => <CalendarComponent />;
export const DefaultValue = () => (
<CalendarComponent defaultValue={new Date(2001, 0, 1)} />
<CalendarComponent defaultValue="2001-01-01" />
);
export const ControlledValue = () => {
const [value, setValue] = React.useState<DateValue>(addDays(new Date(), 1));
const [value, setValue] = React.useState("2020-10-13");

return (
<div>
<input
type="date"
onChange={e => setValue(new Date(e.target.value))}
value={(value as Date).toISOString().slice(0, 10)}
onChange={e => setValue(e.target.value)}
value={value}
/>
<CalendarComponent value={value} onChange={setValue} />
</div>
);
};

export const MinMaxDate = () => (
<CalendarComponent minValue={new Date()} maxValue={addWeeks(new Date(), 1)} />
<CalendarComponent
minValue={format(new Date(), "yyyy-MM-dd")}
maxValue={format(addWeeks(new Date(), 1), "yyyy-MM-dd")}
/>
);

export const MinMaxDefaultDate = () => (
<CalendarComponent
defaultValue={new Date()}
minValue={subWeeks(new Date(), 1)}
maxValue={addWeeks(new Date(), 1)}
defaultValue={format(new Date(2020, 10, 7), "yyyy-MM-dd")}
minValue={format(subWeeks(new Date(2020, 10, 7), 1), "yyyy-MM-dd")}
maxValue={format(addWeeks(new Date(2020, 10, 7), 1), "yyyy-MM-dd")}
/>
);
export const isDisabled = () => (
<CalendarComponent defaultValue={addDays(new Date(), 1)} isDisabled />
);
export const isReadOnly = () => (
<CalendarComponent defaultValue={addDays(new Date(), 1)} isReadOnly />
);

export const isDisabled = () => <CalendarComponent isDisabled />;

export const isReadOnly = () => <CalendarComponent isReadOnly />;

export const autoFocus = () => (
// eslint-disable-next-line jsx-a11y/no-autofocus
<CalendarComponent defaultValue={addDays(new Date(), 1)} autoFocus />
<CalendarComponent autoFocus />
);
Loading