@wordpress/components 29.11.0 → 29.13.1-next.719a03cbe.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/CHANGELOG.md +22 -0
- package/build/box-control/input-control.js +2 -2
- package/build/box-control/input-control.js.map +1 -1
- package/build/calendar/date-calendar/index.js +60 -0
- package/build/calendar/date-calendar/index.js.map +1 -0
- package/build/calendar/date-range-calendar/index.js +168 -0
- package/build/calendar/date-range-calendar/index.js.map +1 -0
- package/build/calendar/index.js +27 -0
- package/build/calendar/index.js.map +1 -0
- package/build/calendar/types.js +6 -0
- package/build/calendar/types.js.map +1 -0
- package/build/calendar/utils/constants.js +68 -0
- package/build/calendar/utils/constants.js.map +1 -0
- package/build/calendar/utils/day-cell.js +137 -0
- package/build/calendar/utils/day-cell.js.map +1 -0
- package/build/calendar/utils/misc.js +10 -0
- package/build/calendar/utils/misc.js.map +1 -0
- package/build/calendar/utils/use-controlled-value.js +58 -0
- package/build/calendar/utils/use-controlled-value.js.map +1 -0
- package/build/calendar/utils/use-localization-props.js +162 -0
- package/build/calendar/utils/use-localization-props.js.map +1 -0
- package/build/custom-gradient-picker/gradient-bar/control-points.js +1 -1
- package/build/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build/custom-select-control-v2/custom-select.js +3 -3
- package/build/custom-select-control-v2/custom-select.js.map +1 -1
- package/build/date-time/date/index.js +1 -1
- package/build/date-time/date/index.js.map +1 -1
- package/build/form-file-upload/index.js +4 -6
- package/build/form-file-upload/index.js.map +1 -1
- package/build/form-token-field/index.js +11 -1
- package/build/form-token-field/index.js.map +1 -1
- package/build/form-token-field/token.js +1 -1
- package/build/form-token-field/token.js.map +1 -1
- package/build/index.js +19 -0
- package/build/index.js.map +1 -1
- package/build/mobile/bottom-sheet/cell.native.js +2 -2
- package/build/mobile/bottom-sheet/cell.native.js.map +1 -1
- package/build/mobile/image/index.native.js +1 -1
- package/build/mobile/image/index.native.js.map +1 -1
- package/build/mobile/link-picker/index.native.js +1 -1
- package/build/mobile/link-picker/index.native.js.map +1 -1
- package/build/navigation/menu/menu-title-search.js +1 -1
- package/build/navigation/menu/menu-title-search.js.map +1 -1
- package/build/palette-edit/index.js +4 -4
- package/build/palette-edit/index.js.map +1 -1
- package/build-module/box-control/input-control.js +2 -2
- package/build-module/box-control/input-control.js.map +1 -1
- package/build-module/calendar/date-calendar/index.js +51 -0
- package/build-module/calendar/date-calendar/index.js.map +1 -0
- package/build-module/calendar/date-range-calendar/index.js +157 -0
- package/build-module/calendar/date-range-calendar/index.js.map +1 -0
- package/build-module/calendar/index.js +4 -0
- package/build-module/calendar/index.js.map +1 -0
- package/build-module/calendar/types.js +2 -0
- package/build-module/calendar/types.js.map +1 -0
- package/build-module/calendar/utils/constants.js +61 -0
- package/build-module/calendar/utils/constants.js.map +1 -0
- package/build-module/calendar/utils/day-cell.js +131 -0
- package/build-module/calendar/utils/day-cell.js.map +1 -0
- package/build-module/calendar/utils/misc.js +4 -0
- package/build-module/calendar/utils/misc.js.map +1 -0
- package/build-module/calendar/utils/use-controlled-value.js +51 -0
- package/build-module/calendar/utils/use-controlled-value.js.map +1 -0
- package/build-module/calendar/utils/use-localization-props.js +154 -0
- package/build-module/calendar/utils/use-localization-props.js.map +1 -0
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js +1 -1
- package/build-module/custom-gradient-picker/gradient-bar/control-points.js.map +1 -1
- package/build-module/custom-select-control-v2/custom-select.js +4 -4
- package/build-module/custom-select-control-v2/custom-select.js.map +1 -1
- package/build-module/date-time/date/index.js +1 -1
- package/build-module/date-time/date/index.js.map +1 -1
- package/build-module/form-file-upload/index.js +4 -6
- package/build-module/form-file-upload/index.js.map +1 -1
- package/build-module/form-token-field/index.js +11 -1
- package/build-module/form-token-field/index.js.map +1 -1
- package/build-module/form-token-field/token.js +1 -1
- package/build-module/form-token-field/token.js.map +1 -1
- package/build-module/index.js +1 -0
- package/build-module/index.js.map +1 -1
- package/build-module/mobile/bottom-sheet/cell.native.js +2 -2
- package/build-module/mobile/bottom-sheet/cell.native.js.map +1 -1
- package/build-module/mobile/image/index.native.js +1 -1
- package/build-module/mobile/image/index.native.js.map +1 -1
- package/build-module/mobile/link-picker/index.native.js +1 -1
- package/build-module/mobile/link-picker/index.native.js.map +1 -1
- package/build-module/navigation/menu/menu-title-search.js +1 -1
- package/build-module/navigation/menu/menu-title-search.js.map +1 -1
- package/build-module/palette-edit/index.js +4 -4
- package/build-module/palette-edit/index.js.map +1 -1
- package/build-style/style-rtl.css +358 -5
- package/build-style/style.css +358 -5
- package/build-types/box-control/input-control.d.ts.map +1 -1
- package/build-types/box-control/utils.d.ts +7 -7
- package/build-types/calendar/date-calendar/index.d.ts +11 -0
- package/build-types/calendar/date-calendar/index.d.ts.map +1 -0
- package/build-types/calendar/date-range-calendar/index.d.ts +14 -0
- package/build-types/calendar/date-range-calendar/index.d.ts.map +1 -0
- package/build-types/calendar/index.d.ts +4 -0
- package/build-types/calendar/index.d.ts.map +1 -0
- package/build-types/calendar/stories/date-calendar.story.d.ts +16 -0
- package/build-types/calendar/stories/date-calendar.story.d.ts.map +1 -0
- package/build-types/calendar/stories/date-range-calendar.story.d.ts +16 -0
- package/build-types/calendar/stories/date-range-calendar.story.d.ts.map +1 -0
- package/build-types/calendar/test/__utils__/index.d.ts +10 -0
- package/build-types/calendar/test/__utils__/index.d.ts.map +1 -0
- package/build-types/calendar/test/date-calendar.d.ts +2 -0
- package/build-types/calendar/test/date-calendar.d.ts.map +1 -0
- package/build-types/calendar/test/date-range-calendar.d.ts +2 -0
- package/build-types/calendar/test/date-range-calendar.d.ts.map +1 -0
- package/build-types/calendar/types.d.ts +317 -0
- package/build-types/calendar/types.d.ts.map +1 -0
- package/build-types/calendar/utils/constants.d.ts +52 -0
- package/build-types/calendar/utils/constants.d.ts.map +1 -0
- package/build-types/calendar/utils/day-cell.d.ts +21 -0
- package/build-types/calendar/utils/day-cell.d.ts.map +1 -0
- package/build-types/calendar/utils/misc.d.ts +2 -0
- package/build-types/calendar/utils/misc.d.ts.map +1 -0
- package/build-types/calendar/utils/use-controlled-value.d.ts +27 -0
- package/build-types/calendar/utils/use-controlled-value.d.ts.map +1 -0
- package/build-types/calendar/utils/use-localization-props.d.ts +64 -0
- package/build-types/calendar/utils/use-localization-props.d.ts.map +1 -0
- package/build-types/custom-gradient-picker/constants.d.ts +6 -3
- package/build-types/custom-gradient-picker/constants.d.ts.map +1 -1
- package/build-types/custom-select-control-v2/custom-select.d.ts.map +1 -1
- package/build-types/dimension-control/sizes.d.ts +15 -3
- package/build-types/dimension-control/sizes.d.ts.map +1 -1
- package/build-types/font-size-picker/constants.d.ts +2 -2
- package/build-types/font-size-picker/constants.d.ts.map +1 -1
- package/build-types/form-file-upload/index.d.ts.map +1 -1
- package/build-types/form-token-field/index.d.ts.map +1 -1
- package/build-types/index.d.ts +1 -0
- package/build-types/index.d.ts.map +1 -1
- package/build-types/popover/overlay-middlewares.d.ts +6 -1
- package/build-types/popover/overlay-middlewares.d.ts.map +1 -1
- package/package.json +21 -20
- package/src/box-control/input-control.tsx +14 -5
- package/src/calendar/date-calendar/README.md +250 -0
- package/src/calendar/date-calendar/index.tsx +55 -0
- package/src/calendar/date-range-calendar/README.md +287 -0
- package/src/calendar/date-range-calendar/index.tsx +203 -0
- package/src/calendar/index.tsx +3 -0
- package/src/calendar/stories/date-calendar.story.tsx +221 -0
- package/src/calendar/stories/date-range-calendar.story.tsx +230 -0
- package/src/calendar/style.scss +431 -0
- package/src/calendar/test/__utils__/index.ts +56 -0
- package/src/calendar/test/date-calendar.tsx +975 -0
- package/src/calendar/test/date-range-calendar.tsx +1701 -0
- package/src/calendar/types.ts +342 -0
- package/src/calendar/utils/constants.ts +62 -0
- package/src/calendar/utils/day-cell.tsx +133 -0
- package/src/calendar/utils/misc.ts +3 -0
- package/src/calendar/utils/use-controlled-value.ts +61 -0
- package/src/calendar/utils/use-localization-props.ts +169 -0
- package/src/circular-option-picker/stories/index.story.tsx +2 -2
- package/src/custom-gradient-picker/gradient-bar/control-points.tsx +1 -1
- package/src/custom-select-control-v2/custom-select.tsx +6 -3
- package/src/date-time/date/index.tsx +1 -1
- package/src/form-file-upload/index.tsx +6 -12
- package/src/form-token-field/index.tsx +12 -1
- package/src/form-token-field/token.tsx +1 -1
- package/src/index.ts +1 -0
- package/src/mobile/bottom-sheet/cell.native.js +2 -2
- package/src/mobile/image/index.native.js +1 -1
- package/src/mobile/link-picker/index.native.js +1 -1
- package/src/navigation/menu/menu-title-search.tsx +1 -1
- package/src/palette-edit/index.tsx +4 -4
- package/src/select-control/style.scss +0 -6
- package/src/style.scss +1 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# `DateRangeCalendar`
|
|
2
|
+
|
|
3
|
+
`DateRangeCalendar` is a React component that provides a customizable calendar interface for **date range** selection.
|
|
4
|
+
|
|
5
|
+
The component is built with accessibility in mind and follows ARIA best practices for calendar widgets. It provides keyboard navigation, screen reader support, and customizable labels for internationalization.
|
|
6
|
+
|
|
7
|
+
## Usage example
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
import { DateRangeCalendar } from '@automattic/components';
|
|
11
|
+
|
|
12
|
+
type DateRange = {
|
|
13
|
+
from: Date | undefined;
|
|
14
|
+
to?: Date | undefined;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function MyComponent() {
|
|
18
|
+
const [ selected, setSelected ] = useState< DateRange >( {
|
|
19
|
+
from: new Date( date.getFullYear(), date.getMonth(), 1 ),
|
|
20
|
+
to: new Date(),
|
|
21
|
+
} );
|
|
22
|
+
|
|
23
|
+
return <DateRangeCalendar selected={ selected } onSelect={ setSelected } />;
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Props
|
|
28
|
+
|
|
29
|
+
These props are shared between both single date and date range calendar modes.
|
|
30
|
+
|
|
31
|
+
### `required`
|
|
32
|
+
|
|
33
|
+
- Type: `boolean`
|
|
34
|
+
- Required: No
|
|
35
|
+
- Default: `false`
|
|
36
|
+
|
|
37
|
+
Whether the selection is required. When `true`, there always needs to be a date selected.
|
|
38
|
+
|
|
39
|
+
### `selected`
|
|
40
|
+
|
|
41
|
+
- Type: `DateRange | undefined | null`
|
|
42
|
+
- Required: No
|
|
43
|
+
|
|
44
|
+
The selected date range. A `DateRange` object has the following shape:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
{
|
|
48
|
+
from: Date | undefined;
|
|
49
|
+
to?: Date | undefined;
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### `onSelect`
|
|
54
|
+
|
|
55
|
+
- Type: `(selected: DateRange | undefined, triggerDate: Date, modifiers: Modifiers, e: React.MouseEvent | React.KeyboardEvent) => void`
|
|
56
|
+
- Required: No
|
|
57
|
+
|
|
58
|
+
Event handler when the selection changes. The `selected` parameter will contain the new date range.
|
|
59
|
+
|
|
60
|
+
### `defaultSelected`
|
|
61
|
+
|
|
62
|
+
- Type: `DateRange`
|
|
63
|
+
- Required: No
|
|
64
|
+
|
|
65
|
+
The default selected range (for uncontrolled usage).
|
|
66
|
+
|
|
67
|
+
### `excludeDisabled`
|
|
68
|
+
|
|
69
|
+
- Type: `boolean`
|
|
70
|
+
- Required: No
|
|
71
|
+
|
|
72
|
+
When `true`, the range will reset when including a disabled day. This is useful to prevent users from selecting ranges that include unavailable dates.
|
|
73
|
+
|
|
74
|
+
### `min`
|
|
75
|
+
|
|
76
|
+
- Type: `number`
|
|
77
|
+
- Required: No
|
|
78
|
+
|
|
79
|
+
The minimum number of days to include in the range. If a user tries to select a range shorter than this, the selection will be adjusted to meet the minimum requirement.
|
|
80
|
+
|
|
81
|
+
### `max`
|
|
82
|
+
|
|
83
|
+
- Type: `number`
|
|
84
|
+
- Required: No
|
|
85
|
+
|
|
86
|
+
The maximum number of days to include in the range. If a user tries to select a range longer than this, the selection will be adjusted to meet the maximum requirement.
|
|
87
|
+
|
|
88
|
+
### `defaultMonth`
|
|
89
|
+
|
|
90
|
+
- Type: `Date`
|
|
91
|
+
- Required: No
|
|
92
|
+
- Default: Current month
|
|
93
|
+
|
|
94
|
+
The initial month to show in the calendar view (uncontrolled).
|
|
95
|
+
|
|
96
|
+
### `month`
|
|
97
|
+
|
|
98
|
+
- Type: `Date`
|
|
99
|
+
- Required: No
|
|
100
|
+
|
|
101
|
+
The month displayed in the calendar view (controlled). Use together with `onMonthChange` to change the month programmatically.
|
|
102
|
+
|
|
103
|
+
### `numberOfMonths`
|
|
104
|
+
|
|
105
|
+
- Type: `number`
|
|
106
|
+
- Required: No
|
|
107
|
+
- Default: `1`
|
|
108
|
+
|
|
109
|
+
The number of months displayed at once.
|
|
110
|
+
|
|
111
|
+
### `startMonth`
|
|
112
|
+
|
|
113
|
+
- Type: `Date`
|
|
114
|
+
- Required: No
|
|
115
|
+
|
|
116
|
+
The earliest month to start the month navigation.
|
|
117
|
+
|
|
118
|
+
### `endMonth`
|
|
119
|
+
|
|
120
|
+
- Type: `Date`
|
|
121
|
+
- Required: No
|
|
122
|
+
|
|
123
|
+
The latest month to end the month navigation.
|
|
124
|
+
|
|
125
|
+
### `autoFocus`
|
|
126
|
+
|
|
127
|
+
- Type: `boolean`
|
|
128
|
+
- Required: No
|
|
129
|
+
|
|
130
|
+
Focus the first selected day (if set) or today's date (if not disabled). Use this prop when you need to focus the calendar after a user action (e.g. opening the dialog with the calendar).
|
|
131
|
+
|
|
132
|
+
### `disabled`
|
|
133
|
+
|
|
134
|
+
- Type: `Matcher | Matcher[] | undefined`
|
|
135
|
+
- Required: No
|
|
136
|
+
|
|
137
|
+
Specify which days are disabled. Using `true` will disable all dates. See the [Matcher Types](#matcher-types) section for more details.
|
|
138
|
+
|
|
139
|
+
### `disableNavigation`
|
|
140
|
+
|
|
141
|
+
- Type: `boolean`
|
|
142
|
+
- Required: No
|
|
143
|
+
|
|
144
|
+
Disable the navigation buttons.
|
|
145
|
+
|
|
146
|
+
### `labels`
|
|
147
|
+
|
|
148
|
+
- Type: `object`
|
|
149
|
+
- Required: No
|
|
150
|
+
|
|
151
|
+
Use custom labels for internationalization. All labels are optional and have sensible defaults:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
{
|
|
155
|
+
labelNav?: () => string; // Navigation toolbar label
|
|
156
|
+
labelGrid?: (date: Date) => string; // Month grid label (default: "LLLL y")
|
|
157
|
+
labelGridcell?: (date: Date, modifiers?: Modifiers) => string; // Grid cell label
|
|
158
|
+
labelNext?: (month: Date | undefined) => string; // Next month button label
|
|
159
|
+
labelPrevious?: (month: Date | undefined) => string; // Previous month button label
|
|
160
|
+
labelDayButton?: (date: Date, modifiers?: Modifiers) => string; // Day button label
|
|
161
|
+
labelWeekday?: (date: Date) => string; // Weekday label
|
|
162
|
+
}
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
**Important: For a correct localized experience, consumers should make sure the locale used for the translated labels and `locale` prop are consistent.**
|
|
166
|
+
|
|
167
|
+
### `locale`
|
|
168
|
+
|
|
169
|
+
- Type: `Locale`
|
|
170
|
+
- Required: No
|
|
171
|
+
- Default: `enUS` from `@date-fns/locale`
|
|
172
|
+
|
|
173
|
+
The locale object used to localize dates. Pass a locale from `@date-fns/locale` to localize the calendar.
|
|
174
|
+
|
|
175
|
+
**Important: For a correct localized experience, consumers should make sure the locale used for the translated labels and `locale` prop are consistent.**
|
|
176
|
+
|
|
177
|
+
### `weekStartsOn`
|
|
178
|
+
|
|
179
|
+
- Type: `0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined`
|
|
180
|
+
- Required: No
|
|
181
|
+
- Default: Based on the `locale` prop
|
|
182
|
+
|
|
183
|
+
The index of the first day of the week (0 - Sunday). Overrides the locale's setting.
|
|
184
|
+
|
|
185
|
+
### `onMonthChange`
|
|
186
|
+
|
|
187
|
+
- Type: `(month: Date) => void`
|
|
188
|
+
- Required: No
|
|
189
|
+
|
|
190
|
+
Event fired when the user navigates between months.
|
|
191
|
+
|
|
192
|
+
### `timeZone`
|
|
193
|
+
|
|
194
|
+
- Type: `string`
|
|
195
|
+
- Required: No
|
|
196
|
+
|
|
197
|
+
The time zone (IANA or UTC offset) to use in the calendar. See [Wikipedia](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) for possible values.
|
|
198
|
+
|
|
199
|
+
When working with time zones, use the `TZDate` object exported by this package instead of the native `Date` object.
|
|
200
|
+
|
|
201
|
+
```tsx
|
|
202
|
+
import { DateRangeCalendar, TZDate } from '@automattic/components';
|
|
203
|
+
|
|
204
|
+
export function WithTimeZone() {
|
|
205
|
+
const timeZone = 'America/New_York';
|
|
206
|
+
const [ selected, setSelected ] = useState< Date | undefined >( {
|
|
207
|
+
from: new TZDate( 2024, 12, 10, timeZone ), // Use `TZDate` instead of `Date`
|
|
208
|
+
to: new TZDate( 2024, 12, 8, timeZone ), // Use `TZDate` instead of `Date`
|
|
209
|
+
} );
|
|
210
|
+
return <DateRangeCalendar timeZone={ timeZone } selected={ selected } onSelect={ setSelected } />;
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
### `role`
|
|
215
|
+
|
|
216
|
+
- Type: `'application' | 'dialog' | undefined`
|
|
217
|
+
- Required: No
|
|
218
|
+
- Default: `'application'`
|
|
219
|
+
|
|
220
|
+
The role attribute to add to the container element.
|
|
221
|
+
|
|
222
|
+
## Matcher Types
|
|
223
|
+
|
|
224
|
+
The calendar component uses a flexible matching system to determine which days should be disabled or have specific modifiers. Here are the available matcher types:
|
|
225
|
+
|
|
226
|
+
### Boolean Matcher
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
const booleanMatcher: Matcher = true; // Will always match the day
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Date Matcher
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
const dateMatcher: Matcher = new Date(); // Will match today's date
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### Array Matcher
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const arrayMatcher: Matcher = [ new Date( 2019, 1, 2 ), new Date( 2019, 1, 4 ) ]; // Will match the days in the array
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Date After Matcher
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
const afterMatcher: DateAfter = { after: new Date( 2019, 1, 2 ) }; // Will match days after the 2nd of February 2019
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Date Before Matcher
|
|
251
|
+
|
|
252
|
+
```typescript
|
|
253
|
+
const beforeMatcher: DateBefore = { before: new Date( 2019, 1, 2 ) }; // Will match days before the 2nd of February 2019
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Date Interval Matcher
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const intervalMatcher: DateInterval = {
|
|
260
|
+
after: new Date( 2019, 1, 2 ),
|
|
261
|
+
before: new Date( 2019, 1, 5 ),
|
|
262
|
+
}; // Will match the days between the 2nd and the 5th of February 2019 (exclusive)
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Date Range Matcher
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
const rangeMatcher: DateRange = {
|
|
269
|
+
from: new Date( 2019, 1, 2 ),
|
|
270
|
+
to: new Date( 2019, 1, 5 ),
|
|
271
|
+
}; // Will match the days between the 2nd and the 5th of February 2019 (inclusive)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
### Day of Week Matcher
|
|
275
|
+
|
|
276
|
+
```typescript
|
|
277
|
+
const dayOfWeekMatcher: DayOfWeek = { dayOfWeek: 0 }; // Will match Sundays
|
|
278
|
+
const weekendMatcher: DayOfWeek = { dayOfWeek: [ 0, 6 ] }; // Will match weekends
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Function Matcher
|
|
282
|
+
|
|
283
|
+
```typescript
|
|
284
|
+
const functionMatcher: Matcher = ( day: Date ) => {
|
|
285
|
+
return day.getMonth() === 2; // Will match when month is March
|
|
286
|
+
};
|
|
287
|
+
```
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { differenceInCalendarDays } from 'date-fns';
|
|
5
|
+
import { DayPicker, rangeContainsModifiers } from 'react-day-picker';
|
|
6
|
+
import { enUS } from 'react-day-picker/locale';
|
|
7
|
+
/**
|
|
8
|
+
* WordPress dependencies
|
|
9
|
+
*/
|
|
10
|
+
import { useMemo, useState } from '@wordpress/element';
|
|
11
|
+
/**
|
|
12
|
+
* Internal dependencies
|
|
13
|
+
*/
|
|
14
|
+
import { COMMON_PROPS, MODIFIER_CLASSNAMES } from '../utils/constants';
|
|
15
|
+
import { clampNumberOfMonths } from '../utils/misc';
|
|
16
|
+
import { useControlledValue } from '../utils/use-controlled-value';
|
|
17
|
+
import { useLocalizationProps } from '../utils/use-localization-props';
|
|
18
|
+
import type { DateRangeCalendarProps, DateRange } from '../types';
|
|
19
|
+
|
|
20
|
+
export function usePreviewRange( {
|
|
21
|
+
selected,
|
|
22
|
+
hoveredDate,
|
|
23
|
+
excludeDisabled,
|
|
24
|
+
min,
|
|
25
|
+
max,
|
|
26
|
+
disabled,
|
|
27
|
+
}: Pick<
|
|
28
|
+
DateRangeCalendarProps,
|
|
29
|
+
'selected' | 'excludeDisabled' | 'min' | 'max' | 'disabled'
|
|
30
|
+
> & {
|
|
31
|
+
hoveredDate: Date | undefined;
|
|
32
|
+
} ) {
|
|
33
|
+
return useMemo( () => {
|
|
34
|
+
if ( ! hoveredDate || ! selected?.from ) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let previewHighlight: DateRange | undefined;
|
|
39
|
+
let potentialNewRange: { from: Date; to: Date } | undefined;
|
|
40
|
+
|
|
41
|
+
// Hovering on a date before the start of the selected range
|
|
42
|
+
if ( hoveredDate < selected.from ) {
|
|
43
|
+
previewHighlight = {
|
|
44
|
+
from: hoveredDate,
|
|
45
|
+
to: selected.from,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
potentialNewRange = {
|
|
49
|
+
from: hoveredDate,
|
|
50
|
+
to: selected.to ?? selected.from,
|
|
51
|
+
};
|
|
52
|
+
} else if (
|
|
53
|
+
selected.to &&
|
|
54
|
+
hoveredDate > selected.from &&
|
|
55
|
+
hoveredDate < selected.to
|
|
56
|
+
) {
|
|
57
|
+
// Hovering on a date between the start and end of the selected range
|
|
58
|
+
previewHighlight = {
|
|
59
|
+
from: selected.from,
|
|
60
|
+
to: hoveredDate,
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
potentialNewRange = {
|
|
64
|
+
from: selected.from,
|
|
65
|
+
to: hoveredDate,
|
|
66
|
+
};
|
|
67
|
+
} else if ( hoveredDate > selected.from ) {
|
|
68
|
+
// Hovering on a date after the end of the selected range (either
|
|
69
|
+
// because it's greater than selected.to, or because it's not defined)
|
|
70
|
+
previewHighlight = {
|
|
71
|
+
from: selected.to ?? selected.from,
|
|
72
|
+
to: hoveredDate,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
potentialNewRange = {
|
|
76
|
+
from: selected.from,
|
|
77
|
+
to: hoveredDate,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (
|
|
82
|
+
min !== undefined &&
|
|
83
|
+
min > 0 &&
|
|
84
|
+
potentialNewRange &&
|
|
85
|
+
differenceInCalendarDays(
|
|
86
|
+
potentialNewRange.to,
|
|
87
|
+
potentialNewRange.from
|
|
88
|
+
) < min
|
|
89
|
+
) {
|
|
90
|
+
previewHighlight = {
|
|
91
|
+
from: hoveredDate,
|
|
92
|
+
to: hoveredDate,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (
|
|
97
|
+
max !== undefined &&
|
|
98
|
+
max > 0 &&
|
|
99
|
+
potentialNewRange &&
|
|
100
|
+
differenceInCalendarDays(
|
|
101
|
+
potentialNewRange.to,
|
|
102
|
+
potentialNewRange.from
|
|
103
|
+
) > max
|
|
104
|
+
) {
|
|
105
|
+
previewHighlight = {
|
|
106
|
+
from: hoveredDate,
|
|
107
|
+
to: hoveredDate,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
excludeDisabled &&
|
|
113
|
+
disabled &&
|
|
114
|
+
potentialNewRange &&
|
|
115
|
+
rangeContainsModifiers( potentialNewRange, disabled )
|
|
116
|
+
) {
|
|
117
|
+
previewHighlight = {
|
|
118
|
+
from: hoveredDate,
|
|
119
|
+
to: hoveredDate,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return previewHighlight;
|
|
124
|
+
}, [ selected, hoveredDate, excludeDisabled, min, max, disabled ] );
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* `DateRangeCalendar` is a React component that provides a customizable calendar
|
|
129
|
+
* interface for **date range** selection.
|
|
130
|
+
*
|
|
131
|
+
* The component is built with accessibility in mind and follows ARIA best
|
|
132
|
+
* practices for calendar widgets. It provides keyboard navigation, screen reader
|
|
133
|
+
* support, and customizable labels for internationalization.
|
|
134
|
+
*/
|
|
135
|
+
export const DateRangeCalendar = ( {
|
|
136
|
+
defaultSelected,
|
|
137
|
+
selected: selectedProp,
|
|
138
|
+
onSelect,
|
|
139
|
+
numberOfMonths = 1,
|
|
140
|
+
excludeDisabled,
|
|
141
|
+
min,
|
|
142
|
+
max,
|
|
143
|
+
disabled,
|
|
144
|
+
locale = enUS,
|
|
145
|
+
timeZone,
|
|
146
|
+
...props
|
|
147
|
+
}: DateRangeCalendarProps ) => {
|
|
148
|
+
const localizationProps = useLocalizationProps( {
|
|
149
|
+
locale,
|
|
150
|
+
timeZone,
|
|
151
|
+
mode: 'range',
|
|
152
|
+
} );
|
|
153
|
+
|
|
154
|
+
const [ selected, setSelected ] = useControlledValue<
|
|
155
|
+
DateRange | undefined
|
|
156
|
+
>( {
|
|
157
|
+
defaultValue: defaultSelected,
|
|
158
|
+
value: selectedProp,
|
|
159
|
+
onChange: onSelect,
|
|
160
|
+
} );
|
|
161
|
+
|
|
162
|
+
const [ hoveredDate, setHoveredDate ] = useState< Date | undefined >(
|
|
163
|
+
undefined
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
// Compute the preview range for hover effect
|
|
167
|
+
const previewRange = usePreviewRange( {
|
|
168
|
+
selected,
|
|
169
|
+
hoveredDate,
|
|
170
|
+
excludeDisabled,
|
|
171
|
+
min,
|
|
172
|
+
max,
|
|
173
|
+
disabled,
|
|
174
|
+
} );
|
|
175
|
+
|
|
176
|
+
const modifiers = useMemo( () => {
|
|
177
|
+
return {
|
|
178
|
+
preview: previewRange,
|
|
179
|
+
preview_start: previewRange?.from,
|
|
180
|
+
preview_end: previewRange?.to,
|
|
181
|
+
};
|
|
182
|
+
}, [ previewRange ] );
|
|
183
|
+
|
|
184
|
+
return (
|
|
185
|
+
<DayPicker
|
|
186
|
+
{ ...COMMON_PROPS }
|
|
187
|
+
{ ...localizationProps }
|
|
188
|
+
{ ...props }
|
|
189
|
+
mode="range"
|
|
190
|
+
numberOfMonths={ clampNumberOfMonths( numberOfMonths ) }
|
|
191
|
+
disabled={ disabled }
|
|
192
|
+
excludeDisabled={ excludeDisabled }
|
|
193
|
+
min={ min }
|
|
194
|
+
max={ max }
|
|
195
|
+
selected={ selected }
|
|
196
|
+
onSelect={ setSelected }
|
|
197
|
+
onDayMouseEnter={ ( date ) => setHoveredDate( date ) }
|
|
198
|
+
onDayMouseLeave={ () => setHoveredDate( undefined ) }
|
|
199
|
+
modifiers={ modifiers }
|
|
200
|
+
modifiersClassNames={ MODIFIER_CLASSNAMES }
|
|
201
|
+
/>
|
|
202
|
+
);
|
|
203
|
+
};
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External dependencies
|
|
3
|
+
*/
|
|
4
|
+
import { fn } from '@storybook/test';
|
|
5
|
+
import {
|
|
6
|
+
enUS,
|
|
7
|
+
fr,
|
|
8
|
+
es,
|
|
9
|
+
de,
|
|
10
|
+
it,
|
|
11
|
+
he,
|
|
12
|
+
ru,
|
|
13
|
+
ja,
|
|
14
|
+
ptBR,
|
|
15
|
+
nl,
|
|
16
|
+
ko,
|
|
17
|
+
tr,
|
|
18
|
+
id,
|
|
19
|
+
zhCN,
|
|
20
|
+
zhTW,
|
|
21
|
+
ar,
|
|
22
|
+
sv,
|
|
23
|
+
} from 'date-fns/locale';
|
|
24
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* WordPress dependencies
|
|
28
|
+
*/
|
|
29
|
+
import { useState, useEffect } from '@wordpress/element';
|
|
30
|
+
/**
|
|
31
|
+
* Internal dependencies
|
|
32
|
+
*/
|
|
33
|
+
import { DateCalendar, TZDate } from '..';
|
|
34
|
+
|
|
35
|
+
const meta: Meta< typeof DateCalendar > = {
|
|
36
|
+
title: 'Components/Selection & Input/Time & Date/DateCalendar',
|
|
37
|
+
component: DateCalendar,
|
|
38
|
+
argTypes: {
|
|
39
|
+
locale: {
|
|
40
|
+
options: [
|
|
41
|
+
'English (US)',
|
|
42
|
+
'French',
|
|
43
|
+
'Spanish',
|
|
44
|
+
'German',
|
|
45
|
+
'Italian',
|
|
46
|
+
'Hebrew',
|
|
47
|
+
'Russian',
|
|
48
|
+
'Japanese',
|
|
49
|
+
'Portuguese (Brazil)',
|
|
50
|
+
'Dutch',
|
|
51
|
+
'Korean',
|
|
52
|
+
'Turkish',
|
|
53
|
+
'Indonesian',
|
|
54
|
+
'Chinese (Simplified)',
|
|
55
|
+
'Chinese (Traditional)',
|
|
56
|
+
'Arabic',
|
|
57
|
+
'Swedish',
|
|
58
|
+
],
|
|
59
|
+
mapping: {
|
|
60
|
+
'English (US)': enUS,
|
|
61
|
+
French: fr,
|
|
62
|
+
Spanish: es,
|
|
63
|
+
German: de,
|
|
64
|
+
Italian: it,
|
|
65
|
+
Hebrew: he,
|
|
66
|
+
Russian: ru,
|
|
67
|
+
Japanese: ja,
|
|
68
|
+
'Portuguese (Brazil)': ptBR,
|
|
69
|
+
Dutch: nl,
|
|
70
|
+
Korean: ko,
|
|
71
|
+
Turkish: tr,
|
|
72
|
+
Indonesian: id,
|
|
73
|
+
'Chinese (Simplified)': zhCN,
|
|
74
|
+
'Chinese (Traditional)': zhTW,
|
|
75
|
+
Arabic: ar,
|
|
76
|
+
Swedish: sv,
|
|
77
|
+
},
|
|
78
|
+
control: 'select',
|
|
79
|
+
},
|
|
80
|
+
timeZone: {
|
|
81
|
+
options: [
|
|
82
|
+
'Pacific/Honolulu',
|
|
83
|
+
'America/New_York',
|
|
84
|
+
'Europe/London',
|
|
85
|
+
'Asia/Tokyo',
|
|
86
|
+
'Pacific/Auckland',
|
|
87
|
+
],
|
|
88
|
+
control: 'select',
|
|
89
|
+
},
|
|
90
|
+
labels: {
|
|
91
|
+
control: false,
|
|
92
|
+
},
|
|
93
|
+
defaultSelected: { control: 'date' },
|
|
94
|
+
selected: { control: 'date' },
|
|
95
|
+
onSelect: {
|
|
96
|
+
control: false,
|
|
97
|
+
},
|
|
98
|
+
defaultMonth: { control: 'date' },
|
|
99
|
+
month: { control: 'date' },
|
|
100
|
+
onMonthChange: {
|
|
101
|
+
control: false,
|
|
102
|
+
},
|
|
103
|
+
endMonth: { control: 'date' },
|
|
104
|
+
startMonth: { control: 'date' },
|
|
105
|
+
},
|
|
106
|
+
args: {
|
|
107
|
+
onMonthChange: fn(),
|
|
108
|
+
onSelect: fn(),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
export default meta;
|
|
112
|
+
|
|
113
|
+
type Story = StoryObj< typeof DateCalendar >;
|
|
114
|
+
|
|
115
|
+
export const Default: Story = {};
|
|
116
|
+
|
|
117
|
+
export const DisabledDates: Story = {
|
|
118
|
+
args: {
|
|
119
|
+
disabled: [
|
|
120
|
+
// Disable tomorrow (single date)
|
|
121
|
+
new Date( new Date().setDate( new Date().getDate() + 1 ) ),
|
|
122
|
+
// Disable all dates after Feb 1st of next year
|
|
123
|
+
{ after: new Date( new Date().getFullYear() + 1, 1, 1 ) },
|
|
124
|
+
// Disable all dates before Dec 1st of last year
|
|
125
|
+
{ before: new Date( new Date().getFullYear() - 1, 11, 1 ) },
|
|
126
|
+
// Disable all dates between 12th and 14th of August of this year
|
|
127
|
+
{
|
|
128
|
+
after: new Date( new Date().getFullYear(), 7, 11 ),
|
|
129
|
+
before: new Date( new Date().getFullYear(), 7, 15 ),
|
|
130
|
+
},
|
|
131
|
+
// Disable all dates between 21st and 26th of October of this year
|
|
132
|
+
{
|
|
133
|
+
from: new Date( new Date().getFullYear(), 9, 21 ),
|
|
134
|
+
to: new Date( new Date().getFullYear(), 9, 26 ),
|
|
135
|
+
},
|
|
136
|
+
// Disable all Wednesdays
|
|
137
|
+
{ dayOfWeek: 3 },
|
|
138
|
+
// Disable all prime day numbers
|
|
139
|
+
function isPrimeDate( date: Date ) {
|
|
140
|
+
return [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 ].includes(
|
|
141
|
+
date.getDate()
|
|
142
|
+
);
|
|
143
|
+
},
|
|
144
|
+
],
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const nextMonth = new Date().getMonth() === 11 ? 0 : new Date().getMonth() + 1;
|
|
149
|
+
const nextMonthYear =
|
|
150
|
+
new Date().getMonth() === 11
|
|
151
|
+
? new Date().getFullYear() + 1
|
|
152
|
+
: new Date().getFullYear();
|
|
153
|
+
const firstDayOfNextMonth = new Date( nextMonthYear, nextMonth, 1 );
|
|
154
|
+
export const WithSelectedDateAndMonth: Story = {
|
|
155
|
+
args: {
|
|
156
|
+
defaultSelected: firstDayOfNextMonth,
|
|
157
|
+
defaultMonth: firstDayOfNextMonth,
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* When working with time zones, use the `TZDate` object exported by this package instead of the native `Date` object.
|
|
163
|
+
*/
|
|
164
|
+
export const WithTimeZone: Story = {
|
|
165
|
+
render: function DateCalendarWithTimeZone( args ) {
|
|
166
|
+
const [ selected, setSelected ] = useState< TZDate | null >( null );
|
|
167
|
+
|
|
168
|
+
useEffect( () => {
|
|
169
|
+
setSelected(
|
|
170
|
+
// Select one week from today every time the time zone changes.
|
|
171
|
+
new TZDate(
|
|
172
|
+
new Date().setDate( new Date().getDate() + 7 ),
|
|
173
|
+
args.timeZone
|
|
174
|
+
)
|
|
175
|
+
);
|
|
176
|
+
}, [ args.timeZone ] );
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<>
|
|
180
|
+
<DateCalendar
|
|
181
|
+
{ ...args }
|
|
182
|
+
selected={ selected }
|
|
183
|
+
onSelect={ ( selectedDate, ...rest ) => {
|
|
184
|
+
setSelected(
|
|
185
|
+
selectedDate
|
|
186
|
+
? new TZDate( selectedDate, args.timeZone )
|
|
187
|
+
: null
|
|
188
|
+
);
|
|
189
|
+
args.onSelect?.( selectedDate, ...rest );
|
|
190
|
+
} }
|
|
191
|
+
disabled={ [
|
|
192
|
+
{
|
|
193
|
+
// Disable any date before today
|
|
194
|
+
before: new TZDate( new Date(), args.timeZone ),
|
|
195
|
+
},
|
|
196
|
+
] }
|
|
197
|
+
/>
|
|
198
|
+
|
|
199
|
+
<p>
|
|
200
|
+
Calendar set to { args.timeZone ?? 'current' } timezone,
|
|
201
|
+
disabling selection for all dates before today, and starting
|
|
202
|
+
with a default date of 1 week from today.
|
|
203
|
+
</p>
|
|
204
|
+
</>
|
|
205
|
+
);
|
|
206
|
+
},
|
|
207
|
+
args: {
|
|
208
|
+
timeZone: 'Pacific/Auckland',
|
|
209
|
+
},
|
|
210
|
+
argTypes: {
|
|
211
|
+
selected: {
|
|
212
|
+
control: false,
|
|
213
|
+
},
|
|
214
|
+
defaultSelected: {
|
|
215
|
+
control: false,
|
|
216
|
+
},
|
|
217
|
+
disabled: {
|
|
218
|
+
control: false,
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
};
|