dce-reactkit 3.12.1-beta-cross-server.8 → 4.0.0-beta-date-and-time.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.
- package/.vscode/settings.json +3 -1
- package/README.md +0 -20
- package/dist/cjs/index.js +7838 -15905
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/cjs/types/components/LoadingSpinner.d.ts +2 -2
- package/dist/cjs/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/cjs/types/components/SimpleDateChooser.d.ts +3 -1
- package/dist/cjs/types/components/SimpleTimeChooser.d.ts +20 -0
- package/dist/cjs/types/components/TabBox.d.ts +1 -0
- package/dist/cjs/types/components/Tooltip.d.ts +1 -1
- package/dist/cjs/types/helpers/getWordCount.d.ts +10 -0
- package/dist/cjs/types/index.d.ts +13 -13
- package/dist/cjs/types/types/ParamType.d.ts +10 -10
- package/dist/cjs/types/types/ReactKitErrorCode.d.ts +2 -28
- package/dist/esm/index.js +6661 -14723
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/index.d.ts +1 -0
- package/dist/esm/types/components/LoadingSpinner.d.ts +2 -2
- package/dist/esm/types/components/Modal/ModalProps.d.ts +2 -0
- package/dist/esm/types/components/SimpleDateChooser.d.ts +3 -1
- package/dist/esm/types/components/SimpleTimeChooser.d.ts +20 -0
- package/dist/esm/types/components/TabBox.d.ts +1 -0
- package/dist/esm/types/components/Tooltip.d.ts +1 -1
- package/dist/esm/types/helpers/getWordCount.d.ts +10 -0
- package/dist/esm/types/index.d.ts +13 -13
- package/dist/esm/types/types/ParamType.d.ts +10 -10
- package/dist/esm/types/types/ReactKitErrorCode.d.ts +2 -28
- package/dist/index.d.ts +189 -379
- package/package.json +5 -11
- package/src/components/AppWrapper.tsx +3 -1
- package/src/components/CheckboxButton.tsx +1 -1
- package/src/components/Dropdown.tsx +1 -1
- package/src/components/IntelliTable.tsx +1 -1
- package/src/components/ItemPicker/NestableItemList.tsx +1 -0
- package/src/components/ItemPicker/index.tsx +96 -0
- package/src/components/LogReviewer.tsx +2 -2
- package/src/components/Modal/ModalProps.ts +4 -0
- package/src/components/Modal/index.tsx +60 -21
- package/src/components/RadioButton.tsx +3 -1
- package/src/components/SimpleDateChooser.tsx +62 -26
- package/src/components/SimpleTimeChooser.tsx +195 -0
- package/src/components/TabBox.tsx +27 -9
- package/src/components/Tooltip.tsx +1 -1
- package/src/helpers/getWordCount.ts +26 -0
- package/src/{client → helpers}/initClient.tsx +5 -1
- package/src/helpers/logClientEvent.tsx +3 -1
- package/src/helpers/visitServerEndpoint.tsx +1 -1
- package/src/index.ts +22 -24
- package/src/types/ParamType.ts +10 -10
- package/src/types/ReactKitErrorCode.tsx +3 -31
- package/dist/cjs/types/components/ItemPicker/index.test.d.ts +0 -1
- package/dist/cjs/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/cjs/types/helpers/dataSigner.d.ts +0 -40
- package/dist/cjs/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/cjs/types/helpers/handleError.d.ts +0 -18
- package/dist/cjs/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/cjs/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -20
- package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -27
- package/dist/cjs/types/server/initCrossServerCredentialCollection.d.ts +0 -8
- package/dist/cjs/types/server/initLogCollection.d.ts +0 -8
- package/dist/cjs/types/server/initServer.d.ts +0 -53
- package/dist/cjs/types/types/CrossServerCredential.d.ts +0 -11
- package/dist/esm/types/components/ItemPicker/index.test.d.ts +0 -1
- package/dist/esm/types/helpers/addDBEditorEndpoints.d.ts +0 -41
- package/dist/esm/types/helpers/dataSigner.d.ts +0 -40
- package/dist/esm/types/helpers/genRouteHandler.d.ts +0 -76
- package/dist/esm/types/helpers/handleError.d.ts +0 -18
- package/dist/esm/types/helpers/handleSuccess.d.ts +0 -8
- package/dist/esm/types/helpers/parseUserAgent.d.ts +0 -17
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +0 -20
- package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +0 -27
- package/dist/esm/types/server/initCrossServerCredentialCollection.d.ts +0 -8
- package/dist/esm/types/server/initLogCollection.d.ts +0 -8
- package/dist/esm/types/server/initServer.d.ts +0 -53
- package/dist/esm/types/types/CrossServerCredential.d.ts +0 -11
- package/src/components/ItemPicker/index.test.tsx +0 -783
- package/src/helpers/addDBEditorEndpoints.ts +0 -128
- package/src/helpers/dataSigner.ts +0 -342
- package/src/helpers/genRouteHandler.ts +0 -911
- package/src/helpers/handleError.ts +0 -65
- package/src/helpers/handleSuccess.ts +0 -18
- package/src/helpers/parseUserAgent.ts +0 -108
- package/src/helpers/visitEndpointOnAnotherServer/index.ts +0 -152
- package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +0 -162
- package/src/server/initCrossServerCredentialCollection.ts +0 -16
- package/src/server/initLogCollection.ts +0 -27
- package/src/server/initServer.ts +0 -305
- package/src/types/CrossServerCredential.ts +0 -16
- /package/dist/cjs/types/{client → helpers}/initClient.d.ts +0 -0
- /package/dist/esm/types/{client → helpers}/initClient.d.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dce-reactkit",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-beta-date-and-time.1",
|
|
4
4
|
"description": "Shared components for Harvard DCE apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"qs": "^6.x.x",
|
|
29
28
|
"react-select": "^5.7.3"
|
|
30
29
|
},
|
|
31
30
|
"peerDependencies": {
|
|
@@ -34,22 +33,19 @@
|
|
|
34
33
|
"@fortawesome/free-solid-svg-icons": "^x.x.x",
|
|
35
34
|
"@fortawesome/react-fontawesome": "^x.x.x",
|
|
36
35
|
"bootstrap": "^5.x.x",
|
|
37
|
-
"
|
|
38
|
-
"react": "^x.x.x",
|
|
39
|
-
"react-dom": "^x.x.x"
|
|
36
|
+
"react": "^x.x.x"
|
|
40
37
|
},
|
|
41
38
|
"devDependencies": {
|
|
42
39
|
"@rollup/plugin-commonjs": "^22.0.0",
|
|
43
40
|
"@rollup/plugin-json": "^4.1.0",
|
|
44
41
|
"@rollup/plugin-node-resolve": "^13.2.1",
|
|
45
|
-
"@rollup/plugin-typescript": "^
|
|
42
|
+
"@rollup/plugin-typescript": "^10.0.1",
|
|
46
43
|
"@types/bootstrap": "^5.2.8",
|
|
47
44
|
"@types/express": "^4.17.17",
|
|
48
45
|
"@types/jest": "^28.1.7",
|
|
49
|
-
"@types/jwt-simple": "^0.5.36",
|
|
50
46
|
"@types/node": "^20.2.5",
|
|
51
|
-
"@types/
|
|
52
|
-
"@types/react": "^
|
|
47
|
+
"@types/react": "^19.0.10",
|
|
48
|
+
"@types/react-dom": "^19.0.4",
|
|
53
49
|
"@typescript-eslint/eslint-plugin": "^5.59.9",
|
|
54
50
|
"@typescript-eslint/parser": "^5.59.9",
|
|
55
51
|
"eslint": "^8.42.0",
|
|
@@ -61,8 +57,6 @@
|
|
|
61
57
|
"eslint-plugin-react-hooks": "^4.6.0",
|
|
62
58
|
"jest": "^28.1.3",
|
|
63
59
|
"jest-environment-jsdom": "^28.1.3",
|
|
64
|
-
"oauth-signature": "^1.5.0",
|
|
65
|
-
"raixa": "^1.0.21",
|
|
66
60
|
"rollup": "^2.70.2",
|
|
67
61
|
"rollup-plugin-dts": "^4.2.1",
|
|
68
62
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
@@ -32,10 +32,12 @@ import ErrorWithCode from '../errors/ErrorWithCode';
|
|
|
32
32
|
// TODO: fix dependency cycle
|
|
33
33
|
// eslint-disable-next-line import/no-cycle
|
|
34
34
|
import logClientEvent from '../helpers/logClientEvent';
|
|
35
|
+
// TODO: fix dependency cycle
|
|
36
|
+
// eslint-disable-next-line import/no-cycle
|
|
35
37
|
import {
|
|
36
38
|
getSessionExpiredMessage,
|
|
37
39
|
isDarkModeOn,
|
|
38
|
-
} from '../
|
|
40
|
+
} from '../helpers/initClient';
|
|
39
41
|
import waitMs from '../helpers/waitMs';
|
|
40
42
|
|
|
41
43
|
// Import constants
|
|
@@ -15,7 +15,7 @@ import { faSquare, faSquareMinus } from '@fortawesome/free-regular-svg-icons';
|
|
|
15
15
|
import Variant from '../types/Variant';
|
|
16
16
|
|
|
17
17
|
// Import shared helpers
|
|
18
|
-
import { isDarkModeOn } from '../
|
|
18
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
19
19
|
|
|
20
20
|
/*------------------------------------------------------------------------*/
|
|
21
21
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -16,7 +16,7 @@ import Variant from '../types/Variant';
|
|
|
16
16
|
import DropdownItemType from '../types/DropdownItemType';
|
|
17
17
|
|
|
18
18
|
// Check dark mode
|
|
19
|
-
import { isDarkModeOn } from '../
|
|
19
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
20
20
|
|
|
21
21
|
/*------------------------------------------------------------------------*/
|
|
22
22
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -315,7 +315,7 @@ const IntelliTable: React.FC<Props> = (props) => {
|
|
|
315
315
|
checked={columnVisibilityMap[column.param]}
|
|
316
316
|
ariaLabel={`show "${column.title}" column in the ${title} table`}
|
|
317
317
|
checkedVariant={Variant.Light}
|
|
318
|
-
uncheckedVariant={Variant.
|
|
318
|
+
uncheckedVariant={Variant.Light}
|
|
319
319
|
/>
|
|
320
320
|
);
|
|
321
321
|
})
|
|
@@ -33,6 +33,8 @@ type Props = {
|
|
|
33
33
|
onChanged: (updatedItems: PickableItem[]) => void,
|
|
34
34
|
// If true, don't add margin to bottom of item picker
|
|
35
35
|
noBottomMargin?: boolean,
|
|
36
|
+
// If true, hide select all or none buttons
|
|
37
|
+
hideSelectAllOrNoneButtons?: boolean,
|
|
36
38
|
};
|
|
37
39
|
|
|
38
40
|
/*------------------------------------------------------------------------*/
|
|
@@ -52,12 +54,61 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
52
54
|
items,
|
|
53
55
|
onChanged,
|
|
54
56
|
noBottomMargin,
|
|
57
|
+
hideSelectAllOrNoneButtons,
|
|
55
58
|
} = props;
|
|
56
59
|
|
|
57
60
|
/*------------------------------------------------------------------------*/
|
|
58
61
|
/* ------------------------- Component Functions ------------------------ */
|
|
59
62
|
/*------------------------------------------------------------------------*/
|
|
60
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Updates the checked state of an item (including all children)
|
|
66
|
+
* @author Yuen Ler Chow
|
|
67
|
+
* @param item the item to update
|
|
68
|
+
* @param checked the new checked state
|
|
69
|
+
* @returns the updated item
|
|
70
|
+
*/
|
|
71
|
+
const updateItemChecked = (
|
|
72
|
+
item: PickableItem,
|
|
73
|
+
checked: boolean,
|
|
74
|
+
): PickableItem => {
|
|
75
|
+
if (item.isGroup) {
|
|
76
|
+
return {
|
|
77
|
+
...item,
|
|
78
|
+
children: item.children.map((child) => {
|
|
79
|
+
return updateItemChecked(child, checked);
|
|
80
|
+
}),
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
return { ...item, checked };
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Selects all items in the list
|
|
88
|
+
* @author Yuen Ler Chow
|
|
89
|
+
*/
|
|
90
|
+
const handleSelectAll = () => {
|
|
91
|
+
const updatedItems = items.map(
|
|
92
|
+
(item) => {
|
|
93
|
+
return updateItemChecked(item, true);
|
|
94
|
+
},
|
|
95
|
+
);
|
|
96
|
+
onChanged(updatedItems);
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Deselects all items in the list
|
|
101
|
+
* @author Yuen Ler Chow
|
|
102
|
+
*/
|
|
103
|
+
const handleDeselectAll = () => {
|
|
104
|
+
const updatedItems = items.map(
|
|
105
|
+
(item) => {
|
|
106
|
+
return updateItemChecked(item, false);
|
|
107
|
+
},
|
|
108
|
+
);
|
|
109
|
+
onChanged(updatedItems);
|
|
110
|
+
};
|
|
111
|
+
|
|
61
112
|
/*------------------------------------------------------------------------*/
|
|
62
113
|
/* ------------------------------- Render ------------------------------- */
|
|
63
114
|
/*------------------------------------------------------------------------*/
|
|
@@ -66,10 +117,55 @@ const ItemPicker: React.FC<Props> = (props) => {
|
|
|
66
117
|
/* --------------- Main UI -------------- */
|
|
67
118
|
/*----------------------------------------*/
|
|
68
119
|
|
|
120
|
+
// Select all/none
|
|
121
|
+
const selectAllOrNone = (
|
|
122
|
+
!hideSelectAllOrNoneButtons
|
|
123
|
+
? (
|
|
124
|
+
<div
|
|
125
|
+
className="d-flex h-100 align-items-end flex-row"
|
|
126
|
+
>
|
|
127
|
+
<div className="d-flex justify-content-end">
|
|
128
|
+
{/* Label */}
|
|
129
|
+
<div
|
|
130
|
+
className="me-2"
|
|
131
|
+
style={{ fontSize: '1.2rem' }}
|
|
132
|
+
>
|
|
133
|
+
Select
|
|
134
|
+
</div>
|
|
135
|
+
{/* Buttons */}
|
|
136
|
+
<div className="btn-group" role="group">
|
|
137
|
+
{/* All */}
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
style={{ borderRight: '0.1rem solid white' }}
|
|
141
|
+
aria-label="Select all contexts"
|
|
142
|
+
className="btn btn-secondary py-0"
|
|
143
|
+
onClick={handleSelectAll}
|
|
144
|
+
>
|
|
145
|
+
All
|
|
146
|
+
</button>
|
|
147
|
+
{/* None */}
|
|
148
|
+
<button
|
|
149
|
+
type="button"
|
|
150
|
+
aria-label="Deselect all contexts"
|
|
151
|
+
className="btn btn-secondary py-0"
|
|
152
|
+
onClick={handleDeselectAll}
|
|
153
|
+
>
|
|
154
|
+
None
|
|
155
|
+
</button>
|
|
156
|
+
</div>
|
|
157
|
+
</div>
|
|
158
|
+
</div>
|
|
159
|
+
)
|
|
160
|
+
: undefined
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Main UI
|
|
69
164
|
return (
|
|
70
165
|
<TabBox
|
|
71
166
|
title={title}
|
|
72
167
|
noBottomMargin={noBottomMargin}
|
|
168
|
+
topRightChildren={selectAllOrNone}
|
|
73
169
|
>
|
|
74
170
|
<div style={{ overflowX: 'auto' }}>
|
|
75
171
|
<NestableItemList
|
|
@@ -1182,7 +1182,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1182
1182
|
year={dateFilterState.startDate.year}
|
|
1183
1183
|
month={dateFilterState.startDate.month}
|
|
1184
1184
|
day={dateFilterState.startDate.day}
|
|
1185
|
-
|
|
1185
|
+
dontAllowFuture
|
|
1186
1186
|
numMonthsToShow={36}
|
|
1187
1187
|
onChange={(month, day, year) => {
|
|
1188
1188
|
dateFilterState.startDate = { month, day, year };
|
|
@@ -1198,7 +1198,7 @@ const LogReviewer: React.FC<Props> = (props) => {
|
|
|
1198
1198
|
year={dateFilterState.endDate.year}
|
|
1199
1199
|
month={dateFilterState.endDate.month}
|
|
1200
1200
|
day={dateFilterState.endDate.day}
|
|
1201
|
-
|
|
1201
|
+
dontAllowFuture
|
|
1202
1202
|
numMonthsToShow={12}
|
|
1203
1203
|
onChange={(month, day, year) => {
|
|
1204
1204
|
if (
|
|
@@ -69,6 +69,10 @@ type ModalProps = {
|
|
|
69
69
|
confirmVariant?: Variant,
|
|
70
70
|
// True if modal should be on top of other modals
|
|
71
71
|
onTopOfOtherModals?: boolean,
|
|
72
|
+
// If true, the modal is loading
|
|
73
|
+
isLoading?: boolean,
|
|
74
|
+
// If true, the loading is cancelable
|
|
75
|
+
isLoadingCancelable?: boolean,
|
|
72
76
|
};
|
|
73
77
|
|
|
74
78
|
export default ModalProps;
|
|
@@ -14,6 +14,9 @@ import React, { useState, useEffect, useRef } from 'react';
|
|
|
14
14
|
import ReactDOM from 'react-dom';
|
|
15
15
|
|
|
16
16
|
// Import other components
|
|
17
|
+
import LoadingSpinner from '../LoadingSpinner';
|
|
18
|
+
|
|
19
|
+
// Import helpers
|
|
17
20
|
import waitMs from '../../helpers/waitMs';
|
|
18
21
|
|
|
19
22
|
// Import types
|
|
@@ -29,7 +32,7 @@ import NUM_MODAL_PORTALS from '../../constants/NUM_MODAL_PORTALS';
|
|
|
29
32
|
// Import shared helpers
|
|
30
33
|
// TODO: fix dependency cycle
|
|
31
34
|
// eslint-disable-next-line import/no-cycle
|
|
32
|
-
import { isDarkModeOn } from '../../
|
|
35
|
+
import { isDarkModeOn } from '../../helpers/initClient';
|
|
33
36
|
|
|
34
37
|
/*------------------------------------------------------------------------*/
|
|
35
38
|
/* ------------------------------ Constants ----------------------------- */
|
|
@@ -41,6 +44,7 @@ const BASE_Z_INDEX_ON_TOP = 2000000000;
|
|
|
41
44
|
|
|
42
45
|
// Constants
|
|
43
46
|
const MS_TO_ANIMATE = 200; // Animation duration
|
|
47
|
+
const MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR = 1000;
|
|
44
48
|
|
|
45
49
|
// Modal type to list of buttons
|
|
46
50
|
const modalTypeToModalButtonTypes: {
|
|
@@ -256,6 +260,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
256
260
|
dontAllowBackdropExit,
|
|
257
261
|
dontShowXButton,
|
|
258
262
|
onTopOfOtherModals,
|
|
263
|
+
isLoading,
|
|
264
|
+
isLoadingCancelable,
|
|
259
265
|
} = props;
|
|
260
266
|
|
|
261
267
|
// Determine if no header either
|
|
@@ -266,6 +272,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
266
272
|
// True if animation is in use
|
|
267
273
|
const [animatingIn, setAnimatingIn] = useState(true);
|
|
268
274
|
const [animatingPop, setAnimatingPop] = useState(false);
|
|
275
|
+
const [showModal, setShowModal] = useState(false);
|
|
269
276
|
|
|
270
277
|
/* -------------- Refs -------------- */
|
|
271
278
|
|
|
@@ -298,6 +305,15 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
298
305
|
if (mounted.current) {
|
|
299
306
|
setAnimatingIn(false);
|
|
300
307
|
}
|
|
308
|
+
if (isLoading) {
|
|
309
|
+
// wait before showing modal
|
|
310
|
+
await waitMs(MS_TO_WAIT_BEFORE_SHOWING_LOADING_INDICATOR);
|
|
311
|
+
if (mounted.current) {
|
|
312
|
+
setShowModal(true);
|
|
313
|
+
}
|
|
314
|
+
} else {
|
|
315
|
+
setShowModal(true);
|
|
316
|
+
}
|
|
301
317
|
})();
|
|
302
318
|
|
|
303
319
|
return () => {
|
|
@@ -400,6 +416,17 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
400
416
|
animationClass = 'Modal-animating-pop';
|
|
401
417
|
}
|
|
402
418
|
|
|
419
|
+
// default to show close button when not loading and not show close button when loading
|
|
420
|
+
// unless downShowXButton or isLoadingCancelable
|
|
421
|
+
const showCloseButton = (
|
|
422
|
+
// The modal must have an onClose function
|
|
423
|
+
onClose
|
|
424
|
+
// ...and the close button is allowed to be visible
|
|
425
|
+
&& !dontShowXButton
|
|
426
|
+
// ...and should not be shown if the modal is loading and not cancelable
|
|
427
|
+
&& !(isLoading && !isLoadingCancelable)
|
|
428
|
+
);
|
|
429
|
+
|
|
403
430
|
// Render the modal
|
|
404
431
|
const contentToRender = (
|
|
405
432
|
<div
|
|
@@ -436,6 +463,8 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
436
463
|
handleClose(ModalButtonType.Cancel);
|
|
437
464
|
}}
|
|
438
465
|
/>
|
|
466
|
+
|
|
467
|
+
{showModal && (
|
|
439
468
|
<div
|
|
440
469
|
className={`modal-dialog modal-${size} ${animationClass} modal-dialog-scrollable modal-dialog-centered`}
|
|
441
470
|
style={{
|
|
@@ -498,7 +527,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
498
527
|
{title}
|
|
499
528
|
</h5>
|
|
500
529
|
|
|
501
|
-
{
|
|
530
|
+
{showCloseButton && (
|
|
502
531
|
<button
|
|
503
532
|
type="button"
|
|
504
533
|
className="Modal-x-button btn-close"
|
|
@@ -518,25 +547,34 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
518
547
|
)}
|
|
519
548
|
</div>
|
|
520
549
|
)}
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
550
|
+
|
|
551
|
+
<div
|
|
552
|
+
className="modal-body"
|
|
553
|
+
style={{
|
|
554
|
+
color: (
|
|
555
|
+
isDarkModeOn()
|
|
556
|
+
? 'white'
|
|
557
|
+
: undefined
|
|
558
|
+
),
|
|
559
|
+
backgroundColor: (
|
|
560
|
+
isDarkModeOn()
|
|
561
|
+
? '#444'
|
|
562
|
+
: undefined
|
|
563
|
+
),
|
|
564
|
+
}}
|
|
565
|
+
>
|
|
566
|
+
{
|
|
567
|
+
isLoading
|
|
568
|
+
? (
|
|
569
|
+
<>
|
|
570
|
+
<LoadingSpinner />
|
|
571
|
+
<span className="sr-only">Content loading</span>
|
|
572
|
+
</>
|
|
573
|
+
)
|
|
574
|
+
: children
|
|
575
|
+
}
|
|
576
|
+
</div>
|
|
577
|
+
|
|
540
578
|
{footer && (
|
|
541
579
|
<div
|
|
542
580
|
className="modal-footer pt-1 pb-1"
|
|
@@ -563,6 +601,7 @@ const Modal: React.FC<ModalProps> = (props) => {
|
|
|
563
601
|
)}
|
|
564
602
|
</div>
|
|
565
603
|
</div>
|
|
604
|
+
)}
|
|
566
605
|
</div>
|
|
567
606
|
);
|
|
568
607
|
|
|
@@ -13,7 +13,9 @@ import { faCircle } from '@fortawesome/free-regular-svg-icons';
|
|
|
13
13
|
|
|
14
14
|
// Import shared types
|
|
15
15
|
import Variant from '../types/Variant';
|
|
16
|
-
|
|
16
|
+
|
|
17
|
+
// Import shared helpers
|
|
18
|
+
import { isDarkModeOn } from '../helpers/initClient';
|
|
17
19
|
|
|
18
20
|
/*------------------------------------------------------------------------*/
|
|
19
21
|
/* -------------------------------- Types ------------------------------- */
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A very simple, lightweight date chooser
|
|
3
3
|
* @author Gabe Abrams
|
|
4
|
+
* @author Gardeniu Liu
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
// Import React
|
|
7
8
|
import React from 'react';
|
|
8
|
-
import getMonthName from '../helpers/getMonthName';
|
|
9
9
|
|
|
10
10
|
// Import helpers
|
|
11
11
|
import getOrdinal from '../helpers/getOrdinal';
|
|
12
|
+
import getMonthName from '../helpers/getMonthName';
|
|
12
13
|
import getTimeInfoInET from '../helpers/getTimeInfoInET';
|
|
13
14
|
|
|
15
|
+
// Import classes
|
|
16
|
+
import ErrorWithCode from '../errors/ErrorWithCode';
|
|
17
|
+
|
|
18
|
+
// Import types
|
|
19
|
+
import ReactKitErrorCode from '../types/ReactKitErrorCode';
|
|
20
|
+
|
|
14
21
|
/*------------------------------------------------------------------------*/
|
|
15
22
|
/* -------------------------------- Types ------------------------------- */
|
|
16
23
|
/*------------------------------------------------------------------------*/
|
|
@@ -33,12 +40,14 @@ type Props = {
|
|
|
33
40
|
* @param year new full year number
|
|
34
41
|
*/
|
|
35
42
|
onChange: (month: number, day: number, year: number) => void,
|
|
36
|
-
// Number of months to allow the user to choose from
|
|
37
|
-
//
|
|
43
|
+
// Number of months in either the past or future to allow the user to choose from
|
|
44
|
+
// If we aren't allowing the past or the future, we will throw an error
|
|
45
|
+
// (max is 12, default is 6 in the past and 6 in the future)
|
|
38
46
|
numMonthsToShow?: number,
|
|
39
|
-
// If true,
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
// If true, the user isn't allowed to select dates in the past
|
|
48
|
+
dontAllowPast?: boolean,
|
|
49
|
+
// If true, the user isn't allowed to select dates in the future
|
|
50
|
+
dontAllowFuture?: boolean,
|
|
42
51
|
};
|
|
43
52
|
|
|
44
53
|
/*------------------------------------------------------------------------*/
|
|
@@ -56,8 +65,9 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
56
65
|
ariaLabel,
|
|
57
66
|
name,
|
|
58
67
|
onChange,
|
|
59
|
-
chooseFromPast,
|
|
60
68
|
numMonthsToShow = 6,
|
|
69
|
+
dontAllowFuture,
|
|
70
|
+
dontAllowPast,
|
|
61
71
|
} = props;
|
|
62
72
|
|
|
63
73
|
/*------------------------------------------------------------------------*/
|
|
@@ -76,16 +86,41 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
76
86
|
days: number[],
|
|
77
87
|
year: number,
|
|
78
88
|
}[] = [];
|
|
89
|
+
|
|
79
90
|
let startYear = today.year;
|
|
80
91
|
let startMonth = today.month;
|
|
81
|
-
|
|
92
|
+
|
|
93
|
+
// Don't allow past or future dates
|
|
94
|
+
if (dontAllowPast && dontAllowFuture) {
|
|
95
|
+
throw new ErrorWithCode(
|
|
96
|
+
'No past or future dates allowed',
|
|
97
|
+
ReactKitErrorCode.SimpleDateChooserInvalidDateRange,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Require numMonthsToShow to be positive
|
|
102
|
+
if (numMonthsToShow <= 0) {
|
|
103
|
+
throw new ErrorWithCode(
|
|
104
|
+
'numMonthsToShow must be positive',
|
|
105
|
+
ReactKitErrorCode.SimpleDateChooserInvalidNumMonths,
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Recalculate startMonth and startYear when allowing past dates
|
|
110
|
+
if (!dontAllowPast) {
|
|
82
111
|
startMonth -= Math.max(0, numMonthsToShow - 1);
|
|
83
112
|
while (startMonth <= 0) {
|
|
84
113
|
startMonth += 12;
|
|
85
114
|
startYear -= 1;
|
|
86
115
|
}
|
|
87
116
|
}
|
|
88
|
-
|
|
117
|
+
// Calculate total number of months to show
|
|
118
|
+
let totalMonthsToShow = numMonthsToShow;
|
|
119
|
+
if (!dontAllowPast && !dontAllowFuture) {
|
|
120
|
+
totalMonthsToShow = totalMonthsToShow * 2 - 1;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
for (let i = 0; i < totalMonthsToShow; i++) {
|
|
89
124
|
// Get month and year info
|
|
90
125
|
const unmoddedMonth = (startMonth + i);
|
|
91
126
|
let month = unmoddedMonth;
|
|
@@ -105,24 +140,25 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
105
140
|
// Figure out which days are allowed
|
|
106
141
|
const days = [];
|
|
107
142
|
const numDaysInMonth = (new Date(year, month, 0)).getDate();
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
days.push(day);
|
|
143
|
+
|
|
144
|
+
// Current month
|
|
145
|
+
if (month === today.month && year === today.year) {
|
|
146
|
+
// Past selection: add all previous days of the month
|
|
147
|
+
if (!dontAllowPast) {
|
|
148
|
+
for (let day = 1; day < today.day; day++) {
|
|
149
|
+
days.push(day);
|
|
150
|
+
}
|
|
117
151
|
}
|
|
118
|
-
|
|
152
|
+
days.push(today.day); // Add current day
|
|
119
153
|
// Future selection: add all remaining days of the month
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
154
|
+
if (!dontAllowFuture) {
|
|
155
|
+
for (let day = today.day + 1; day <= numDaysInMonth; day++) {
|
|
156
|
+
days.push(day);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} else { // Past or future month
|
|
160
|
+
// Include all days in the month
|
|
161
|
+
for (let day = 1; day <= numDaysInMonth; day++) {
|
|
126
162
|
days.push(day);
|
|
127
163
|
}
|
|
128
164
|
}
|
|
@@ -158,8 +194,8 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
|
|
|
158
194
|
</option>,
|
|
159
195
|
);
|
|
160
196
|
|
|
197
|
+
// This is the currently selected month
|
|
161
198
|
if (month === choice.month) {
|
|
162
|
-
// This is the currently selected month
|
|
163
199
|
// Create day options
|
|
164
200
|
choice.days.forEach((dayChoice) => {
|
|
165
201
|
const ordinal = getOrdinal(dayChoice);
|