dce-reactkit 3.0.0-beta.9 → 3.0.2

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 (59) hide show
  1. package/README.md +1 -546
  2. package/dist/cjs/index.js +945 -83
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/types/components/AppWrapper.d.ts +25 -0
  5. package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
  6. package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
  7. package/dist/cjs/types/components/RadioButton.d.ts +20 -0
  8. package/dist/cjs/types/components/SimpleDateChooser.d.ts +22 -0
  9. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  10. package/dist/cjs/types/helpers/genRouteHandler.d.ts +32 -0
  11. package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
  12. package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
  13. package/dist/cjs/types/helpers/handleError.d.ts +18 -0
  14. package/dist/cjs/types/helpers/handleSuccess.d.ts +8 -0
  15. package/dist/cjs/types/helpers/visitServerEndpoint.d.ts +23 -0
  16. package/dist/cjs/types/index.d.ts +13 -1
  17. package/dist/cjs/types/server/initServer.d.ts +21 -0
  18. package/dist/cjs/types/types/ParamType.d.ts +17 -0
  19. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
  20. package/dist/esm/index.js +934 -84
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/types/components/AppWrapper.d.ts +25 -0
  23. package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
  24. package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
  25. package/dist/esm/types/components/RadioButton.d.ts +20 -0
  26. package/dist/esm/types/components/SimpleDateChooser.d.ts +22 -0
  27. package/dist/esm/types/components/TabBox.d.ts +1 -0
  28. package/dist/esm/types/helpers/genRouteHandler.d.ts +32 -0
  29. package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
  30. package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
  31. package/dist/esm/types/helpers/handleError.d.ts +18 -0
  32. package/dist/esm/types/helpers/handleSuccess.d.ts +8 -0
  33. package/dist/esm/types/helpers/visitServerEndpoint.d.ts +23 -0
  34. package/dist/esm/types/index.d.ts +13 -1
  35. package/dist/esm/types/server/initServer.d.ts +21 -0
  36. package/dist/esm/types/types/ParamType.d.ts +17 -0
  37. package/dist/esm/types/types/ReactKitErrorCode.d.ts +3 -1
  38. package/dist/index.d.ts +231 -10
  39. package/package.json +3 -1
  40. package/rollup.config.js +0 -1
  41. package/src/components/AppWrapper.tsx +73 -11
  42. package/src/components/ButtonInputGroup.tsx +89 -0
  43. package/src/components/CheckboxButton.tsx +100 -0
  44. package/src/components/ErrorBox.tsx +4 -4
  45. package/src/components/LoadingSpinner.tsx +3 -3
  46. package/src/components/Modal.tsx +184 -23
  47. package/src/components/RadioButton.tsx +102 -0
  48. package/src/components/SimpleDateChooser.tsx +222 -0
  49. package/src/components/TabBox.tsx +65 -58
  50. package/src/helpers/genRouteHandler.ts +329 -0
  51. package/src/helpers/getOrdinal.tsx +13 -0
  52. package/src/helpers/getTimeInfoInET.tsx +56 -0
  53. package/src/helpers/handleError.ts +65 -0
  54. package/src/helpers/handleSuccess.ts +18 -0
  55. package/src/helpers/visitServerEndpoint.tsx +110 -0
  56. package/src/index.ts +27 -0
  57. package/src/server/initServer.ts +53 -0
  58. package/src/types/ParamType.ts +18 -0
  59. package/src/types/ReactKitErrorCode.tsx +3 -1
@@ -63,9 +63,9 @@ const ErrorBox: React.FC<Props> = (props) => {
63
63
  <span
64
64
  style={{
65
65
  backgroundColor: 'white',
66
- borderRadius: '5px',
67
- paddingLeft: '3px',
68
- paddingRight: '3px',
66
+ borderRadius: '0.3rem',
67
+ paddingLeft: '0.2rem',
68
+ paddingRight: '0.2rem',
69
69
  color: '#DC4150',
70
70
  fontVariant: 'small-caps',
71
71
  fontSize: '80%',
@@ -85,7 +85,7 @@ const ErrorBox: React.FC<Props> = (props) => {
85
85
  <div
86
86
  className="alert alert-danger text-center"
87
87
  style={{
88
- maxWidth: '650px',
88
+ maxWidth: '40rem',
89
89
  margin: 'auto',
90
90
  }}
91
91
  >
@@ -20,10 +20,10 @@ const style = `
20
20
  .LoadingSpinner-blip-2,
21
21
  .LoadingSpinner-blip-3,
22
22
  .LoadingSpinner-blip-4 {
23
- font-size: 25px;
23
+ font-size: 1.8rem;
24
24
  opacity: 0.6;
25
- margin-top: 20px;
26
- margin-bottom: 20px;
25
+ margin-top: 1rem;
26
+ margin-bottom: 1rem;
27
27
  }
28
28
 
29
29
  /* First Blip */
@@ -15,21 +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
- `;
24
-
25
18
  /*------------------------------------------------------------------------*/
26
19
  /* Constants */
27
20
  /*------------------------------------------------------------------------*/
28
21
 
29
22
  // Constants
30
- const MS_TO_ANIMATE = 400; // Time to animate in/out (defined by bootstrap)
31
- const MS_ANIMATE_IN_DELAY = 10;
32
- // Time to wait before animating in (must be >0 or animation won't trigger)
23
+ const MS_TO_ANIMATE = 200; // Animation duration
33
24
 
34
25
  // Modal type to list of buttons
35
26
  const modalTypeToModalButtonTypes: {
@@ -113,6 +104,116 @@ const ModalButtonTypeToLabelAndVariant = {
113
104
  },
114
105
  };
115
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
+
116
217
  /*------------------------------------------------------------------------*/
117
218
  /* Types */
118
219
  /*------------------------------------------------------------------------*/
@@ -197,11 +298,17 @@ const Modal: React.FC<Props> = (props) => {
197
298
 
198
299
  /* -------------- State ------------- */
199
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
+
200
305
  // If true, the modal is shown
201
306
  const [visible, setVisible] = useState(false);
202
307
 
203
- // True if currently animating in
204
- 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);
205
312
 
206
313
  /*------------------------------------------------------------------------*/
207
314
  /* Lifecycle Functions */
@@ -214,13 +321,10 @@ const Modal: React.FC<Props> = (props) => {
214
321
  useEffect(
215
322
  () => {
216
323
  (async () => {
217
- // Start the component animating in
218
- await waitMs(MS_ANIMATE_IN_DELAY);
219
- setAnimatingIn(true);
220
-
221
324
  // Wait and then set visible to true
222
325
  await waitMs(MS_TO_ANIMATE);
223
326
  setVisible(true);
327
+ setAnimatingIn(false);
224
328
  })();
225
329
  },
226
330
  [],
@@ -254,16 +358,23 @@ const Modal: React.FC<Props> = (props) => {
254
358
 
255
359
  // Update the state
256
360
  setVisible(false);
361
+ setAnimatingOut(true);
257
362
 
258
363
  // Call the handler after the modal has animated out
259
364
  await waitMs(MS_TO_ANIMATE);
260
365
  onClose(ModalButtonType);
366
+ setGone(true);
261
367
  };
262
368
 
263
369
  /*------------------------------------------------------------------------*/
264
370
  /* Render */
265
371
  /*------------------------------------------------------------------------*/
266
372
 
373
+ // Return nothing if gone
374
+ if (gone) {
375
+ return null;
376
+ }
377
+
267
378
  /*----------------------------------------*/
268
379
  /* Footer */
269
380
  /*----------------------------------------*/
@@ -295,8 +406,9 @@ const Modal: React.FC<Props> = (props) => {
295
406
  // Create the button
296
407
  return (
297
408
  <button
409
+ key={ModalButtonType}
298
410
  type="button"
299
- className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'mr-1'}`}
411
+ className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`}
300
412
  onClick={() => {
301
413
  handleClose(ModalButtonType);
302
414
  }}
@@ -317,24 +429,73 @@ const Modal: React.FC<Props> = (props) => {
317
429
  : undefined
318
430
  );
319
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
+
320
445
  // Render the modal
321
446
  return (
322
447
  <div
323
- className={`modal fade show modal-dialog-scrollable modal-${size}`}
448
+ className={`modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`}
324
449
  tabIndex={-1}
325
450
  style={{
326
451
  zIndex: (
327
452
  onTopOfOtherModals
328
- ? 6000000000
453
+ ? 5000000001
329
454
  : 5000000000
330
455
  ),
331
456
  display: 'block',
457
+ margin: 'auto',
458
+ left: 0,
459
+ right: 0,
332
460
  }}
333
461
  >
334
- <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
+ >
335
491
  <div className="modal-content">
336
492
  <div className="modal-header">
337
- <h5 className="modal-title">
493
+ <h5
494
+ className="modal-title"
495
+ style={{
496
+ fontWeight: 'bold',
497
+ }}
498
+ >
338
499
  {title}
339
500
  </h5>
340
501
 
@@ -342,9 +503,9 @@ const Modal: React.FC<Props> = (props) => {
342
503
  <button
343
504
  type="button"
344
505
  className="btn-close"
345
- data-bs-dismiss="modal"
346
506
  aria-label="Close"
347
507
  onClick={() => {
508
+ // Handle close
348
509
  handleClose(ModalButtonType.Cancel);
349
510
  }}
350
511
  />
@@ -356,7 +517,7 @@ const Modal: React.FC<Props> = (props) => {
356
517
  </div>
357
518
  )}
358
519
  {footer && (
359
- <div className="modal-footer">
520
+ <div className="modal-footer pt-1 pb-1">
360
521
  {footer}
361
522
  </div>
362
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,222 @@
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
+ // Aria label
19
+ ariaLabel: string,
20
+ // Name of the chooser (machine-readable, hyphenated)
21
+ name: string,
22
+ // Currently selected month
23
+ month: number,
24
+ // Currently selected day
25
+ day: number,
26
+ // Currently selected year
27
+ year: number,
28
+ /**
29
+ * Handler for when date changes
30
+ * @param month new 1-indexed month number
31
+ * @param day new 1-indexed day number
32
+ * @param year new full year number
33
+ */
34
+ onChange: (month: number, day: number, year: number) => void,
35
+ // Number of months in the future to allow the user to schedule into
36
+ // (max is 12)
37
+ numMonthsToShow?: number,
38
+ };
39
+
40
+ /*------------------------------------------------------------------------*/
41
+ /* Constants */
42
+ /*------------------------------------------------------------------------*/
43
+
44
+ // Constants
45
+ const MONTH_NAMES = [
46
+ 'January',
47
+ 'February',
48
+ 'March',
49
+ 'April',
50
+ 'May',
51
+ 'June',
52
+ 'July',
53
+ 'August',
54
+ 'September',
55
+ 'October',
56
+ 'November',
57
+ 'December',
58
+ ];
59
+
60
+ /*------------------------------------------------------------------------*/
61
+ /* Component */
62
+ /*------------------------------------------------------------------------*/
63
+
64
+ const SimpleDateChooser: React.FC<Props> = (props) => {
65
+ /*------------------------------------------------------------------------*/
66
+ /* Setup */
67
+ /*------------------------------------------------------------------------*/
68
+
69
+ /* -------------- Props ------------- */
70
+
71
+ const {
72
+ ariaLabel,
73
+ name,
74
+ month,
75
+ day,
76
+ year,
77
+ onChange,
78
+ } = props;
79
+ const numMonthsToShow = Math.min(props.numMonthsToShow ?? 6, 12);
80
+
81
+ /*------------------------------------------------------------------------*/
82
+ /* Render */
83
+ /*------------------------------------------------------------------------*/
84
+
85
+ /*----------------------------------------*/
86
+ /* Main UI */
87
+ /*----------------------------------------*/
88
+
89
+ // Determine the set of choices allowed
90
+ const today = getTimeInfoInET();
91
+ const choices: {
92
+ choiceName: string,
93
+ month: number,
94
+ days: number[],
95
+ year: number,
96
+ }[] = [];
97
+ for (let i = 0; i < numMonthsToShow; i++) {
98
+ // Get month and year info
99
+ const unmoddedMonth = (today.month + i);
100
+ const month = (
101
+ unmoddedMonth > 12
102
+ ? unmoddedMonth - 12
103
+ : unmoddedMonth
104
+ );
105
+ const monthName = MONTH_NAMES[month - 1];
106
+ const year = (
107
+ unmoddedMonth > 12
108
+ ? today.year + 1
109
+ : today.year
110
+ );
111
+
112
+ // Figure out which days are allowed
113
+ const days = [];
114
+ const numDaysInMonth = (new Date(year, month, 0)).getDate();
115
+ const firstDay = (
116
+ month === today.month
117
+ ? today.day // Current month: start at current date
118
+ : 1 // Future month: start at beginning of month
119
+ );
120
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
121
+ days.push(day);
122
+ }
123
+
124
+ choices.push({
125
+ choiceName: `${monthName} ${year}`,
126
+ month,
127
+ year,
128
+ days,
129
+ });
130
+ }
131
+
132
+ // Create choice options
133
+ const monthOptions: React.ReactNode[] = [];
134
+ const dayOptions: React.ReactNode[] = [];
135
+ choices.forEach((choice) => {
136
+ // Create month option
137
+ monthOptions.push(
138
+ <option
139
+ key={choice.month}
140
+ value={choice.month}
141
+ aria-label={`choose ${choice.choiceName}`}
142
+ onSelect={() => {
143
+ onChange(choice.month, choice.days[0], choice.year);
144
+ }}
145
+ >
146
+ {choice.choiceName}
147
+ </option>
148
+ );
149
+
150
+ if (month === choice.month) {
151
+ // This is the currently selected month
152
+ // Create day options
153
+ choice.days.forEach((dayChoice) => {
154
+ const ordinal = getOrdinal(dayChoice);
155
+ dayOptions.push(
156
+ <option
157
+ key={dayChoice}
158
+ value={dayChoice}
159
+ aria-label={`choose date ${dayChoice}`}
160
+ >
161
+ {dayChoice}
162
+ {ordinal}
163
+ </option>
164
+ );
165
+ });
166
+ }
167
+ });
168
+
169
+ return (
170
+ <div
171
+ className="SimpleDateChooser d-inline-block"
172
+ aria-label={`date chooser with selected date: ${month} ${day}, ${year}`}
173
+ >
174
+ {/* Month Chooser */}
175
+ <select
176
+ aria-label={`month for ${ariaLabel}`}
177
+ className="custom-select d-inline-block mr-1"
178
+ style={{ width: 'auto' }}
179
+ id={`SimpleDateChooser-${name}-month`}
180
+ value={month}
181
+ onChange={(e) => {
182
+ const choice = choices[e.target.selectedIndex];
183
+
184
+ // Change day, month, and year
185
+ onChange(
186
+ choice.month,
187
+ choice.days[0],
188
+ choice.year,
189
+ );
190
+ }}
191
+ >
192
+ {monthOptions}
193
+ </select>
194
+
195
+ {/* Day Chooser */}
196
+ <select
197
+ aria-label={`day for ${ariaLabel}`}
198
+ className="custom-select d-inline-block"
199
+ style={{ width: 'auto' }}
200
+ id={`SimpleDateChooser-${name}-day`}
201
+ value={day}
202
+ onChange={(e) => {
203
+ // Only change the day
204
+ onChange(
205
+ month,
206
+ Number.parseInt(e.target.value, 10),
207
+ year,
208
+ );
209
+ }}
210
+ >
211
+ {dayOptions}
212
+ </select>
213
+ </div>
214
+ );
215
+ };
216
+
217
+ /*------------------------------------------------------------------------*/
218
+ /* Wrap Up */
219
+ /*------------------------------------------------------------------------*/
220
+
221
+ // Export component
222
+ export default SimpleDateChooser;