@sproutsocial/seeds-react-datepicker 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +6 -0
- package/.eslintrc.js +4 -0
- package/.turbo/turbo-build.log +21 -0
- package/CHANGELOG.md +7 -0
- package/dist/esm/index.js +411 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/index.d.mts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +451 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +9 -0
- package/package.json +53 -0
- package/src/DateRangePicker/DateRangePicker.stories.tsx +125 -0
- package/src/DateRangePicker/DateRangePicker.tsx +83 -0
- package/src/DateRangePicker/DateRangePickerTypes.ts +35 -0
- package/src/DateRangePicker/StatefulDateRangePicker.tsx +47 -0
- package/src/DateRangePicker/__tests__/DateRangePicker.test.tsx +257 -0
- package/src/DateRangePicker/__tests__/DateRangePicker.typetest.tsx +34 -0
- package/src/DateRangePicker/index.ts +6 -0
- package/src/SingleDatePicker/SingleDatePicker.stories.tsx +92 -0
- package/src/SingleDatePicker/SingleDatePicker.tsx +73 -0
- package/src/SingleDatePicker/SingleDatePickerTypes.ts +25 -0
- package/src/SingleDatePicker/StatefulSingleDatePicker.tsx +25 -0
- package/src/SingleDatePicker/__tests__/SingleDatePicker.test.tsx +155 -0
- package/src/SingleDatePicker/__tests__/SingleDatePicker.typetest.tsx +24 -0
- package/src/SingleDatePicker/index.ts +6 -0
- package/src/common.tsx +60 -0
- package/src/index.ts +2 -0
- package/src/styled.d.ts +7 -0
- package/src/styles.ts +224 -0
- package/src/types.ts +24 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +12 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React, { useCallback, useState } from "react";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
import DayPickerSingleDateController from "react-dates/lib/components/DayPickerSingleDateController";
|
|
4
|
+
import { ReactDatesCssOverrides } from "../styles";
|
|
5
|
+
import {
|
|
6
|
+
commonDatePickerProps,
|
|
7
|
+
DefaultSetStatusText,
|
|
8
|
+
getVisibleMonths,
|
|
9
|
+
} from "../common";
|
|
10
|
+
import type { TypeSingleDatePickerProps } from "./SingleDatePickerTypes";
|
|
11
|
+
import { VisuallyHidden } from "@sproutsocial/seeds-react-visually-hidden";
|
|
12
|
+
|
|
13
|
+
const noop = () => {};
|
|
14
|
+
|
|
15
|
+
const SingleDatePicker = ({
|
|
16
|
+
onDateChange,
|
|
17
|
+
setStatusText = DefaultSetStatusText,
|
|
18
|
+
date = null,
|
|
19
|
+
focused = false,
|
|
20
|
+
onFocusChange = noop,
|
|
21
|
+
initialVisibleMonth,
|
|
22
|
+
numberOfMonths = 1,
|
|
23
|
+
onBlur,
|
|
24
|
+
...rest
|
|
25
|
+
}: TypeSingleDatePickerProps) => {
|
|
26
|
+
const [statusText, updateStatusText] = useState(() =>
|
|
27
|
+
setStatusText(
|
|
28
|
+
getVisibleMonths(
|
|
29
|
+
moment(initialVisibleMonth?.() || date || undefined),
|
|
30
|
+
numberOfMonths
|
|
31
|
+
)
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
const handleMonthClick = useCallback(
|
|
36
|
+
// @ts-ignore unknown types
|
|
37
|
+
(date) => {
|
|
38
|
+
updateStatusText(setStatusText(getVisibleMonths(date, numberOfMonths)));
|
|
39
|
+
},
|
|
40
|
+
[numberOfMonths, setStatusText]
|
|
41
|
+
);
|
|
42
|
+
const wrappedOnBlur = useCallback<React.KeyboardEventHandler<HTMLDivElement>>(
|
|
43
|
+
(event) => {
|
|
44
|
+
onFocusChange?.({ focused: false });
|
|
45
|
+
onBlur?.(event);
|
|
46
|
+
},
|
|
47
|
+
[onBlur, onFocusChange]
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<>
|
|
52
|
+
<ReactDatesCssOverrides />
|
|
53
|
+
<VisuallyHidden>
|
|
54
|
+
<div role="status">{statusText}</div>
|
|
55
|
+
</VisuallyHidden>
|
|
56
|
+
<DayPickerSingleDateController
|
|
57
|
+
{...commonDatePickerProps}
|
|
58
|
+
date={date}
|
|
59
|
+
numberOfMonths={numberOfMonths}
|
|
60
|
+
onDateChange={onDateChange}
|
|
61
|
+
initialVisibleMonth={initialVisibleMonth || null}
|
|
62
|
+
focused={focused}
|
|
63
|
+
onBlur={wrappedOnBlur}
|
|
64
|
+
onFocusChange={onFocusChange}
|
|
65
|
+
onPrevMonthClick={handleMonthClick}
|
|
66
|
+
onNextMonthClick={handleMonthClick}
|
|
67
|
+
{...rest}
|
|
68
|
+
/>
|
|
69
|
+
</>
|
|
70
|
+
);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default React.memo<TypeSingleDatePickerProps>(SingleDatePicker);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Moment } from "moment";
|
|
2
|
+
import type { TypeCommonDatePickerProps } from "../types";
|
|
3
|
+
|
|
4
|
+
export interface TypeStatefulSingleDatePickerProps
|
|
5
|
+
extends TypeCommonDatePickerProps {
|
|
6
|
+
date?: Moment | null;
|
|
7
|
+
onDateChange?: (date: Moment | null) => void;
|
|
8
|
+
focused?: boolean;
|
|
9
|
+
onFocusChange?: (arg0: { focused: boolean }) => void;
|
|
10
|
+
setStatusText?: (dates: Moment[]) => string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface TypeSingleDatePickerProps
|
|
14
|
+
extends Required<Pick<TypeStatefulSingleDatePickerProps, "onDateChange">>,
|
|
15
|
+
Pick<
|
|
16
|
+
TypeStatefulSingleDatePickerProps,
|
|
17
|
+
| "date"
|
|
18
|
+
| "focused"
|
|
19
|
+
| "onFocusChange"
|
|
20
|
+
| "setStatusText"
|
|
21
|
+
| "initialVisibleMonth"
|
|
22
|
+
| "numberOfMonths"
|
|
23
|
+
> {
|
|
24
|
+
onBlur?: React.KeyboardEventHandler<HTMLDivElement> | undefined;
|
|
25
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useState } from "react";
|
|
2
|
+
import type { TypeStatefulSingleDatePickerProps } from "./SingleDatePickerTypes";
|
|
3
|
+
import SingleDatePicker from "./SingleDatePicker";
|
|
4
|
+
|
|
5
|
+
export const StatefulSingleDatePicker = ({
|
|
6
|
+
date,
|
|
7
|
+
onDateChange,
|
|
8
|
+
...rest
|
|
9
|
+
}: TypeStatefulSingleDatePickerProps) => {
|
|
10
|
+
const [stateDate, setDate] = useState(date);
|
|
11
|
+
|
|
12
|
+
// @ts-ignore unknown types
|
|
13
|
+
const handleDateChange = (date) => {
|
|
14
|
+
onDateChange && onDateChange(date);
|
|
15
|
+
setDate(date);
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<SingleDatePicker
|
|
20
|
+
date={stateDate}
|
|
21
|
+
onDateChange={handleDateChange}
|
|
22
|
+
{...rest}
|
|
23
|
+
/>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import moment, { type Moment } from "moment";
|
|
3
|
+
import {
|
|
4
|
+
render,
|
|
5
|
+
fireEvent,
|
|
6
|
+
screen,
|
|
7
|
+
waitFor,
|
|
8
|
+
act,
|
|
9
|
+
} from "@sproutsocial/seeds-react-testing-library";
|
|
10
|
+
import {
|
|
11
|
+
formatDateAsCalendarDay,
|
|
12
|
+
formatDateAsCalendarHeader,
|
|
13
|
+
getVisibleMonthWithReactDatesInternalApi,
|
|
14
|
+
} from "../../common";
|
|
15
|
+
import { SingleDatePicker, StatefulSingleDatePicker } from "../index";
|
|
16
|
+
import Popout from "@sproutsocial/seeds-react-popout";
|
|
17
|
+
import Button from "@sproutsocial/seeds-react-button";
|
|
18
|
+
|
|
19
|
+
const today = moment();
|
|
20
|
+
|
|
21
|
+
describe("SingleDatePicker", () => {
|
|
22
|
+
beforeEach(() => {
|
|
23
|
+
jest.useFakeTimers();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test("should select today's date", async () => {
|
|
27
|
+
render(<StatefulSingleDatePicker date={today} setStatusText={() => ""} />);
|
|
28
|
+
await waitFor(() => {
|
|
29
|
+
expect(
|
|
30
|
+
screen.getByLabelText(`Selected. ${formatDateAsCalendarDay(today)}`)
|
|
31
|
+
).toBeInTheDocument();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("should change dates", async () => {
|
|
36
|
+
render(<StatefulSingleDatePicker date={today} setStatusText={() => ""} />);
|
|
37
|
+
const threeDaysFromNow = formatDateAsCalendarDay(
|
|
38
|
+
today.clone().add(3, "days")
|
|
39
|
+
);
|
|
40
|
+
fireEvent.click(screen.getByLabelText(threeDaysFromNow));
|
|
41
|
+
await waitFor(() => {
|
|
42
|
+
expect(
|
|
43
|
+
screen.getByLabelText(`Selected. ${threeDaysFromNow}`)
|
|
44
|
+
).toBeInTheDocument();
|
|
45
|
+
expect(
|
|
46
|
+
screen.queryByLabelText(`Selected. ${formatDateAsCalendarDay(today)}`)
|
|
47
|
+
).not.toBeInTheDocument();
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("should go to previous month", async () => {
|
|
52
|
+
const { container } = render(
|
|
53
|
+
<StatefulSingleDatePicker date={today} setStatusText={() => ""} />
|
|
54
|
+
);
|
|
55
|
+
expect(getVisibleMonthWithReactDatesInternalApi(container)).toEqual(
|
|
56
|
+
formatDateAsCalendarHeader(today)
|
|
57
|
+
);
|
|
58
|
+
fireEvent.click(
|
|
59
|
+
screen.getByLabelText("Move backward to switch to the previous month.")
|
|
60
|
+
);
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(getVisibleMonthWithReactDatesInternalApi(container)).toEqual(
|
|
63
|
+
formatDateAsCalendarHeader(today.clone().subtract(1, "month"))
|
|
64
|
+
);
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test("should go to next month", async () => {
|
|
69
|
+
const { container } = render(
|
|
70
|
+
<StatefulSingleDatePicker date={today} setStatusText={() => ""} />
|
|
71
|
+
);
|
|
72
|
+
expect(getVisibleMonthWithReactDatesInternalApi(container)).toEqual(
|
|
73
|
+
formatDateAsCalendarHeader(today)
|
|
74
|
+
);
|
|
75
|
+
fireEvent.click(
|
|
76
|
+
screen.getByLabelText("Move forward to switch to the next month.")
|
|
77
|
+
);
|
|
78
|
+
await waitFor(() => {
|
|
79
|
+
expect(getVisibleMonthWithReactDatesInternalApi(container)).toEqual(
|
|
80
|
+
formatDateAsCalendarHeader(today.clone().add(1, "month"))
|
|
81
|
+
);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("should call onDateChange", async () => {
|
|
86
|
+
const onDateChange = jest.fn();
|
|
87
|
+
render(
|
|
88
|
+
<StatefulSingleDatePicker
|
|
89
|
+
date={today}
|
|
90
|
+
onDateChange={onDateChange}
|
|
91
|
+
setStatusText={() => ""}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
const threeDaysFromNow = today.clone().add(3, "days");
|
|
95
|
+
fireEvent.click(
|
|
96
|
+
screen.getByLabelText(formatDateAsCalendarDay(threeDaysFromNow))
|
|
97
|
+
);
|
|
98
|
+
await waitFor(() => {
|
|
99
|
+
expect(onDateChange).toHaveBeenCalledTimes(1);
|
|
100
|
+
expect(onDateChange.mock.calls[0][0].isValid()).toEqual(true);
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// TODO: This test times out. We'll come back to fix later
|
|
105
|
+
xtest("should close on escape key press", async () => {
|
|
106
|
+
const buttonText = "Date Picker";
|
|
107
|
+
const initialDate = moment("2020-01-01");
|
|
108
|
+
|
|
109
|
+
const FocusControlledPopoutDatePicker = () => {
|
|
110
|
+
const [focused, setIsFocused] = React.useState(false);
|
|
111
|
+
const [date, pickDate] = React.useState<Moment | null>(initialDate);
|
|
112
|
+
|
|
113
|
+
const onFocusChange = ({ focused }: { focused: boolean }) =>
|
|
114
|
+
setIsFocused(focused);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Popout
|
|
118
|
+
isOpen={focused}
|
|
119
|
+
setIsOpen={setIsFocused}
|
|
120
|
+
content={
|
|
121
|
+
<Popout.Content p={0} py={400}>
|
|
122
|
+
<SingleDatePicker
|
|
123
|
+
date={date}
|
|
124
|
+
onDateChange={pickDate}
|
|
125
|
+
focused={focused}
|
|
126
|
+
onFocusChange={onFocusChange}
|
|
127
|
+
setStatusText={() => "status text"}
|
|
128
|
+
/>
|
|
129
|
+
</Popout.Content>
|
|
130
|
+
}
|
|
131
|
+
>
|
|
132
|
+
<Button appearance="secondary" onClick={() => setIsFocused(true)}>
|
|
133
|
+
{buttonText}
|
|
134
|
+
</Button>
|
|
135
|
+
</Popout>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
render(<FocusControlledPopoutDatePicker />);
|
|
140
|
+
|
|
141
|
+
expect(screen.getByText(buttonText)).toBeInTheDocument();
|
|
142
|
+
|
|
143
|
+
fireEvent.click(screen.getByText(buttonText));
|
|
144
|
+
|
|
145
|
+
await waitFor(() => {
|
|
146
|
+
expect(screen.getByText("January 2020")).toBeInTheDocument();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
fireEvent.keyDown(screen.getByText("January 2020"), { key: "Escape" });
|
|
150
|
+
|
|
151
|
+
await waitFor(() => {
|
|
152
|
+
expect(screen.queryByText("January 2020")).not.toBeInTheDocument();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { SingleDatePicker, StatefulSingleDatePicker } from "../index";
|
|
3
|
+
|
|
4
|
+
const mockedRequiredProps = {
|
|
5
|
+
onDateChange: jest.fn(),
|
|
6
|
+
setStatusText: jest.fn(),
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
10
|
+
function SingleDatePickerTypes() {
|
|
11
|
+
return (
|
|
12
|
+
<>
|
|
13
|
+
<StatefulSingleDatePicker setStatusText={() => ""} />
|
|
14
|
+
<StatefulSingleDatePicker date={null} setStatusText={() => ""} />
|
|
15
|
+
{/* @ts-expect-error - test that invalid type is rejected */}
|
|
16
|
+
<StatefulSingleDatePicker date="invalid" />
|
|
17
|
+
|
|
18
|
+
<SingleDatePicker {...mockedRequiredProps} />
|
|
19
|
+
<SingleDatePicker {...mockedRequiredProps} date={null} />
|
|
20
|
+
{/* @ts-expect-error - test missing required props is rejected */}
|
|
21
|
+
<SingleDatePicker />
|
|
22
|
+
</>
|
|
23
|
+
);
|
|
24
|
+
}
|
package/src/common.tsx
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import "react-dates/initialize";
|
|
2
|
+
import "react-dates/lib/css/_datepicker.css";
|
|
3
|
+
import type { Moment } from "moment";
|
|
4
|
+
import React from "react";
|
|
5
|
+
import Icon, { type TypeIconName } from "@sproutsocial/seeds-react-icon";
|
|
6
|
+
import { CalendarDay } from "./styles";
|
|
7
|
+
import type { TypeCommonDatePickerProps } from "./types";
|
|
8
|
+
|
|
9
|
+
type TypeCalendarNavButtonType = "left" | "right";
|
|
10
|
+
|
|
11
|
+
const iconNames: { [key in TypeCalendarNavButtonType]: TypeIconName } = {
|
|
12
|
+
left: "arrow-left-solid",
|
|
13
|
+
right: "arrow-right-solid",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const CalendarNavButton = ({
|
|
17
|
+
type,
|
|
18
|
+
}: {
|
|
19
|
+
type: TypeCalendarNavButtonType;
|
|
20
|
+
}) => <Icon size="mini" name={iconNames[type]} aria-hidden />;
|
|
21
|
+
|
|
22
|
+
export const commonDatePickerProps: TypeCommonDatePickerProps = {
|
|
23
|
+
hideKeyboardShortcutsPanel: true,
|
|
24
|
+
daySize: 30,
|
|
25
|
+
navPrev: <CalendarNavButton type="left" />,
|
|
26
|
+
navNext: <CalendarNavButton type="right" />,
|
|
27
|
+
renderDayContents: (day, modifiers) => (
|
|
28
|
+
<CalendarDay day={day} modifiers={modifiers}>
|
|
29
|
+
{day.format("D")}
|
|
30
|
+
</CalendarDay>
|
|
31
|
+
),
|
|
32
|
+
initialVisibleMonth: null,
|
|
33
|
+
};
|
|
34
|
+
// Testing utilities
|
|
35
|
+
|
|
36
|
+
export const formatDateAsCalendarHeader = (date: Moment): string =>
|
|
37
|
+
date.format("MMMM YYYY");
|
|
38
|
+
|
|
39
|
+
export const formatDateAsCalendarDay = (date: Moment): string =>
|
|
40
|
+
date.format("dddd, MMMM D, YYYY");
|
|
41
|
+
|
|
42
|
+
export const getVisibleMonthWithReactDatesInternalApi = (
|
|
43
|
+
container: HTMLElement
|
|
44
|
+
): string =>
|
|
45
|
+
container.querySelector("[data-visible=true] strong")?.textContent ?? "";
|
|
46
|
+
|
|
47
|
+
// Receives a single Moment and returns an array of Moments, one for each currently visible month.
|
|
48
|
+
// @ts-ignore unknown types
|
|
49
|
+
export const getVisibleMonths = (initialMonth, numberOfMonths) => {
|
|
50
|
+
const months = [initialMonth];
|
|
51
|
+
for (let i = 1; i < numberOfMonths; i++) {
|
|
52
|
+
months.push(initialMonth.clone().add(i, "month"));
|
|
53
|
+
}
|
|
54
|
+
return months;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// Default setStatusText prop for both SingleDatePicker and DateRangePicker
|
|
58
|
+
// @ts-ignore unknown types
|
|
59
|
+
export const DefaultSetStatusText = (dates) =>
|
|
60
|
+
dates.map(formatDateAsCalendarHeader).join(", ");
|
package/src/index.ts
ADDED
package/src/styled.d.ts
ADDED
package/src/styles.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import styled, { createGlobalStyle, css } from "styled-components";
|
|
2
|
+
import moment from "moment";
|
|
3
|
+
import type { ModifiersShape } from "react-dates";
|
|
4
|
+
import { disabled } from "@sproutsocial/seeds-react-mixins";
|
|
5
|
+
import Box from "@sproutsocial/seeds-react-box";
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* Partial list of modifiers given to renderDayContents by airbnb/react-dates. There may be more.
|
|
9
|
+
*
|
|
10
|
+
* today, blocked, blocked-calendar, blocked-out-of-range, highlighted-calendar, valid,
|
|
11
|
+
* selected, selected-start, selected-end, blocked-minimum-nights, selected-span, last-in-range,
|
|
12
|
+
* hovered, hovered-span, hovered-offset, after-hovered-start, first-day-of-week, last-day-of-week,
|
|
13
|
+
* hovered-start-first-possible-end, hovered-start-blocked-minimum-nights, before-hovered-end,
|
|
14
|
+
* no-selected-start-before-selected-end, selected-start-in-hovered-span, selected-start-no-selected-end,
|
|
15
|
+
* selected-end-no-selected-start
|
|
16
|
+
*
|
|
17
|
+
*/
|
|
18
|
+
const isSelected = (modifiers: ModifiersShape) =>
|
|
19
|
+
modifiers.has("selected-span") ||
|
|
20
|
+
modifiers.has("selected") ||
|
|
21
|
+
modifiers.has("selected-start") ||
|
|
22
|
+
modifiers.has("selected-end") ||
|
|
23
|
+
modifiers.has("hovered-span") ||
|
|
24
|
+
modifiers.has("after-hovered-start");
|
|
25
|
+
|
|
26
|
+
const isOutOfRange = (modifiers: ModifiersShape) =>
|
|
27
|
+
modifiers.has("blocked-out-of-range");
|
|
28
|
+
|
|
29
|
+
const isHoveredAndInRange = (modifiers: ModifiersShape) =>
|
|
30
|
+
modifiers.has("hovered") && !modifiers.has("blocked-out-of-range");
|
|
31
|
+
|
|
32
|
+
const shouldHaveLeftPill = (modifiers: ModifiersShape, day: moment.Moment) => {
|
|
33
|
+
if (!isSelected(modifiers)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (
|
|
38
|
+
modifiers.has("selected") ||
|
|
39
|
+
modifiers.has("selected-start") ||
|
|
40
|
+
modifiers.has("first-day-of-week") ||
|
|
41
|
+
day.isSame(moment(day).startOf("month"), "day")
|
|
42
|
+
) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return false;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const shouldHaveRightPill = (modifiers: ModifiersShape, day: moment.Moment) => {
|
|
50
|
+
if (!isSelected(modifiers)) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
modifiers.has("selected") ||
|
|
56
|
+
modifiers.has("selected-end") ||
|
|
57
|
+
modifiers.has("last-day-of-week") ||
|
|
58
|
+
day.isSame(moment(day).endOf("month"), "day")
|
|
59
|
+
) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return false;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const CalendarDay = styled(Box)<{
|
|
67
|
+
modifiers: ModifiersShape;
|
|
68
|
+
day: moment.Moment;
|
|
69
|
+
}>`
|
|
70
|
+
${({ modifiers, day, theme }) => {
|
|
71
|
+
if (isSelected(modifiers)) {
|
|
72
|
+
return css`
|
|
73
|
+
background-color: ${theme.colors.container.background.selected};
|
|
74
|
+
color: ${theme.colors.text.inverse};
|
|
75
|
+
margin-left: ${shouldHaveLeftPill(modifiers, day) && theme.space[200]};
|
|
76
|
+
margin-right: ${shouldHaveRightPill(modifiers, day) &&
|
|
77
|
+
theme.space[200]};
|
|
78
|
+
border-top-left-radius: ${shouldHaveLeftPill(modifiers, day) &&
|
|
79
|
+
theme.radii.pill};
|
|
80
|
+
border-bottom-left-radius: ${shouldHaveLeftPill(modifiers, day) &&
|
|
81
|
+
theme.radii.pill};
|
|
82
|
+
border-top-right-radius: ${shouldHaveRightPill(modifiers, day) &&
|
|
83
|
+
theme.radii.pill};
|
|
84
|
+
border-bottom-right-radius: ${shouldHaveRightPill(modifiers, day) &&
|
|
85
|
+
theme.radii.pill};
|
|
86
|
+
`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (isHoveredAndInRange(modifiers)) {
|
|
90
|
+
return css`
|
|
91
|
+
margin: 0 ${theme.space[200]};
|
|
92
|
+
border-radius: ${theme.radii.pill};
|
|
93
|
+
border: 1px solid ${theme.colors.container.border.selected};
|
|
94
|
+
`;
|
|
95
|
+
} else if (isOutOfRange(modifiers)) {
|
|
96
|
+
return css`
|
|
97
|
+
color: ${theme.colors.text.subtext};
|
|
98
|
+
${disabled};
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
}};
|
|
102
|
+
`;
|
|
103
|
+
|
|
104
|
+
export const ReactDatesCssOverrides = createGlobalStyle`
|
|
105
|
+
.DayPicker {
|
|
106
|
+
box-sizing: content-box;
|
|
107
|
+
font-weight: ${({ theme }) => theme.fontWeights.normal};
|
|
108
|
+
font-family: ${(props) => props.theme.fontFamily};
|
|
109
|
+
|
|
110
|
+
/* override react-dates height to better reflect how tall the component actually is */
|
|
111
|
+
/* adding margin/padding will be more truer to our seeds system because the height */
|
|
112
|
+
/* of the calendar adds an extra row of padding if we do not override it */
|
|
113
|
+
&_transitionContainer {
|
|
114
|
+
/* need !important because react-dates sets height on the element itself */
|
|
115
|
+
height: 228px !important;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
&_weekHeader {
|
|
119
|
+
color: ${({ theme }) => theme.colors.text.headline};
|
|
120
|
+
border-bottom: 1px solid ${({ theme }) => theme.colors.container.border.base};
|
|
121
|
+
|
|
122
|
+
/* Magic number to match position of .CalendarMonth_caption */
|
|
123
|
+
top: 26px;
|
|
124
|
+
|
|
125
|
+
/* Magic number to make the bottom border line stretch the full width */
|
|
126
|
+
width: 230px;
|
|
127
|
+
|
|
128
|
+
&_ul {
|
|
129
|
+
/* Magic number to line up day name headings over the correct numbers */
|
|
130
|
+
padding-left: 10px;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
&_weekHeaders__horizontal {
|
|
135
|
+
margin-left: 0
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
&_weekHeader_li {
|
|
139
|
+
${({ theme }) => theme.typography[200]}
|
|
140
|
+
color: ${({ theme }) => theme.colors.text.subtext};
|
|
141
|
+
font-weight: ${({ theme }) => theme.fontWeights.semibold};
|
|
142
|
+
margin: 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&__horizontal {
|
|
146
|
+
box-shadow: none;
|
|
147
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.CalendarDay {
|
|
152
|
+
background-color: transparent;
|
|
153
|
+
|
|
154
|
+
&__selected, &__selected_span, &:hover {
|
|
155
|
+
background-color: transparent;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
&__default {
|
|
159
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
160
|
+
}
|
|
161
|
+
&__default,
|
|
162
|
+
&__default:hover {
|
|
163
|
+
border: none;
|
|
164
|
+
color: ${({ theme }) => theme.colors.text.body};
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.CalendarMonth {
|
|
169
|
+
${({ theme }) => theme.typography[200]}
|
|
170
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
171
|
+
|
|
172
|
+
/* spacing between visible months and months off canvas */
|
|
173
|
+
padding: 0 15px;
|
|
174
|
+
|
|
175
|
+
&_caption {
|
|
176
|
+
${({ theme }) => theme.typography[200]}
|
|
177
|
+
color: ${({ theme }) => theme.colors.text.headline};
|
|
178
|
+
padding-top: 0;
|
|
179
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
180
|
+
|
|
181
|
+
strong {
|
|
182
|
+
font-weight: ${({ theme }) => theme.fontWeights.semibold};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
&_table {
|
|
187
|
+
line-height: 21.333px;
|
|
188
|
+
tr {
|
|
189
|
+
vertical-align: middle;
|
|
190
|
+
}
|
|
191
|
+
td {
|
|
192
|
+
padding: 0;
|
|
193
|
+
border-bottom: none;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.CalendarMonthGrid {
|
|
199
|
+
background: ${({ theme }) => theme.colors.container.background.base};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/* Left and Right Arrow Buttons to navigate months */
|
|
203
|
+
.DayPickerNavigation_button__horizontal {
|
|
204
|
+
color: ${({ theme }) => theme.colors.button.pill.text.base};
|
|
205
|
+
top: -4px;
|
|
206
|
+
padding: 7px 8px;
|
|
207
|
+
position: absolute;
|
|
208
|
+
line-height: 0.78;
|
|
209
|
+
border-radius: 9999px;
|
|
210
|
+
border: none;
|
|
211
|
+
background: ${({ theme }) => theme.colors.button.pill.background.base};
|
|
212
|
+
|
|
213
|
+
&:nth-child(1) {
|
|
214
|
+
left: 22px;
|
|
215
|
+
}
|
|
216
|
+
&:nth-child(2) {
|
|
217
|
+
right: 22px;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
&:hover {
|
|
221
|
+
background: ${({ theme }) => theme.colors.button.pill.background.hover};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
`;
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import type { Moment } from "moment";
|
|
3
|
+
import type { ModifiersShape } from "react-dates";
|
|
4
|
+
|
|
5
|
+
export interface TypeCommonDatePickerProps {
|
|
6
|
+
daySize?: number;
|
|
7
|
+
isOutsideRange?: (date: Moment) => boolean;
|
|
8
|
+
hideKeyboardShortcutsPanel?: boolean;
|
|
9
|
+
navPrev?: React.ReactNode;
|
|
10
|
+
navNext?: React.ReactNode;
|
|
11
|
+
numberOfMonths?: number;
|
|
12
|
+
renderDayContents?: (
|
|
13
|
+
day: Moment,
|
|
14
|
+
modifiers: ModifiersShape
|
|
15
|
+
) => React.ReactNode;
|
|
16
|
+
keepOpenOnDateSelect?: boolean;
|
|
17
|
+
initialVisibleMonth?: (() => Moment) | null;
|
|
18
|
+
minimumNights?: number;
|
|
19
|
+
phrases?: Record<
|
|
20
|
+
string,
|
|
21
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
22
|
+
Node | ((arg0: { date: Moment }) => React.ReactNode)
|
|
23
|
+
>;
|
|
24
|
+
}
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig((options) => ({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["cjs", "esm"],
|
|
6
|
+
clean: true,
|
|
7
|
+
legacyOutput: true,
|
|
8
|
+
dts: options.dts,
|
|
9
|
+
external: ["react"],
|
|
10
|
+
sourcemap: true,
|
|
11
|
+
metafile: options.metafile,
|
|
12
|
+
}));
|