dce-reactkit 3.0.0-beta.4 → 3.0.0-beta.40

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.
Files changed (60) hide show
  1. package/.eslintrc.js +95 -0
  2. package/README.md +1 -546
  3. package/dist/cjs/index.js +784 -85
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/AppWrapper.d.ts +25 -2
  6. package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
  7. package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
  8. package/dist/cjs/types/components/RadioButton.d.ts +20 -0
  9. package/dist/cjs/types/components/SimpleDateChooser.d.ts +21 -0
  10. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  11. package/dist/cjs/types/helpers/genRouteHandler.d.ts +30 -0
  12. package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
  13. package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
  14. package/dist/cjs/types/helpers/handleError.d.ts +18 -0
  15. package/dist/cjs/types/helpers/handleSuccess.d.ts +8 -0
  16. package/dist/cjs/types/helpers/visitServerEndpoint.d.ts +23 -0
  17. package/dist/cjs/types/index.d.ts +10 -1
  18. package/dist/cjs/types/server/initServer.d.ts +21 -0
  19. package/dist/cjs/types/types/ParamType.d.ts +17 -0
  20. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
  21. package/dist/esm/index.js +776 -86
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/types/components/AppWrapper.d.ts +25 -2
  24. package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
  25. package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
  26. package/dist/esm/types/components/RadioButton.d.ts +20 -0
  27. package/dist/esm/types/components/SimpleDateChooser.d.ts +21 -0
  28. package/dist/esm/types/components/TabBox.d.ts +1 -0
  29. package/dist/esm/types/helpers/genRouteHandler.d.ts +30 -0
  30. package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
  31. package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
  32. package/dist/esm/types/helpers/handleError.d.ts +18 -0
  33. package/dist/esm/types/helpers/handleSuccess.d.ts +8 -0
  34. package/dist/esm/types/helpers/visitServerEndpoint.d.ts +23 -0
  35. package/dist/esm/types/index.d.ts +10 -1
  36. package/dist/esm/types/server/initServer.d.ts +21 -0
  37. package/dist/esm/types/types/ParamType.d.ts +17 -0
  38. package/dist/esm/types/types/ReactKitErrorCode.d.ts +3 -1
  39. package/dist/index.d.ts +182 -10
  40. package/package.json +13 -1
  41. package/rollup.config.js +0 -1
  42. package/src/components/AppWrapper.tsx +73 -15
  43. package/src/components/ButtonInputGroup.tsx +89 -0
  44. package/src/components/CheckboxButton.tsx +100 -0
  45. package/src/components/ErrorBox.tsx +4 -4
  46. package/src/components/LoadingSpinner.tsx +3 -3
  47. package/src/components/Modal.tsx +185 -71
  48. package/src/components/RadioButton.tsx +102 -0
  49. package/src/components/SimpleDateChooser.tsx +217 -0
  50. package/src/components/TabBox.tsx +65 -58
  51. package/src/helpers/genRouteHandler.ts +326 -0
  52. package/src/helpers/getOrdinal.tsx +13 -0
  53. package/src/helpers/getTimeInfoInET.tsx +56 -0
  54. package/src/helpers/handleError.ts +65 -0
  55. package/src/helpers/handleSuccess.ts +18 -0
  56. package/src/helpers/visitServerEndpoint.tsx +110 -0
  57. package/src/index.ts +21 -0
  58. package/src/server/initServer.ts +53 -0
  59. package/src/types/ParamType.ts +18 -0
  60. package/src/types/ReactKitErrorCode.tsx +3 -1
@@ -15,69 +15,12 @@ import ModalButtonType from '../types/ModalButtonType';
15
15
  import ModalSize from '../types/ModalSize';
16
16
  import ModalType from '../types/ModalType';
17
17
 
18
- /*------------------------------------------------------------------------*/
19
- /* Style */
20
- /*------------------------------------------------------------------------*/
21
-
22
- const style = `
23
- /* More opaque backdrop */
24
- .Modal-backdrop {
25
- background-color: black !important;
26
- opacity: 0.8 !important;
27
- z-index: 10000000 !important;
28
- }
29
-
30
- /* Customize the modal header and body */
31
- .modal-header {
32
- /* Dark theme */
33
- background-color: #222 !important;
34
- color: white !important;
35
-
36
- /* Border */
37
- border: 1px solid #545454 !important;
38
- }
39
- .modal-body {
40
- /* Dark theme */
41
- background-color: #222 !important;
42
- color: white !important;
43
-
44
- /* Border */
45
- border-top: 0 !important;
46
- border-left: 1px solid #545454 !important;
47
- border-bottom: 1px solid #545454 !important;
48
- border-right: 1px solid #545454 !important;
49
- border-bottom-left-radius: 3px;
50
- border-bottom-right-radius: 3px;
51
- }
52
-
53
- /* Create our own animation */
54
- .modal-content {
55
- animation-name: Modal-drop-in;
56
- animation-duration: 0.4s;
57
- animation-iteration-count: 1;
58
- animation-fill-mode: forwards;
59
- animation-timing-function: ease-out;
60
- }
61
- @keyframes Modal-drop-in {
62
- 0% {
63
- transform: scale(0.8);
64
- opacity: 0.5;
65
- }
66
- 100% {
67
- transform: scale(1);
68
- opacity: 1;
69
- }
70
- }
71
- `;
72
-
73
18
  /*------------------------------------------------------------------------*/
74
19
  /* Constants */
75
20
  /*------------------------------------------------------------------------*/
76
21
 
77
22
  // Constants
78
- const MS_TO_ANIMATE = 400; // Time to animate in/out (defined by bootstrap)
79
- const MS_ANIMATE_IN_DELAY = 10;
80
- // Time to wait before animating in (must be >0 or animation won't trigger)
23
+ const MS_TO_ANIMATE = 200; // Animation duration
81
24
 
82
25
  // Modal type to list of buttons
83
26
  const modalTypeToModalButtonTypes: {
@@ -161,6 +104,116 @@ const ModalButtonTypeToLabelAndVariant = {
161
104
  },
162
105
  };
163
106
 
107
+ /*------------------------------------------------------------------------*/
108
+ /* Style */
109
+ /*------------------------------------------------------------------------*/
110
+
111
+ const style = `
112
+ .Modal-backdrop {
113
+ position: fixed;
114
+ top: 0;
115
+ left: 0;
116
+ width: 100vw;
117
+ height: 100vw;
118
+ background-color: rgba(0, 0, 0, 0.7);
119
+ }
120
+
121
+ .Modal-fading-in {
122
+ animation-name: Modal-fading-in;
123
+ animation-duration: ${Math.floor(MS_TO_ANIMATE * 2)}ms;
124
+ animation-iteration-count: 1;
125
+ animation-fill-mode: both;
126
+ animation-timing-function: ease-out;
127
+ }
128
+
129
+ @keyframes Modal-fading-in {
130
+ 0% {
131
+ opacity: 0;
132
+ }
133
+ 100% {
134
+ opacity: 1;
135
+ }
136
+ }
137
+
138
+ .Modal-fading-out {
139
+ animation-name: Modal-fading-out;
140
+ animation-duration: ${MS_TO_ANIMATE}ms;
141
+ animation-iteration-count: 1;
142
+ animation-fill-mode: both;
143
+ animation-timing-function: ease-in;
144
+ }
145
+
146
+ @keyframes Modal-fading-out {
147
+ 0% {
148
+ opacity: 1;
149
+ }
150
+ 100% {
151
+ opacity: 0;
152
+ }
153
+ }
154
+
155
+ .Modal-animating-in {
156
+ animation-name: Modal-animating-in;
157
+ animation-duration: ${MS_TO_ANIMATE}ms;
158
+ animation-iteration-count: 1;
159
+ animation-fill-mode: both;
160
+ animation-timing-function: ease-out;
161
+ }
162
+
163
+ @keyframes Modal-animating-in {
164
+ 0% {
165
+ transform: scale(1.05) translate(0, -1.5rem);
166
+ opacity: 0;
167
+ }
168
+ 100% {
169
+ transform: scale(1) translate(0, 0);
170
+ opacity: 1;
171
+ }
172
+ }
173
+
174
+ .Modal-animating-pop {
175
+ animation-name: Modal-animating-pop;
176
+ animation-duration: ${MS_TO_ANIMATE}ms;
177
+ animation-iteration-count: 1;
178
+ animation-fill-mode: both;
179
+ animation-timing-function: ease-in-out;
180
+ }
181
+
182
+ @keyframes Modal-animating-pop {
183
+ 0% {
184
+ transform: scale(1);
185
+ opacity: 1;
186
+ }
187
+ 50% {
188
+ transform: scale(1.05);
189
+ opacity: 0.9;
190
+ }
191
+ 100% {
192
+ transform: scale(1);
193
+ opacity: 1;
194
+ }
195
+ }
196
+
197
+ .Modal-animating-out {
198
+ animation-name: Modal-animating-out;
199
+ animation-duration: ${MS_TO_ANIMATE}ms;
200
+ animation-iteration-count: 1;
201
+ animation-fill-mode: both;
202
+ animation-timing-function: ease-in;
203
+ }
204
+
205
+ @keyframes Modal-animating-out {
206
+ 0% {
207
+ transform: scale(1) translate(0, 0);
208
+ opacity: 1;
209
+ }
210
+ 100% {
211
+ transform: scale(1.05) translate(0, -1.5rem);
212
+ opacity: 0;
213
+ }
214
+ }
215
+ `;
216
+
164
217
  /*------------------------------------------------------------------------*/
165
218
  /* Types */
166
219
  /*------------------------------------------------------------------------*/
@@ -245,11 +298,17 @@ const Modal: React.FC<Props> = (props) => {
245
298
 
246
299
  /* -------------- State ------------- */
247
300
 
301
+ // If true, the modal is completely gone
302
+ // (not just invisible, also removed from the dom)
303
+ const [gone, setGone] = useState(false);
304
+
248
305
  // If true, the modal is shown
249
306
  const [visible, setVisible] = useState(false);
250
307
 
251
- // True if currently animating in
252
- const [animatingIn, setAnimatingIn] = useState(false);
308
+ // True if animation is in use
309
+ const [animatingIn, setAnimatingIn] = useState(true);
310
+ const [animatingPop, setAnimatingPop] = useState(false);
311
+ const [animatingOut, setAnimatingOut] = useState(false);
253
312
 
254
313
  /*------------------------------------------------------------------------*/
255
314
  /* Lifecycle Functions */
@@ -262,13 +321,10 @@ const Modal: React.FC<Props> = (props) => {
262
321
  useEffect(
263
322
  () => {
264
323
  (async () => {
265
- // Start the component animating in
266
- await waitMs(MS_ANIMATE_IN_DELAY);
267
- setAnimatingIn(true);
268
-
269
324
  // Wait and then set visible to true
270
325
  await waitMs(MS_TO_ANIMATE);
271
326
  setVisible(true);
327
+ setAnimatingIn(false);
272
328
  })();
273
329
  },
274
330
  [],
@@ -302,16 +358,23 @@ const Modal: React.FC<Props> = (props) => {
302
358
 
303
359
  // Update the state
304
360
  setVisible(false);
361
+ setAnimatingOut(true);
305
362
 
306
363
  // Call the handler after the modal has animated out
307
364
  await waitMs(MS_TO_ANIMATE);
308
365
  onClose(ModalButtonType);
366
+ setGone(true);
309
367
  };
310
368
 
311
369
  /*------------------------------------------------------------------------*/
312
370
  /* Render */
313
371
  /*------------------------------------------------------------------------*/
314
372
 
373
+ // Return nothing if gone
374
+ if (gone) {
375
+ return null;
376
+ }
377
+
315
378
  /*----------------------------------------*/
316
379
  /* Footer */
317
380
  /*----------------------------------------*/
@@ -343,8 +406,9 @@ const Modal: React.FC<Props> = (props) => {
343
406
  // Create the button
344
407
  return (
345
408
  <button
409
+ key={ModalButtonType}
346
410
  type="button"
347
- className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'mr-1'}`}
411
+ className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`}
348
412
  onClick={() => {
349
413
  handleClose(ModalButtonType);
350
414
  }}
@@ -365,23 +429,73 @@ const Modal: React.FC<Props> = (props) => {
365
429
  : undefined
366
430
  );
367
431
 
432
+ // Choose an animation
433
+ let animationClass = '';
434
+ let backdropAnimationClass = '';
435
+ if (animatingIn) {
436
+ animationClass = 'Modal-animating-in';
437
+ backdropAnimationClass = 'Modal-fading-in';
438
+ } else if (animatingOut) {
439
+ animationClass = 'Modal-animating-out';
440
+ backdropAnimationClass = 'Modal-fading-out';
441
+ } else if (animatingPop) {
442
+ animationClass = 'Modal-animating-pop';
443
+ }
444
+
368
445
  // Render the modal
369
446
  return (
370
447
  <div
371
- className={`modal fade modal-${size}`}
448
+ className={`modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`}
372
449
  tabIndex={-1}
373
450
  style={{
374
451
  zIndex: (
375
452
  onTopOfOtherModals
376
- ? 6000000000
453
+ ? 5000000001
377
454
  : 5000000000
378
455
  ),
456
+ display: 'block',
457
+ margin: 'auto',
458
+ left: 0,
459
+ right: 0,
379
460
  }}
380
461
  >
381
- <div className="modal-dialog">
462
+ <style>{style}</style>
463
+ <div
464
+ className={`Modal-backdrop ${backdropAnimationClass}`}
465
+ style={{
466
+ zIndex: 5000000003,
467
+ }}
468
+ onClick={async () => {
469
+ // Skip if exit via backdrop not allowed
470
+ if (dontAllowBackdropExit || !onClose) {
471
+ // Show pop animation
472
+ if (!animatingPop) {
473
+ setAnimatingPop(true);
474
+
475
+ // Wait then stop pop animation
476
+ await waitMs(MS_TO_ANIMATE);
477
+ setAnimatingPop(false);
478
+ }
479
+ return;
480
+ }
481
+ // Handle close
482
+ handleClose(ModalButtonType.Cancel);
483
+ }}
484
+ />
485
+ <div
486
+ className={`modal-dialog ${animationClass}`}
487
+ style={{
488
+ zIndex: 5000000002,
489
+ }}
490
+ >
382
491
  <div className="modal-content">
383
492
  <div className="modal-header">
384
- <h5 className="modal-title">
493
+ <h5
494
+ className="modal-title"
495
+ style={{
496
+ fontWeight: 'bold',
497
+ }}
498
+ >
385
499
  {title}
386
500
  </h5>
387
501
 
@@ -389,9 +503,9 @@ const Modal: React.FC<Props> = (props) => {
389
503
  <button
390
504
  type="button"
391
505
  className="btn-close"
392
- data-bs-dismiss="modal"
393
506
  aria-label="Close"
394
507
  onClick={() => {
508
+ // Handle close
395
509
  handleClose(ModalButtonType.Cancel);
396
510
  }}
397
511
  />
@@ -403,7 +517,7 @@ const Modal: React.FC<Props> = (props) => {
403
517
  </div>
404
518
  )}
405
519
  {footer && (
406
- <div className="modal-footer">
520
+ <div className="modal-footer pt-1 pb-1">
407
521
  {footer}
408
522
  </div>
409
523
  )}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * A radio selection button
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import FontAwesome Icons
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+ import { faDotCircle } from '@fortawesome/free-solid-svg-icons';
12
+ import { faCircle } from '@fortawesome/free-regular-svg-icons';
13
+
14
+ // Import shared types
15
+ import Variant from '../types/Variant';
16
+
17
+ /*------------------------------------------------------------------------*/
18
+ /* Types */
19
+ /*------------------------------------------------------------------------*/
20
+
21
+ type Props = {
22
+ // Text of the button
23
+ text: string,
24
+ // Handler for when this item is selected (not called if already selected)
25
+ onSelected: () => void,
26
+ // Aria label
27
+ ariaLabel: string,
28
+ // Button title
29
+ title?: string,
30
+ // True if selected
31
+ selected?: boolean,
32
+ // The id of the button
33
+ id?: string,
34
+ // If true, no margin on right
35
+ noMarginOnRight?: boolean,
36
+ // Variant for when radio is selected
37
+ selectedVariant?: Variant,
38
+ // Variant for when radio is not selected
39
+ unselectedVariant?: Variant,
40
+ // True if using a small button
41
+ small?: boolean,
42
+ };
43
+
44
+ /*------------------------------------------------------------------------*/
45
+ /* Component */
46
+ /*------------------------------------------------------------------------*/
47
+
48
+ const RadioButton: React.FC<Props> = (props) => {
49
+ /*------------------------------------------------------------------------*/
50
+ /* Setup */
51
+ /*------------------------------------------------------------------------*/
52
+
53
+ /* -------------- Props ------------- */
54
+
55
+ const {
56
+ text,
57
+ onSelected,
58
+ ariaLabel,
59
+ title,
60
+ selected,
61
+ id,
62
+ noMarginOnRight,
63
+ selectedVariant = Variant.Secondary,
64
+ unselectedVariant = Variant.Light,
65
+ small,
66
+ } = props;
67
+
68
+ /*------------------------------------------------------------------------*/
69
+ /* Render */
70
+ /*------------------------------------------------------------------------*/
71
+
72
+ /*----------------------------------------*/
73
+ /* Main UI */
74
+ /*----------------------------------------*/
75
+
76
+ return (
77
+ <button
78
+ type="button"
79
+ id={id}
80
+ title={title}
81
+ className={`btn btn-${selected ? selectedVariant : unselectedVariant}${selected ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`}
82
+ aria-label={`${ariaLabel}${selected ? ': currently selected' : ''}`}
83
+ onClick={() => {
84
+ if (!selected) {
85
+ onSelected();
86
+ }
87
+ }}
88
+ >
89
+ <FontAwesomeIcon
90
+ icon={selected ? faDotCircle : faCircle}
91
+ className="me-1"
92
+ />
93
+ {text}
94
+ </button>
95
+ );
96
+ };
97
+
98
+ /*------------------------------------------------------------------------*/
99
+ /* Wrap up */
100
+ /*------------------------------------------------------------------------*/
101
+
102
+ export default RadioButton;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * A very simple, lightweight date chooser
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import helpers
10
+ import getOrdinal from '../helpers/getOrdinal';
11
+ import getTimeInfoInET from '../helpers/getTimeInfoInET';
12
+
13
+ /*------------------------------------------------------------------------*/
14
+ /* Types */
15
+ /*------------------------------------------------------------------------*/
16
+
17
+ type Props = {
18
+ // Name of the chooser (machine-readable, hyphenated)
19
+ name: string,
20
+ // Currently selected month
21
+ month: number,
22
+ // Currently selected day
23
+ day: number,
24
+ // Currently selected year
25
+ year: number,
26
+ /**
27
+ * Handler for when date changes
28
+ * @param month new 1-indexed month number
29
+ * @param day new 1-indexed day number
30
+ * @param year new full year number
31
+ */
32
+ onChange: (month: number, day: number, year: number) => void,
33
+ // Number of months in the future to allow the user to schedule into
34
+ // (max is 12)
35
+ numMonthsToShow?: number,
36
+ };
37
+
38
+ /*------------------------------------------------------------------------*/
39
+ /* Constants */
40
+ /*------------------------------------------------------------------------*/
41
+
42
+ // Constants
43
+ const MONTH_NAMES = [
44
+ 'January',
45
+ 'February',
46
+ 'March',
47
+ 'April',
48
+ 'May',
49
+ 'June',
50
+ 'July',
51
+ 'August',
52
+ 'September',
53
+ 'October',
54
+ 'November',
55
+ 'December',
56
+ ];
57
+
58
+ /*------------------------------------------------------------------------*/
59
+ /* Component */
60
+ /*------------------------------------------------------------------------*/
61
+
62
+ const SimpleDateChooser: React.FC<Props> = (props) => {
63
+ /*------------------------------------------------------------------------*/
64
+ /* Setup */
65
+ /*------------------------------------------------------------------------*/
66
+
67
+ /* -------------- Props ------------- */
68
+
69
+ const {
70
+ name,
71
+ month,
72
+ day,
73
+ year,
74
+ onChange,
75
+ } = props;
76
+ const numMonthsToShow = Math.min(props.numMonthsToShow ?? 6, 12);
77
+
78
+ /*------------------------------------------------------------------------*/
79
+ /* Render */
80
+ /*------------------------------------------------------------------------*/
81
+
82
+ /*----------------------------------------*/
83
+ /* Main UI */
84
+ /*----------------------------------------*/
85
+
86
+ // Determine the set of choices allowed
87
+ const today = getTimeInfoInET();
88
+ const choices: {
89
+ choiceName: string,
90
+ month: number,
91
+ days: number[],
92
+ year: number,
93
+ }[] = [];
94
+ for (let i = 0; i < numMonthsToShow; i++) {
95
+ // Get month and year info
96
+ const unmoddedMonth = (today.month + i);
97
+ const month = (
98
+ unmoddedMonth > 12
99
+ ? unmoddedMonth - 12
100
+ : unmoddedMonth
101
+ );
102
+ const monthName = MONTH_NAMES[month - 1];
103
+ const year = (
104
+ unmoddedMonth > 12
105
+ ? today.year + 1
106
+ : today.year
107
+ );
108
+
109
+ // Figure out which days are allowed
110
+ const days = [];
111
+ const numDaysInMonth = (new Date(year, month, 0)).getDate();
112
+ const firstDay = (
113
+ month === today.month
114
+ ? today.day // Current month: start at current date
115
+ : 1 // Future month: start at beginning of month
116
+ );
117
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
118
+ days.push(day);
119
+ }
120
+
121
+ choices.push({
122
+ choiceName: `${monthName} ${year}`,
123
+ month,
124
+ year,
125
+ days,
126
+ });
127
+ }
128
+
129
+ // Create choice options
130
+ const monthOptions: React.ReactNode[] = [];
131
+ const dayOptions: React.ReactNode[] = [];
132
+ choices.forEach((choice) => {
133
+ // Create month option
134
+ monthOptions.push(
135
+ <option
136
+ key={choice.month}
137
+ value={choice.month}
138
+ aria-label={`choose ${choice.choiceName}`}
139
+ onSelect={() => {
140
+ onChange(choice.month, choice.days[0], choice.year);
141
+ }}
142
+ >
143
+ {choice.choiceName}
144
+ </option>
145
+ );
146
+
147
+ if (month === choice.month) {
148
+ // This is the currently selected month
149
+ // Create day options
150
+ choice.days.forEach((dayChoice) => {
151
+ const ordinal = getOrdinal(dayChoice);
152
+ dayOptions.push(
153
+ <option
154
+ key={dayChoice}
155
+ value={dayChoice}
156
+ aria-label={`choose date ${dayChoice}`}
157
+ >
158
+ {dayChoice}
159
+ {ordinal}
160
+ </option>
161
+ );
162
+ });
163
+ }
164
+ });
165
+
166
+ return (
167
+ <div
168
+ className="SimpleDateChooser d-inline-block"
169
+ aria-label={`date chooser with selected date: ${month} ${day}, ${year}`}
170
+ >
171
+ {/* Month Chooser */}
172
+ <select
173
+ className="custom-select d-inline-block mr-1"
174
+ style={{ width: 'auto' }}
175
+ id={`SimpleDateChooser-${name}-month`}
176
+ value={month}
177
+ onChange={(e) => {
178
+ const choice = choices[e.target.selectedIndex];
179
+
180
+ // Change day, month, and year
181
+ onChange(
182
+ choice.month,
183
+ choice.days[0],
184
+ choice.year,
185
+ );
186
+ }}
187
+ >
188
+ {monthOptions}
189
+ </select>
190
+
191
+ {/* Day Chooser */}
192
+ <select
193
+ className="custom-select d-inline-block"
194
+ style={{ width: 'auto' }}
195
+ id={`SimpleDateChooser-${name}-day`}
196
+ value={day}
197
+ onChange={(e) => {
198
+ // Only change the day
199
+ onChange(
200
+ month,
201
+ Number.parseInt(e.target.value, 10),
202
+ year,
203
+ );
204
+ }}
205
+ >
206
+ {dayOptions}
207
+ </select>
208
+ </div>
209
+ );
210
+ };
211
+
212
+ /*------------------------------------------------------------------------*/
213
+ /* Wrap Up */
214
+ /*------------------------------------------------------------------------*/
215
+
216
+ // Export component
217
+ export default SimpleDateChooser;