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.
- package/.eslintrc.js +95 -0
- package/dist/cjs/index.js +362 -35707
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/AppWrapper.d.ts +1 -1
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/esm/index.js +267 -35594
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/AppWrapper.d.ts +1 -1
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/index.d.ts +2 -1
- package/package.json +14 -4
- package/rollup.config.js +3 -0
- package/src/components/AppWrapper.tsx +1 -1
- package/src/components/ErrorBox.tsx +4 -4
- package/src/components/LoadingSpinner.tsx +3 -3
- package/src/components/Modal.tsx +213 -97
- package/src/components/TabBox.tsx +65 -58
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.
|
|
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 || {}),
|
|
@@ -63,9 +63,9 @@ const ErrorBox: React.FC<Props> = (props) => {
|
|
|
63
63
|
<span
|
|
64
64
|
style={{
|
|
65
65
|
backgroundColor: 'white',
|
|
66
|
-
borderRadius: '
|
|
67
|
-
paddingLeft: '
|
|
68
|
-
paddingRight: '
|
|
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: '
|
|
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:
|
|
23
|
+
font-size: 3rem;
|
|
24
24
|
opacity: 0.6;
|
|
25
|
-
margin-top:
|
|
26
|
-
margin-bottom:
|
|
25
|
+
margin-top: 1rem;
|
|
26
|
+
margin-bottom: 1rem;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
/* First Blip */
|
package/src/components/Modal.tsx
CHANGED
|
@@ -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 =
|
|
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
|
|
253
|
-
const [animatingIn, setAnimatingIn] = useState(
|
|
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 ? '' : '
|
|
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
|
-
<
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
?
|
|
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
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
/*
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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
|
|