dce-reactkit 3.0.0-beta.2 → 3.0.0-beta.22

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.
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A wrapper for the entire React app that adds global functionality like
3
- * handling for fatal error messages
3
+ * handling for fatal error messages, adds bootstrap support
4
4
  * @author Gabe Abrams
5
5
  */
6
6
  import React from 'react';
@@ -6,6 +6,7 @@ import React from 'react';
6
6
  declare type Props = {
7
7
  title: React.ReactNode;
8
8
  children: React.ReactNode;
9
+ noBottomPadding?: boolean;
9
10
  };
10
11
  declare const TabBox: React.FC<Props>;
11
12
  export default TabBox;
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ import React from 'react';
3
3
 
4
4
  /**
5
5
  * A wrapper for the entire React app that adds global functionality like
6
- * handling for fatal error messages
6
+ * handling for fatal error messages, adds bootstrap support
7
7
  * @author Gabe Abrams
8
8
  */
9
9
 
@@ -157,6 +157,7 @@ declare const Modal: React.FC<Props$1>;
157
157
  declare type Props = {
158
158
  title: React.ReactNode;
159
159
  children: React.ReactNode;
160
+ noBottomPadding?: boolean;
160
161
  };
161
162
  declare const TabBox: React.FC<Props>;
162
163
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.22",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -19,18 +19,28 @@
19
19
  },
20
20
  "homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
21
21
  "peerDependencies": {
22
- "react": "^18.0.0",
23
- "react-bootstrap": "^2.3.0",
24
22
  "@fortawesome/free-solid-svg-icons": "^6.1.1",
25
- "@fortawesome/react-fontawesome": "^0.1.18"
23
+ "@fortawesome/react-fontawesome": "^0.1.18",
24
+ "bootstrap": "^5.1.3",
25
+ "react": "^18.0.0"
26
26
  },
27
27
  "devDependencies": {
28
28
  "@rollup/plugin-commonjs": "^22.0.0",
29
29
  "@rollup/plugin-node-resolve": "^13.2.1",
30
30
  "@rollup/plugin-typescript": "^8.3.2",
31
31
  "@types/react": "^18.0.7",
32
+ "@typescript-eslint/eslint-plugin": "^5.21.0",
33
+ "@typescript-eslint/parser": "^5.21.0",
34
+ "eslint": "^8.14.0",
35
+ "eslint-config-airbnb": "^19.0.4",
36
+ "eslint-config-airbnb-typescript": "^17.0.0",
37
+ "eslint-plugin-import": "^2.26.0",
38
+ "eslint-plugin-jsx-a11y": "^6.5.1",
39
+ "eslint-plugin-react": "^7.29.4",
40
+ "eslint-plugin-react-hooks": "^4.4.0",
32
41
  "rollup": "^2.70.2",
33
42
  "rollup-plugin-dts": "^4.2.1",
43
+ "rollup-plugin-sourcemaps": "^0.6.3",
34
44
  "typescript": "^4.6.3"
35
45
  }
36
46
  }
package/rollup.config.js CHANGED
@@ -2,6 +2,8 @@ import resolve from "@rollup/plugin-node-resolve";
2
2
  import commonjs from "@rollup/plugin-commonjs";
3
3
  import typescript from "@rollup/plugin-typescript";
4
4
  import dts from "rollup-plugin-dts";
5
+ import sourcemaps from 'rollup-plugin-sourcemaps';
6
+
5
7
 
6
8
  const packageJson = require("./package.json");
7
9
 
@@ -24,6 +26,7 @@ export default [
24
26
  resolve(),
25
27
  commonjs(),
26
28
  typescript({ tsconfig: "./tsconfig.json" }),
29
+ sourcemaps(),
27
30
  ],
28
31
  external: [
29
32
  ...Object.keys(packageJson.dependencies || {}),
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * A wrapper for the entire React app that adds global functionality like
3
- * handling for fatal error messages
3
+ * handling for fatal error messages, adds bootstrap support
4
4
  * @author Gabe Abrams
5
5
  */
6
6
 
@@ -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: 3rem;
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 */
@@ -7,7 +7,6 @@
7
7
  import React, { useState, useEffect } from 'react';
8
8
 
9
9
  // Import other components
10
- import BootstrapModal from 'react-bootstrap/Modal';
11
10
  import waitMs from '../helpers/waitMs';
12
11
 
13
12
  // Import types
@@ -16,69 +15,12 @@ import ModalButtonType from '../types/ModalButtonType';
16
15
  import ModalSize from '../types/ModalSize';
17
16
  import ModalType from '../types/ModalType';
18
17
 
19
- /*------------------------------------------------------------------------*/
20
- /* Style */
21
- /*------------------------------------------------------------------------*/
22
-
23
- const style = `
24
- /* More opaque backdrop */
25
- .Modal-backdrop {
26
- background-color: black !important;
27
- opacity: 0.8 !important;
28
- z-index: 10000000 !important;
29
- }
30
-
31
- /* Customize the modal header and body */
32
- .modal-header {
33
- /* Dark theme */
34
- background-color: #222 !important;
35
- color: white !important;
36
-
37
- /* Border */
38
- border: 1px solid #545454 !important;
39
- }
40
- .modal-body {
41
- /* Dark theme */
42
- background-color: #222 !important;
43
- color: white !important;
44
-
45
- /* Border */
46
- border-top: 0 !important;
47
- border-left: 1px solid #545454 !important;
48
- border-bottom: 1px solid #545454 !important;
49
- border-right: 1px solid #545454 !important;
50
- border-bottom-left-radius: 3px;
51
- border-bottom-right-radius: 3px;
52
- }
53
-
54
- /* Create our own animation */
55
- .modal-content {
56
- animation-name: Modal-drop-in;
57
- animation-duration: 0.4s;
58
- animation-iteration-count: 1;
59
- animation-fill-mode: forwards;
60
- animation-timing-function: ease-out;
61
- }
62
- @keyframes Modal-drop-in {
63
- 0% {
64
- transform: scale(0.8);
65
- opacity: 0.5;
66
- }
67
- 100% {
68
- transform: scale(1);
69
- opacity: 1;
70
- }
71
- }
72
- `;
73
-
74
18
  /*------------------------------------------------------------------------*/
75
19
  /* Constants */
76
20
  /*------------------------------------------------------------------------*/
77
21
 
78
22
  // Constants
79
- const MS_TO_ANIMATE = 400; // Time to animate in/out (defined by bootstrap)
80
- const MS_ANIMATE_IN_DELAY = 10;
81
- // Time to wait before animating in (must be >0 or animation won't trigger)
23
+ const MS_TO_ANIMATE = 200; // Animation duration
82
24
 
83
25
  // Modal type to list of buttons
84
26
  const modalTypeToModalButtonTypes: {
@@ -162,6 +104,116 @@ const ModalButtonTypeToLabelAndVariant = {
162
104
  },
163
105
  };
164
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
+
165
217
  /*------------------------------------------------------------------------*/
166
218
  /* Types */
167
219
  /*------------------------------------------------------------------------*/
@@ -246,11 +298,17 @@ const Modal: React.FC<Props> = (props) => {
246
298
 
247
299
  /* -------------- State ------------- */
248
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
+
249
305
  // If true, the modal is shown
250
306
  const [visible, setVisible] = useState(false);
251
307
 
252
- // True if currently animating in
253
- 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);
254
312
 
255
313
  /*------------------------------------------------------------------------*/
256
314
  /* Lifecycle Functions */
@@ -263,13 +321,10 @@ const Modal: React.FC<Props> = (props) => {
263
321
  useEffect(
264
322
  () => {
265
323
  (async () => {
266
- // Start the component animating in
267
- await waitMs(MS_ANIMATE_IN_DELAY);
268
- setAnimatingIn(true);
269
-
270
324
  // Wait and then set visible to true
271
325
  await waitMs(MS_TO_ANIMATE);
272
326
  setVisible(true);
327
+ setAnimatingIn(false);
273
328
  })();
274
329
  },
275
330
  [],
@@ -303,16 +358,23 @@ const Modal: React.FC<Props> = (props) => {
303
358
 
304
359
  // Update the state
305
360
  setVisible(false);
361
+ setAnimatingOut(true);
306
362
 
307
363
  // Call the handler after the modal has animated out
308
364
  await waitMs(MS_TO_ANIMATE);
309
365
  onClose(ModalButtonType);
366
+ setGone(true);
310
367
  };
311
368
 
312
369
  /*------------------------------------------------------------------------*/
313
370
  /* Render */
314
371
  /*------------------------------------------------------------------------*/
315
372
 
373
+ // Return nothing if gone
374
+ if (gone) {
375
+ return null;
376
+ }
377
+
316
378
  /*----------------------------------------*/
317
379
  /* Footer */
318
380
  /*----------------------------------------*/
@@ -345,7 +407,7 @@ const Modal: React.FC<Props> = (props) => {
345
407
  return (
346
408
  <button
347
409
  type="button"
348
- className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'mr-1'}`}
410
+ className={`Modal-${ModalButtonType}-button btn btn-${variant} ${last ? '' : 'me-1'}`}
349
411
  onClick={() => {
350
412
  handleClose(ModalButtonType);
351
413
  }}
@@ -366,47 +428,101 @@ const Modal: React.FC<Props> = (props) => {
366
428
  : undefined
367
429
  );
368
430
 
431
+ // Choose an animation
432
+ let animationClass = '';
433
+ let backdropAnimationClass = '';
434
+ if (animatingIn) {
435
+ animationClass = 'Modal-animating-in';
436
+ backdropAnimationClass = 'Modal-fading-in';
437
+ } else if (animatingOut) {
438
+ animationClass = 'Modal-animating-out';
439
+ backdropAnimationClass = 'Modal-fading-out';
440
+ } else if (animatingPop) {
441
+ animationClass = 'Modal-animating-pop';
442
+ }
443
+
369
444
  // Render the modal
370
445
  return (
371
- <BootstrapModal
372
- show={visible}
373
- size={size !== ModalSize.Medium ? size : undefined}
374
- onHide={() => {
375
- if (!dontAllowBackdropExit) {
376
- handleClose(ModalButtonType.Cancel);
377
- }
378
- }}
446
+ <div
447
+ className={`modal show modal-dialog-scrollable modal-dialog-centered modal-${size}`}
448
+ tabIndex={-1}
379
449
  style={{
380
450
  zIndex: (
381
451
  onTopOfOtherModals
382
- ? 6000000000
452
+ ? 5000000001
383
453
  : 5000000000
384
454
  ),
455
+ display: 'block',
456
+ margin: 'auto',
457
+ left: 0,
458
+ right: 0,
385
459
  }}
386
- backdropClassName="Modal-backdrop"
387
- centered
388
460
  >
389
461
  <style>{style}</style>
390
- {title && (
391
- <BootstrapModal.Header
392
- closeButton={!!onClose}
393
- >
394
- <BootstrapModal.Title>
395
- {title}
396
- </BootstrapModal.Title>
397
- </BootstrapModal.Header>
398
- )}
399
- {children && (
400
- <BootstrapModal.Body>
401
- {children}
402
- </BootstrapModal.Body>
403
- )}
404
- {footer && (
405
- <BootstrapModal.Footer>
406
- {footer}
407
- </BootstrapModal.Footer>
408
- )}
409
- </BootstrapModal>
462
+ <div
463
+ className={`Modal-backdrop ${backdropAnimationClass}`}
464
+ style={{
465
+ zIndex: 5000000003,
466
+ }}
467
+ onClick={async () => {
468
+ // Skip if exit via backdrop not allowed
469
+ if (dontAllowBackdropExit || !onClose) {
470
+ // Show pop animation
471
+ if (!animatingPop) {
472
+ setAnimatingPop(true);
473
+
474
+ // Wait then stop pop animation
475
+ await waitMs(MS_TO_ANIMATE);
476
+ setAnimatingPop(false);
477
+ }
478
+ return;
479
+ }
480
+ // Handle close
481
+ handleClose(ModalButtonType.Cancel);
482
+ }}
483
+ />
484
+ <div
485
+ className={`modal-dialog ${animationClass}`}
486
+ style={{
487
+ zIndex: 5000000002,
488
+ }}
489
+ >
490
+ <div className="modal-content">
491
+ <div className="modal-header">
492
+ <h5
493
+ className="modal-title"
494
+ style={{
495
+ fontWeight: 'bold',
496
+ }}
497
+ >
498
+ {title}
499
+ </h5>
500
+
501
+ {onClose && (
502
+ <button
503
+ type="button"
504
+ className="btn-close"
505
+ aria-label="Close"
506
+ onClick={() => {
507
+ // Handle close
508
+ handleClose(ModalButtonType.Cancel);
509
+ }}
510
+ />
511
+ )}
512
+ </div>
513
+ {children && (
514
+ <div className="modal-body">
515
+ {children}
516
+ </div>
517
+ )}
518
+ {footer && (
519
+ <div className="modal-footer pt-1 pb-1">
520
+ {footer}
521
+ </div>
522
+ )}
523
+ </div>
524
+ </div>
525
+ </div>
410
526
  );
411
527
  };
412
528
 
@@ -15,6 +15,8 @@ type Props = {
15
15
  title: React.ReactNode,
16
16
  // Children/contents inside the box
17
17
  children: React.ReactNode,
18
+ // If true, don't add padding below the tab box
19
+ noBottomPadding?: boolean,
18
20
  };
19
21
 
20
22
  /*------------------------------------------------------------------------*/
@@ -22,63 +24,67 @@ type Props = {
22
24
  /*------------------------------------------------------------------------*/
23
25
 
24
26
  const style = `
25
- /* Tab Box */
26
- .TabBox-box {
27
- /* Light Border */
28
- border: 2px solid #dedede;
29
-
30
- /* Rounded Corners (except top-left) */
31
- border-bottom-right-radius: 5px;
32
- border-bottom-left-radius: 5px;
33
- border-top-right-radius: 5px;
34
-
35
- /* Very Light Gray Border */
36
- background: #fdfdfd;
37
-
38
- /* Align Contents on Left */
39
- text-align: left;
40
- }
41
-
42
- /* Container for Title */
43
- .TabBox-title-container {
44
- /* Place on Left */
45
- position: relative;
46
- left: 0;
47
- text-align: left;
48
- }
49
-
50
- /* Tab-style Title */
51
- .TabBox-title {
52
- /* Place so it Barely Overlaps the Box Border */
53
- display: inline-block;
54
- position: relative;
55
- top: 2px; /* Gives Illusion that Border Doesn't Exist Below Tab */
56
-
57
- /* Title-sized Font */
58
- font-size: 25px;
59
-
60
- /* Add Border on Top and Sides */
61
- border-top: 2px solid #dedede;
62
- border-left: 2px solid #dedede;
63
- border-right: 2px solid #dedede;
64
-
65
- /* Round the Top Corners */
66
- border-top-left-radius: 5px;
67
- border-top-right-radius: 5px;
68
-
69
- /* Add Text Padding */
70
- padding-left: 12px;
71
- padding-right: 12px;
72
-
73
- /* Match Background Color of Box */
74
- background: #fdfdfd;
75
- }
76
-
77
- /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
78
- .TabBox-children {
79
- position: relative;
80
- z-index: 1;
81
- }
27
+ /* Tab Box */
28
+ .TabBox-box {
29
+ /* Light Border */
30
+ border: 0.15rem solid #dedede;
31
+
32
+ /* Rounded Corners (except top-left) */
33
+ border-bottom-right-radius: 0.3rem;
34
+ border-bottom-left-radius: 0.3rem;
35
+ border-top-right-radius: 0.3rem;
36
+
37
+ /* Very Light Gray Border */
38
+ background: #fdfdfd;
39
+
40
+ /* Align Contents on Left */
41
+ text-align: left;
42
+
43
+ /* Add Text Padding */
44
+ padding-left: 0.3rem;
45
+ padding-right: 0.3rem;
46
+ }
47
+
48
+ /* Container for Title */
49
+ .TabBox-title-container {
50
+ /* Place on Left */
51
+ position: relative;
52
+ left: 0;
53
+ text-align: left;
54
+ }
55
+
56
+ /* Tab-style Title */
57
+ .TabBox-title {
58
+ /* Place so it Barely Overlaps the Box Border */
59
+ display: inline-block;
60
+ position: relative;
61
+ top: 0.15rem; /* Gives Illusion that Border Doesn't Exist Below Tab */
62
+
63
+ /* Title-sized Font */
64
+ font-size: 1.5rem;
65
+
66
+ /* Add Border on Top and Sides */
67
+ border-top: 0.15rem solid #dedede;
68
+ border-left: 0.15rem solid #dedede;
69
+ border-right: 0.15rem solid #dedede;
70
+
71
+ /* Round the Top Corners */
72
+ border-top-left-radius: 0.3rem;
73
+ border-top-right-radius: 0.3rem;
74
+
75
+ /* Add Text Padding */
76
+ padding-left: 0.5rem;
77
+ padding-right: 0.5rem;
78
+
79
+ /* Match Background Color of Box */
80
+ background: #fdfdfd;
81
+ }
82
+
83
+ /* Make the TabBox's Children Appear Above Title if Overlap Occurs */
84
+ .TabBox-children {
85
+ position: relative;
86
+ z-index: 1;
87
+ }
82
88
  `;
83
89
 
84
90
  /*------------------------------------------------------------------------*/
@@ -95,6 +101,7 @@ const TabBox: React.FC<Props> = (props) => {
95
101
  const {
96
102
  title,
97
103
  children,
104
+ noBottomPadding,
98
105
  } = props;
99
106
 
100
107
  /*------------------------------------------------------------------------*/
@@ -107,7 +114,7 @@ const TabBox: React.FC<Props> = (props) => {
107
114
 
108
115
  // Full UI
109
116
  return (
110
- <div>
117
+ <div className={`TabBox-container ${noBottomPadding ? '' : 'mb-2'}`}>
111
118
  {/* Style */}
112
119
  <style>{style}</style>
113
120