dce-reactkit 3.7.9 → 3.7.10-beta.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.
@@ -19,11 +19,12 @@ import ModalButtonType from '../types/ModalButtonType';
19
19
  import ModalType from '../types/ModalType';
20
20
  import Variant from '../types/Variant';
21
21
  import LogBuiltInMetadata from '../types/LogBuiltInMetadata';
22
+ import ModalProps from './Modal/ModalProps';
22
23
 
23
24
  // Import shared components
24
25
  // TODO: fix dependency cycle
25
26
  // eslint-disable-next-line import/no-cycle
26
- import Modal from './Modal';
27
+ import ModalForWrapper from './Modal/ModalForWrapper';
27
28
 
28
29
  // Import custom errors
29
30
  import ErrorWithCode from '../errors/ErrorWithCode';
@@ -358,6 +359,99 @@ export const showSessionExpiredMessage = async () => {
358
359
  }
359
360
  };
360
361
 
362
+ /*----------------------------------------*/
363
+ /* --------------- Modals --------------- */
364
+ /*----------------------------------------*/
365
+
366
+ // Modal tuple with id and props
367
+ type ModalTuple = {
368
+ // Unique id
369
+ id: number,
370
+ // Modal props
371
+ props: ModalProps,
372
+ };
373
+
374
+ // Stored copies of setters
375
+ let setModals: (modals: ModalTuple[]) => void;
376
+
377
+ // Stored copies of modals
378
+ let modals: ModalTuple[] = [];
379
+
380
+ /**
381
+ * Add a modal to the screen
382
+ * @author Gabe Abrams
383
+ * @param id the uniqueId of the modal
384
+ * @param props the props for the modal
385
+ */
386
+ export const addModal = async (
387
+ id: number,
388
+ props: ModalProps,
389
+ ) => {
390
+ // Wait for helper to exist
391
+ await waitForHelper(() => {
392
+ return !!setModals;
393
+ });
394
+
395
+ // Add modal
396
+ modals.push({
397
+ id,
398
+ props,
399
+ });
400
+
401
+ // Update modals
402
+ setModals(modals);
403
+ };
404
+
405
+ /**
406
+ * Update a modal on the screen by id (if it exists) with new props
407
+ * @author Gabe Abrams
408
+ * @param id the uniqueId of the modal
409
+ * @param props the new props for the modal
410
+ */
411
+ export const updateModal = async (
412
+ id: number,
413
+ props: ModalProps,
414
+ ) => {
415
+ // Wait for helper to exist
416
+ await waitForHelper(() => {
417
+ return !!setModals;
418
+ });
419
+
420
+ // Update modal
421
+ modals = modals.map((modal) => {
422
+ if (modal.id === id) {
423
+ return {
424
+ id,
425
+ props,
426
+ };
427
+ }
428
+ return modal;
429
+ });
430
+
431
+ // Update modals
432
+ setModals(modals);
433
+ };
434
+
435
+ /**
436
+ * Remove a modal from the screen by id (if it exists)
437
+ * @author Gabe Abrams
438
+ * @param id the uniqueId of the modal
439
+ */
440
+ export const removeModal = async (id: number) => {
441
+ // Wait for helper to exist
442
+ await waitForHelper(() => {
443
+ return !!setModals;
444
+ });
445
+
446
+ // Remove modal
447
+ modals = modals.filter((modal) => {
448
+ return modal.id !== id;
449
+ });
450
+
451
+ // Update modals
452
+ setModals(modals);
453
+ };
454
+
361
455
  /*------------------------------------------------------------------------*/
362
456
  /* -------------------------------- Style ------------------------------- */
363
457
  /*------------------------------------------------------------------------*/
@@ -464,6 +558,13 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
464
558
  ] = useState<boolean>(false);
465
559
  setSessionHasExpired = setSessionHasExpiredInner;
466
560
 
561
+ // Modals
562
+ const [
563
+ modalsFromState,
564
+ setModalsInner,
565
+ ] = useState<ModalTuple[]>([]);
566
+ setModals = setModalsInner;
567
+
467
568
  /*------------------------------------------------------------------------*/
468
569
  /* ------------------------------- Render ------------------------------- */
469
570
  /*------------------------------------------------------------------------*/
@@ -479,7 +580,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
479
580
 
480
581
  if (alertInfo) {
481
582
  modal = (
482
- <Modal
583
+ <ModalForWrapper
483
584
  key={`alert-${alertInfo.title}-${alertInfo.text}`}
484
585
  title={alertInfo.title}
485
586
  type={ModalType.Okay}
@@ -493,7 +594,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
493
594
  onTopOfOtherModals
494
595
  >
495
596
  {alertInfo.text}
496
- </Modal>
597
+ </ModalForWrapper>
497
598
  );
498
599
  }
499
600
 
@@ -501,7 +602,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
501
602
 
502
603
  if (confirmInfo) {
503
604
  modal = (
504
- <Modal
605
+ <ModalForWrapper
505
606
  key={`confirm-${confirmInfo.title}-${confirmInfo.text}`}
506
607
  title={confirmInfo.title}
507
608
  type={ModalType.OkayCancel}
@@ -518,10 +619,26 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
518
619
  dontAllowBackdropExit
519
620
  >
520
621
  {confirmInfo.text}
521
- </Modal>
622
+ </ModalForWrapper>
522
623
  );
523
624
  }
524
625
 
626
+ /* ---------- Custom Modals --------- */
627
+
628
+ // List of modals added outside reactkit via the Modal component
629
+ const customModals: React.ReactNode[] = [];
630
+
631
+ // Add custom modals
632
+ modalsFromState.forEach((modalTuple) => {
633
+ customModals.push(
634
+ <ModalForWrapper
635
+ key={String(modalTuple.props.key ?? modalTuple.id)}
636
+ // eslint-disable-next-line react/jsx-props-no-spreading
637
+ {...modalTuple.props}
638
+ />,
639
+ );
640
+ });
641
+
525
642
  /*----------------------------------------*/
526
643
  /* ---------------- Views --------------- */
527
644
  /*----------------------------------------*/
@@ -641,10 +758,12 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
641
758
  background-color: white;
642
759
  color: black;
643
760
  border: 0.1rem solid black;
761
+ pointer-events: none;
644
762
  }
645
763
  div[data-popper-placement="top"] .tooltip-arrow::before {
646
764
  border-top-color: white !important;
647
765
  transform: translate(0, -0.05rem);
766
+ pointer-events: none;
648
767
  }
649
768
  `
650
769
  : `
@@ -652,10 +771,12 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
652
771
  background-color: black;
653
772
  color: black;
654
773
  border: 0.1rem solid white;
774
+ pointer-events: none;
655
775
  }
656
776
  div[data-popper-placement="top"] .tooltip-arrow::before {
657
777
  border-top-color: black !important;
658
778
  transform: translate(0, -0.05rem);
779
+ pointer-events: none;
659
780
  }
660
781
  `
661
782
  );
@@ -679,6 +800,9 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
679
800
  {/* Modal */}
680
801
  {modal}
681
802
 
803
+ {/* Custom Modals */}
804
+ {customModals}
805
+
682
806
  {/* Body */}
683
807
  {body}
684
808
  </>
@@ -26,7 +26,7 @@ import roundToNumDecimals from '../helpers/roundToNumDecimals';
26
26
  import genCSV from '../helpers/genCSV';
27
27
 
28
28
  // Import shared components
29
- import Modal from './Modal';
29
+ import Modal from './Modal/ModalForWrapper';
30
30
  import ModalType from '../types/ModalType';
31
31
  import CheckboxButton from './CheckboxButton';
32
32
  import CSVDownloadButton from './CSVDownloadButton';
@@ -4,7 +4,8 @@
4
4
  /* eslint-disable react/destructuring-assignment */
5
5
 
6
6
  /**
7
- * A generic popup modal
7
+ * The displayable modal component (this is the modal that's added to the
8
+ * wrapper, not the one that the programmer renders)
8
9
  * @author Gabe Abrams
9
10
  */
10
11
 
@@ -12,18 +13,19 @@
12
13
  import React, { useState, useEffect, useRef } from 'react';
13
14
 
14
15
  // Import other components
15
- import waitMs from '../helpers/waitMs';
16
+ import waitMs from '../../helpers/waitMs';
16
17
 
17
18
  // Import types
18
- import Variant from '../types/Variant';
19
- import ModalButtonType from '../types/ModalButtonType';
20
- import ModalSize from '../types/ModalSize';
21
- import ModalType from '../types/ModalType';
19
+ import Variant from '../../types/Variant';
20
+ import ModalButtonType from '../../types/ModalButtonType';
21
+ import ModalSize from '../../types/ModalSize';
22
+ import ModalType from '../../types/ModalType';
23
+ import ModalProps from './ModalProps';
22
24
 
23
25
  // Import shared helpers
24
26
  // TODO: fix dependency cycle
25
27
  // eslint-disable-next-line import/no-cycle
26
- import { isDarkModeOn } from '../client/initClient';
28
+ import { isDarkModeOn } from '../../client/initClient';
27
29
 
28
30
  /*------------------------------------------------------------------------*/
29
31
  /* ------------------------------ Constants ----------------------------- */
@@ -142,7 +144,7 @@ const getModalButtonTypeToLabelAndVariant = () => {
142
144
  /*------------------------------------------------------------------------*/
143
145
 
144
146
  const style = `
145
- .Modal-backdrop {
147
+ .ModalForWrapper-backdrop {
146
148
  position: fixed;
147
149
  top: 0;
148
150
  left: 0;
@@ -150,7 +152,7 @@ const style = `
150
152
  height: 200vh;
151
153
  background-color: rgba(0, 0, 0, 0.7);
152
154
  }
153
- .Modal-fading-in {
155
+ .ModalForWrapper-fading-in {
154
156
  animation-name: Modal-fading-in;
155
157
  animation-duration: ${Math.floor(MS_TO_ANIMATE * 2)}ms;
156
158
  animation-iteration-count: 1;
@@ -165,7 +167,7 @@ const style = `
165
167
  opacity: 1;
166
168
  }
167
169
  }
168
- .Modal-animating-in {
170
+ .ModalForWrapper-animating-in {
169
171
  animation-name: Modal-animating-in;
170
172
  animation-duration: ${MS_TO_ANIMATE}ms;
171
173
  animation-iteration-count: 1;
@@ -182,14 +184,14 @@ const style = `
182
184
  opacity: 1;
183
185
  }
184
186
  }
185
- .Modal-animating-pop {
186
- animation-name: Modal-animating-pop;
187
+ .ModalForWrapper-animating-pop {
188
+ animation-name: ModalForWrapper-animating-pop;
187
189
  animation-duration: ${MS_TO_ANIMATE}ms;
188
190
  animation-iteration-count: 1;
189
191
  animation-fill-mode: both;
190
192
  animation-timing-function: ease-in-out;
191
193
  }
192
- @keyframes Modal-animating-pop {
194
+ @keyframes ModalForWrapper-animating-pop {
193
195
  0% {
194
196
  transform: scale(1);
195
197
  opacity: 1;
@@ -205,74 +207,11 @@ const style = `
205
207
  }
206
208
  `;
207
209
 
208
- /*------------------------------------------------------------------------*/
209
- /* -------------------------------- Types ------------------------------- */
210
- /*------------------------------------------------------------------------*/
211
-
212
- type Props = {
213
- // Type of the modal
214
- type?: ModalType,
215
- // Size of the modal
216
- size?: ModalSize,
217
- // Title of the modal (if excluded, no header)
218
- title?: React.ReactNode,
219
- // The body of the modal
220
- children?: React.ReactNode,
221
- // Handler to call when modal is closed (if excluded, not closable)
222
- onClose?: (type: ModalButtonType) => void,
223
- // If true, don't allow the user to click the backdrop to exit
224
- dontAllowBackdropExit?: boolean,
225
- // If true, don't show the "X" close button
226
- dontShowXButton?: boolean,
227
- // Custom label for "okay" button
228
- okayLabel?: string,
229
- // Custom variant for "okay" button
230
- okayVariant?: Variant,
231
- // Custom label for "cancel" button
232
- cancelLabel?: string,
233
- // Custom variant for "cancel" button
234
- cancelVariant?: Variant,
235
- // Custom label for "yes" button
236
- yesLabel?: string,
237
- // Custom variant for "yes" button
238
- yesVariant?: Variant,
239
- // Custom label for "no" button
240
- noLabel?: string,
241
- // Custom variant for "no" button
242
- noVariant?: Variant,
243
- // Custom label for "abandon" button
244
- abandonLabel?: string,
245
- // Custom variant for "abandon" button
246
- abandonVariant?: Variant,
247
- // Custom label for "goBack" button
248
- goBackLabel?: string,
249
- // Custom variant for "goBack" button
250
- goBackVariant?: Variant,
251
- // Custom label for "continue" button
252
- continueLabel?: string,
253
- // Custom variant for "continue" button
254
- continueVariant?: Variant,
255
- // Custom label for "imSure" button
256
- imSureLabel?: string,
257
- // Custom variant for "imSure" button
258
- imSureVariant?: Variant,
259
- // Custom label for "delete" button
260
- deleteLabel?: string,
261
- // Custom variant for "delete" button
262
- deleteVariant?: Variant,
263
- // Custom label for "confirm" button
264
- confirmLabel?: string,
265
- // Custom variant for "confirm" button
266
- confirmVariant?: Variant,
267
- // True if modal should be on top of other modals
268
- onTopOfOtherModals?: boolean,
269
- };
270
-
271
210
  /*------------------------------------------------------------------------*/
272
211
  /* ------------------------------ Component ----------------------------- */
273
212
  /*------------------------------------------------------------------------*/
274
213
 
275
- const Modal: React.FC<Props> = (props) => {
214
+ const Modal: React.FC<ModalProps> = (props) => {
276
215
  /*------------------------------------------------------------------------*/
277
216
  /* -------------------------------- Setup ------------------------------- */
278
217
  /*------------------------------------------------------------------------*/
@@ -383,7 +322,7 @@ const Modal: React.FC<Props> = (props) => {
383
322
  <button
384
323
  key={modalButtonType}
385
324
  type="button"
386
- className={`Modal-${modalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`}
325
+ className={`ModalForWrapper-${modalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`}
387
326
  onClick={() => {
388
327
  handleClose(modalButtonType);
389
328
  }}
@@ -408,10 +347,10 @@ const Modal: React.FC<Props> = (props) => {
408
347
  let animationClass = '';
409
348
  let backdropAnimationClass = '';
410
349
  if (animatingIn) {
411
- animationClass = 'Modal-animating-in';
412
- backdropAnimationClass = 'Modal-fading-in';
350
+ animationClass = 'ModalForWrapper-animating-in';
351
+ backdropAnimationClass = 'ModalForWrapper-fading-in';
413
352
  } else if (animatingPop) {
414
- animationClass = 'Modal-animating-pop';
353
+ animationClass = 'ModalForWrapper-animating-pop';
415
354
  }
416
355
 
417
356
  // Render the modal
@@ -433,7 +372,7 @@ const Modal: React.FC<Props> = (props) => {
433
372
  >
434
373
  <style>{style}</style>
435
374
  <div
436
- className={`Modal-backdrop ${backdropAnimationClass}`}
375
+ className={`ModalForWrapper-backdrop ${backdropAnimationClass}`}
437
376
  style={{
438
377
  zIndex: 5000000003,
439
378
  }}
@@ -502,7 +441,7 @@ const Modal: React.FC<Props> = (props) => {
502
441
  {(onClose && !dontShowXButton) && (
503
442
  <button
504
443
  type="button"
505
- className="Modal-x-button btn-close"
444
+ className="ModalForWrapper-x-button btn-close"
506
445
  aria-label="Close"
507
446
  style={{
508
447
  backgroundColor: (
@@ -0,0 +1,72 @@
1
+ // Import types
2
+ import Variant from '../../types/Variant';
3
+ import ModalButtonType from '../../types/ModalButtonType';
4
+ import ModalSize from '../../types/ModalSize';
5
+ import ModalType from '../../types/ModalType';
6
+
7
+ /**
8
+ * Props for the Modal component
9
+ * @author Gabe Abrams
10
+ */
11
+ type ModalProps = {
12
+ // Key of the modal
13
+ key?: string,
14
+ // Type of the modal
15
+ type?: ModalType,
16
+ // Size of the modal
17
+ size?: ModalSize,
18
+ // Title of the modal (if excluded, no header)
19
+ title?: React.ReactNode,
20
+ // The body of the modal
21
+ children?: React.ReactNode,
22
+ // Handler to call when modal is closed (if excluded, not closable)
23
+ onClose?: (type: ModalButtonType) => void,
24
+ // If true, don't allow the user to click the backdrop to exit
25
+ dontAllowBackdropExit?: boolean,
26
+ // If true, don't show the "X" close button
27
+ dontShowXButton?: boolean,
28
+ // Custom label for "okay" button
29
+ okayLabel?: string,
30
+ // Custom variant for "okay" button
31
+ okayVariant?: Variant,
32
+ // Custom label for "cancel" button
33
+ cancelLabel?: string,
34
+ // Custom variant for "cancel" button
35
+ cancelVariant?: Variant,
36
+ // Custom label for "yes" button
37
+ yesLabel?: string,
38
+ // Custom variant for "yes" button
39
+ yesVariant?: Variant,
40
+ // Custom label for "no" button
41
+ noLabel?: string,
42
+ // Custom variant for "no" button
43
+ noVariant?: Variant,
44
+ // Custom label for "abandon" button
45
+ abandonLabel?: string,
46
+ // Custom variant for "abandon" button
47
+ abandonVariant?: Variant,
48
+ // Custom label for "goBack" button
49
+ goBackLabel?: string,
50
+ // Custom variant for "goBack" button
51
+ goBackVariant?: Variant,
52
+ // Custom label for "continue" button
53
+ continueLabel?: string,
54
+ // Custom variant for "continue" button
55
+ continueVariant?: Variant,
56
+ // Custom label for "imSure" button
57
+ imSureLabel?: string,
58
+ // Custom variant for "imSure" button
59
+ imSureVariant?: Variant,
60
+ // Custom label for "delete" button
61
+ deleteLabel?: string,
62
+ // Custom variant for "delete" button
63
+ deleteVariant?: Variant,
64
+ // Custom label for "confirm" button
65
+ confirmLabel?: string,
66
+ // Custom variant for "confirm" button
67
+ confirmVariant?: Variant,
68
+ // True if modal should be on top of other modals
69
+ onTopOfOtherModals?: boolean,
70
+ };
71
+
72
+ export default ModalProps;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * General use modal component
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React, { useEffect, useRef } from 'react';
8
+
9
+ // Import shared types
10
+ import ModalProps from './ModalProps';
11
+
12
+ // Import helpers from AppWrapper
13
+ import { addModal, removeModal, updateModal } from '../AppWrapper';
14
+
15
+ /*------------------------------------------------------------------------*/
16
+ /* --------------------------- Static Helpers --------------------------- */
17
+ /*------------------------------------------------------------------------*/
18
+
19
+ // Next unique id
20
+ let nextUniqueId = 0;
21
+
22
+ /**
23
+ * Get a new unique id for this modal
24
+ * @author Gabe Abrams
25
+ * @returns new unique id
26
+ */
27
+ const getNextUniqueId = (): number => {
28
+ // eslint-disable-next-line no-plusplus
29
+ return nextUniqueId++;
30
+ };
31
+
32
+ /*------------------------------------------------------------------------*/
33
+ /* ------------------------------ Component ----------------------------- */
34
+ /*------------------------------------------------------------------------*/
35
+
36
+ const Modal: React.FC<ModalProps> = (props) => {
37
+ /*------------------------------------------------------------------------*/
38
+ /* -------------------------------- Setup ------------------------------- */
39
+ /*------------------------------------------------------------------------*/
40
+
41
+ /* -------------- Refs -------------- */
42
+
43
+ // Initialize refs
44
+ const id = useRef<number>(getNextUniqueId());
45
+
46
+ /*------------------------------------------------------------------------*/
47
+ /* ------------------------- Lifecycle Functions ------------------------ */
48
+ /*------------------------------------------------------------------------*/
49
+
50
+ /**
51
+ * Mount: add to app wrapper
52
+ * @author Gabe Abrams
53
+ */
54
+ useEffect(
55
+ () => {
56
+ addModal(id.current, props);
57
+ },
58
+ [],
59
+ );
60
+
61
+ /**
62
+ * Update: update modal props in app wrapper when props change
63
+ * @author Gabe Abrams
64
+ */
65
+ useEffect(
66
+ () => {
67
+ // Update modal props
68
+ updateModal(id.current, props);
69
+ },
70
+ [props],
71
+ );
72
+
73
+ /**
74
+ * Unmount: remove from app wrapper when unmounting
75
+ * @author Gabe Abrams
76
+ */
77
+ useEffect(
78
+ () => {
79
+ return () => {
80
+ // Remove modal
81
+ removeModal(id.current);
82
+ };
83
+ },
84
+ [],
85
+ );
86
+
87
+ /*------------------------------------------------------------------------*/
88
+ /* ------------------------------- Render ------------------------------- */
89
+ /*------------------------------------------------------------------------*/
90
+
91
+ /*----------------------------------------*/
92
+ /* --------------- Main UI -------------- */
93
+ /*----------------------------------------*/
94
+
95
+ return (
96
+ <div className="Modal-shell d-none" />
97
+ );
98
+ };
99
+
100
+ /*------------------------------------------------------------------------*/
101
+ /* ------------------------------- Wrap Up ------------------------------ */
102
+ /*------------------------------------------------------------------------*/
103
+
104
+ // Export component
105
+ export default Modal;