dce-reactkit 4.1.0 → 4.1.1
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/dist/cjs/index.js +183 -40
- package/dist/cjs/index.js.map +1 -1
- package/dist/esm/index.js +183 -40
- package/dist/esm/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/SimpleDateChooser.tsx +337 -95
package/package.json
CHANGED
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
// Import React
|
|
8
|
-
import React from 'react';
|
|
8
|
+
import React, { useReducer } from 'react';
|
|
9
|
+
|
|
10
|
+
// Import AppWrapper helpers
|
|
11
|
+
import { confirm } from './AppWrapper';
|
|
9
12
|
|
|
10
13
|
// Import helpers
|
|
11
14
|
import getOrdinal from '../helpers/getOrdinal';
|
|
@@ -51,32 +54,86 @@ type Props = {
|
|
|
51
54
|
};
|
|
52
55
|
|
|
53
56
|
/*------------------------------------------------------------------------*/
|
|
54
|
-
/*
|
|
57
|
+
/* -------------------------------- State ------------------------------- */
|
|
55
58
|
/*------------------------------------------------------------------------*/
|
|
56
59
|
|
|
57
|
-
|
|
58
|
-
/*------------------------------------------------------------------------*/
|
|
59
|
-
/* -------------------------------- Setup ------------------------------- */
|
|
60
|
-
/*------------------------------------------------------------------------*/
|
|
60
|
+
/* -------------- Views ------------- */
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
enum View {
|
|
63
|
+
// Date chooser
|
|
64
|
+
DateChooser = 'DateChooser',
|
|
65
|
+
// Invalid date
|
|
66
|
+
InvalidDate = 'InvalidDate',
|
|
67
|
+
}
|
|
63
68
|
|
|
69
|
+
/* -------- State Definition -------- */
|
|
70
|
+
|
|
71
|
+
type State = {
|
|
72
|
+
view: View,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/* ------------- Actions ------------ */
|
|
76
|
+
|
|
77
|
+
// Types of actions
|
|
78
|
+
enum ActionType {
|
|
79
|
+
// Fix invalid date so it is now in range
|
|
80
|
+
FixInvalidDate = 'FixInvalidDate',
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Action definitions
|
|
84
|
+
type Action = {
|
|
85
|
+
// Action type
|
|
86
|
+
type: ActionType.FixInvalidDate,
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Reducer that executes actions
|
|
91
|
+
* @author Gardenia Liu
|
|
92
|
+
* @param state current state
|
|
93
|
+
* @param action action to execute
|
|
94
|
+
* @returns updated state
|
|
95
|
+
*/
|
|
96
|
+
const reducer = (state: State, action: Action): State => {
|
|
97
|
+
switch (action.type) {
|
|
98
|
+
case ActionType.FixInvalidDate: {
|
|
99
|
+
return {
|
|
100
|
+
...state,
|
|
101
|
+
view: View.DateChooser,
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
default: {
|
|
105
|
+
return state;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/*------------------------------------------------------------------------*/
|
|
111
|
+
/* --------------------------- Static Helpers --------------------------- */
|
|
112
|
+
/*------------------------------------------------------------------------*/
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Get the list of choices in the date chooser given props
|
|
116
|
+
* @author Gardenia Liu
|
|
117
|
+
* @author Gabe Abrams
|
|
118
|
+
* @param opts object containing all arguments
|
|
119
|
+
* @param opts.numMonthsToShow number of months to show
|
|
120
|
+
* @param opts.dontAllowPast if true, the user isn't allowed to select dates in the past
|
|
121
|
+
* @param opts.dontAllowFuture if true, the user isn't allowed to select dates in the future
|
|
122
|
+
* @returns choices
|
|
123
|
+
*/
|
|
124
|
+
const getChoices = (
|
|
125
|
+
opts: {
|
|
126
|
+
numMonthsToShow?: number,
|
|
127
|
+
dontAllowPast?: boolean,
|
|
128
|
+
dontAllowFuture?: boolean,
|
|
129
|
+
},
|
|
130
|
+
) => {
|
|
131
|
+
// Destructure props
|
|
64
132
|
const {
|
|
65
|
-
ariaLabel,
|
|
66
|
-
name,
|
|
67
|
-
onChange,
|
|
68
|
-
numMonthsToShow = 6,
|
|
69
133
|
dontAllowFuture,
|
|
70
134
|
dontAllowPast,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
/*------------------------------------------------------------------------*/
|
|
74
|
-
/* ------------------------------- Render ------------------------------- */
|
|
75
|
-
/*------------------------------------------------------------------------*/
|
|
76
|
-
|
|
77
|
-
/*----------------------------------------*/
|
|
78
|
-
/* --------------- Main UI -------------- */
|
|
79
|
-
/*----------------------------------------*/
|
|
135
|
+
numMonthsToShow = 6,
|
|
136
|
+
} = opts;
|
|
80
137
|
|
|
81
138
|
// Determine the set of choices allowed
|
|
82
139
|
const today = getTimeInfoInET();
|
|
@@ -171,93 +228,278 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
171
228
|
});
|
|
172
229
|
}
|
|
173
230
|
|
|
174
|
-
//
|
|
231
|
+
// Return choices
|
|
232
|
+
return choices;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Checks whether a given date is outside the valid range of allowed choices
|
|
237
|
+
* @author Gardenia Liu
|
|
238
|
+
* @param opts object containing all arguments
|
|
239
|
+
* @param opts.month 1-indexed month
|
|
240
|
+
* @param opts.day day of the month
|
|
241
|
+
* @param opts.year full year
|
|
242
|
+
* @param opts.choices valid date choices
|
|
243
|
+
* @returns true if date is out of range
|
|
244
|
+
*/
|
|
245
|
+
const isDateOutOfRange = (
|
|
246
|
+
opts: {
|
|
247
|
+
month: number,
|
|
248
|
+
day: number,
|
|
249
|
+
year: number,
|
|
250
|
+
choices: {
|
|
251
|
+
month: number,
|
|
252
|
+
year: number,
|
|
253
|
+
days: number[],
|
|
254
|
+
}[],
|
|
255
|
+
},
|
|
256
|
+
): boolean => {
|
|
175
257
|
const {
|
|
176
258
|
month,
|
|
177
259
|
day,
|
|
178
260
|
year,
|
|
261
|
+
choices,
|
|
262
|
+
} = opts;
|
|
263
|
+
|
|
264
|
+
return !choices.some((choice) => {
|
|
265
|
+
return (
|
|
266
|
+
choice.month === month
|
|
267
|
+
&& choice.year === year
|
|
268
|
+
&& choice.days.includes(day)
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
/*------------------------------------------------------------------------*/
|
|
274
|
+
/* ------------------------------ Component ----------------------------- */
|
|
275
|
+
/*------------------------------------------------------------------------*/
|
|
276
|
+
|
|
277
|
+
const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
278
|
+
/*------------------------------------------------------------------------*/
|
|
279
|
+
/* -------------------------------- Setup ------------------------------- */
|
|
280
|
+
/*------------------------------------------------------------------------*/
|
|
281
|
+
|
|
282
|
+
/* -------------- Props ------------- */
|
|
283
|
+
|
|
284
|
+
const {
|
|
285
|
+
ariaLabel,
|
|
286
|
+
name,
|
|
287
|
+
dontAllowPast,
|
|
288
|
+
dontAllowFuture,
|
|
289
|
+
numMonthsToShow,
|
|
290
|
+
onChange,
|
|
291
|
+
month,
|
|
292
|
+
day,
|
|
293
|
+
year,
|
|
179
294
|
} = props;
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
choices
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
295
|
+
|
|
296
|
+
// Get choices
|
|
297
|
+
const choices = getChoices({
|
|
298
|
+
numMonthsToShow,
|
|
299
|
+
dontAllowPast,
|
|
300
|
+
dontAllowFuture,
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
/* -------------- State ------------- */
|
|
304
|
+
|
|
305
|
+
// Check if the currently selected date is out of range
|
|
306
|
+
const currentSelectedDateOutOfRange = isDateOutOfRange({
|
|
307
|
+
month,
|
|
308
|
+
day,
|
|
309
|
+
year,
|
|
310
|
+
choices,
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// Initial state
|
|
314
|
+
const initialState: State = {
|
|
315
|
+
view: (
|
|
316
|
+
currentSelectedDateOutOfRange
|
|
317
|
+
? View.InvalidDate
|
|
318
|
+
: View.DateChooser
|
|
319
|
+
),
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
// Initialize state
|
|
323
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
324
|
+
|
|
325
|
+
// Destructure common state
|
|
326
|
+
const {
|
|
327
|
+
view,
|
|
328
|
+
} = state;
|
|
329
|
+
|
|
330
|
+
/*------------------------------------------------------------------------*/
|
|
331
|
+
/* ------------------------- Component Functions ------------------------ */
|
|
332
|
+
/*------------------------------------------------------------------------*/
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Ask the user if they want to edit an invalid date
|
|
336
|
+
* @author Gardenia Liu
|
|
337
|
+
* @author Gabe Abrams
|
|
338
|
+
*/
|
|
339
|
+
const askToEditInvalidDate = async () => {
|
|
340
|
+
// Ask the user if they want to edit the date
|
|
341
|
+
const confirmed = await confirm(
|
|
342
|
+
'Are you sure?',
|
|
343
|
+
'The current date is outside the normal range. If you edit it, you\'ll need to choose a new date in the normal range.',
|
|
344
|
+
{
|
|
345
|
+
confirmButtonText: 'Edit Date',
|
|
346
|
+
},
|
|
195
347
|
);
|
|
196
348
|
|
|
197
|
-
//
|
|
198
|
-
if (
|
|
199
|
-
//
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
aria-label={`choose date ${dayChoice}`}
|
|
207
|
-
>
|
|
208
|
-
{dayChoice}
|
|
209
|
-
{ordinal}
|
|
210
|
-
</option>,
|
|
211
|
-
);
|
|
349
|
+
// Check if user confirmed
|
|
350
|
+
if (confirmed) {
|
|
351
|
+
// Update the date to today
|
|
352
|
+
const today = getTimeInfoInET();
|
|
353
|
+
onChange(today.month, today.day, today.year);
|
|
354
|
+
|
|
355
|
+
// Update state
|
|
356
|
+
dispatch({
|
|
357
|
+
type: ActionType.FixInvalidDate,
|
|
212
358
|
});
|
|
213
359
|
}
|
|
214
|
-
}
|
|
360
|
+
};
|
|
215
361
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
362
|
+
/*------------------------------------------------------------------------*/
|
|
363
|
+
/* ------------------------------- Render ------------------------------- */
|
|
364
|
+
/*------------------------------------------------------------------------*/
|
|
365
|
+
|
|
366
|
+
/*----------------------------------------*/
|
|
367
|
+
/* ---------------- Views --------------- */
|
|
368
|
+
/*----------------------------------------*/
|
|
369
|
+
|
|
370
|
+
// Body that will be filled with the current view
|
|
371
|
+
let body: React.ReactNode;
|
|
372
|
+
|
|
373
|
+
/* ---------- DateChooser ---------- */
|
|
374
|
+
|
|
375
|
+
if (view === View.DateChooser) {
|
|
376
|
+
// Create lists of options
|
|
377
|
+
const monthOptions: React.ReactNode[] = [];
|
|
378
|
+
const dayOptions: React.ReactNode[] = [];
|
|
379
|
+
|
|
380
|
+
// Render each option and add it to the list
|
|
381
|
+
choices.forEach((choice) => {
|
|
382
|
+
// Create month option
|
|
383
|
+
monthOptions.push(
|
|
384
|
+
<option
|
|
385
|
+
key={`${choice.year}-${choice.month}`}
|
|
386
|
+
value={`${choice.month}-${choice.year}`}
|
|
387
|
+
aria-label={`choose ${choice.choiceName}`}
|
|
388
|
+
onSelect={() => {
|
|
389
|
+
onChange(choice.month, choice.days[0], choice.year);
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
{choice.choiceName}
|
|
393
|
+
</option>,
|
|
394
|
+
);
|
|
395
|
+
|
|
396
|
+
// This is the currently selected month
|
|
397
|
+
if (month === choice.month) {
|
|
398
|
+
// Create day options
|
|
399
|
+
choice.days.forEach((dayChoice) => {
|
|
400
|
+
const ordinal = getOrdinal(dayChoice);
|
|
401
|
+
dayOptions.push(
|
|
402
|
+
<option
|
|
403
|
+
key={`${choice.year}-${choice.month}-${dayChoice}`}
|
|
404
|
+
value={dayChoice}
|
|
405
|
+
aria-label={`choose date ${dayChoice}`}
|
|
406
|
+
>
|
|
407
|
+
{dayChoice}
|
|
408
|
+
{ordinal}
|
|
409
|
+
</option>,
|
|
255
410
|
);
|
|
256
|
-
}
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
// Create body
|
|
416
|
+
body = (
|
|
417
|
+
<div
|
|
418
|
+
className="SimpleDateChooser-inner-container d-inline-block"
|
|
419
|
+
aria-label={`date chooser with selected date: ${month}/${day}/${year}`}
|
|
257
420
|
>
|
|
258
|
-
{
|
|
259
|
-
|
|
260
|
-
|
|
421
|
+
{/* Month Chooser */}
|
|
422
|
+
<select
|
|
423
|
+
aria-label={`month for ${ariaLabel}`}
|
|
424
|
+
className="custom-select d-inline-block mr-1"
|
|
425
|
+
style={{ width: 'auto' }}
|
|
426
|
+
id={`SimpleDateChooser-${name}-month`}
|
|
427
|
+
value={`${month}-${year}`}
|
|
428
|
+
onChange={(e) => {
|
|
429
|
+
const choice = choices[e.target.selectedIndex];
|
|
430
|
+
|
|
431
|
+
// Change day, month, and year
|
|
432
|
+
onChange(
|
|
433
|
+
choice.month,
|
|
434
|
+
choice.days[0],
|
|
435
|
+
choice.year,
|
|
436
|
+
);
|
|
437
|
+
}}
|
|
438
|
+
>
|
|
439
|
+
{monthOptions}
|
|
440
|
+
</select>
|
|
441
|
+
|
|
442
|
+
{/* Day Chooser */}
|
|
443
|
+
<select
|
|
444
|
+
aria-label={`day for ${ariaLabel}`}
|
|
445
|
+
className="custom-select d-inline-block"
|
|
446
|
+
style={{ width: 'auto' }}
|
|
447
|
+
id={`SimpleDateChooser-${name}-day`}
|
|
448
|
+
value={day}
|
|
449
|
+
onChange={(e) => {
|
|
450
|
+
// Only change the day
|
|
451
|
+
onChange(
|
|
452
|
+
month,
|
|
453
|
+
Number.parseInt(e.target.value, 10),
|
|
454
|
+
year,
|
|
455
|
+
);
|
|
456
|
+
}}
|
|
457
|
+
>
|
|
458
|
+
{dayOptions}
|
|
459
|
+
</select>
|
|
460
|
+
</div>
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/* --------- DateOutOfRange --------- */
|
|
465
|
+
|
|
466
|
+
if (view === View.InvalidDate) {
|
|
467
|
+
body = (
|
|
468
|
+
<div className="SimpleDateChooser-inner-container d-inline-block">
|
|
469
|
+
<button
|
|
470
|
+
type="button"
|
|
471
|
+
className="btn btn-light"
|
|
472
|
+
onClick={askToEditInvalidDate}
|
|
473
|
+
aria-label={`edit date for ${ariaLabel}`}
|
|
474
|
+
>
|
|
475
|
+
{getMonthName(month).full}
|
|
476
|
+
{' '}
|
|
477
|
+
{day}
|
|
478
|
+
{getOrdinal(day)}
|
|
479
|
+
,
|
|
480
|
+
{' '}
|
|
481
|
+
{year}
|
|
482
|
+
</button>
|
|
483
|
+
<button
|
|
484
|
+
type="button"
|
|
485
|
+
className="btn btn-secondary"
|
|
486
|
+
onClick={askToEditInvalidDate}
|
|
487
|
+
aria-label={`edit date for ${ariaLabel}`}
|
|
488
|
+
>
|
|
489
|
+
Edit
|
|
490
|
+
</button>
|
|
491
|
+
</div>
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/*----------------------------------------*/
|
|
496
|
+
/* --------------- Main UI -------------- */
|
|
497
|
+
/*----------------------------------------*/
|
|
498
|
+
|
|
499
|
+
return (
|
|
500
|
+
<span className="SimpleDateChooser-outer-container">
|
|
501
|
+
{body}
|
|
502
|
+
</span>
|
|
261
503
|
);
|
|
262
504
|
};
|
|
263
505
|
|