dce-reactkit 3.1.4 → 3.1.7
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/dist/cjs/index.js +236 -8
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/ItemPicker/NestableItemList.d.ts +17 -0
- package/dist/cjs/types/components/ItemPicker/index.d.ts +18 -0
- package/dist/cjs/types/components/ItemPicker/index.test.d.ts +1 -0
- package/dist/cjs/types/components/ItemPicker/types/PickableItem.d.ts +16 -0
- package/dist/cjs/types/helpers/parallelLimit.d.ts +12 -0
- package/dist/cjs/types/index.d.ts +3 -1
- package/dist/esm/index.js +236 -10
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/ItemPicker/NestableItemList.d.ts +17 -0
- package/dist/esm/types/components/ItemPicker/index.d.ts +18 -0
- package/dist/esm/types/components/ItemPicker/index.test.d.ts +1 -0
- package/dist/esm/types/components/ItemPicker/types/PickableItem.d.ts +16 -0
- package/dist/esm/types/helpers/parallelLimit.d.ts +12 -0
- package/dist/esm/types/index.d.ts +3 -1
- package/dist/index.d.ts +72 -27
- package/package.json +12 -2
- package/sandbox.js +20 -0
- package/src/components/ItemPicker/NestableItemList.tsx +288 -0
- package/src/components/ItemPicker/index.test.tsx +783 -0
- package/src/components/ItemPicker/index.tsx +85 -0
- package/src/components/ItemPicker/types/PickableItem.tsx +30 -0
- package/src/helpers/parallelLimit.ts +63 -0
- package/src/index.ts +4 -0
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ import React from 'react';
|
|
|
7
7
|
* @author Gabe Abrams
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
declare type Props$
|
|
10
|
+
declare type Props$d = {
|
|
11
11
|
children: React.ReactNode;
|
|
12
12
|
sendRequest: SendRequestFunction;
|
|
13
13
|
dark?: boolean;
|
|
@@ -52,7 +52,7 @@ declare const confirm: (title: string, text: string) => Promise<boolean>;
|
|
|
52
52
|
* @param [errorTitle] title of the error box
|
|
53
53
|
*/
|
|
54
54
|
declare const showFatalError: (error: any, errorTitle?: string) => undefined;
|
|
55
|
-
declare const AppWrapper: React.FC<Props$
|
|
55
|
+
declare const AppWrapper: React.FC<Props$d>;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Loading spinner/indicator
|
|
@@ -65,12 +65,12 @@ declare const LoadingSpinner: () => JSX.Element;
|
|
|
65
65
|
* @author Gabe Abrams
|
|
66
66
|
*/
|
|
67
67
|
|
|
68
|
-
declare type Props$
|
|
68
|
+
declare type Props$c = {
|
|
69
69
|
error: any;
|
|
70
70
|
title?: string;
|
|
71
71
|
onClose?: () => void;
|
|
72
72
|
};
|
|
73
|
-
declare const ErrorBox: React.FC<Props$
|
|
73
|
+
declare const ErrorBox: React.FC<Props$c>;
|
|
74
74
|
|
|
75
75
|
/**
|
|
76
76
|
* Bootstrap variants
|
|
@@ -136,7 +136,7 @@ declare enum ModalType {
|
|
|
136
136
|
* @author Gabe Abrams
|
|
137
137
|
*/
|
|
138
138
|
|
|
139
|
-
declare type Props$
|
|
139
|
+
declare type Props$b = {
|
|
140
140
|
type?: ModalType;
|
|
141
141
|
size?: ModalSize;
|
|
142
142
|
title?: React.ReactNode;
|
|
@@ -165,27 +165,27 @@ declare type Props$a = {
|
|
|
165
165
|
confirmVariant?: Variant;
|
|
166
166
|
onTopOfOtherModals?: boolean;
|
|
167
167
|
};
|
|
168
|
-
declare const Modal: React.FC<Props$
|
|
168
|
+
declare const Modal: React.FC<Props$b>;
|
|
169
169
|
|
|
170
170
|
/**
|
|
171
171
|
* A box with a tab on the top that holds buttons and other content
|
|
172
172
|
* @author Gabe Abrams
|
|
173
173
|
*/
|
|
174
174
|
|
|
175
|
-
declare type Props$
|
|
175
|
+
declare type Props$a = {
|
|
176
176
|
title: React.ReactNode;
|
|
177
177
|
children: React.ReactNode;
|
|
178
178
|
noBottomMargin?: boolean;
|
|
179
179
|
noBottomPadding?: boolean;
|
|
180
180
|
};
|
|
181
|
-
declare const TabBox: React.FC<Props$
|
|
181
|
+
declare const TabBox: React.FC<Props$a>;
|
|
182
182
|
|
|
183
183
|
/**
|
|
184
184
|
* A radio selection button
|
|
185
185
|
* @author Gabe Abrams
|
|
186
186
|
*/
|
|
187
187
|
|
|
188
|
-
declare type Props$
|
|
188
|
+
declare type Props$9 = {
|
|
189
189
|
text: string;
|
|
190
190
|
onSelected: () => void;
|
|
191
191
|
ariaLabel: string;
|
|
@@ -197,14 +197,14 @@ declare type Props$8 = {
|
|
|
197
197
|
unselectedVariant?: Variant;
|
|
198
198
|
small?: boolean;
|
|
199
199
|
};
|
|
200
|
-
declare const RadioButton: React.FC<Props$
|
|
200
|
+
declare const RadioButton: React.FC<Props$9>;
|
|
201
201
|
|
|
202
202
|
/**
|
|
203
203
|
* A checkbox button
|
|
204
204
|
* @author Gabe Abrams
|
|
205
205
|
*/
|
|
206
206
|
|
|
207
|
-
declare type Props$
|
|
207
|
+
declare type Props$8 = {
|
|
208
208
|
text: string;
|
|
209
209
|
onChanged: (checked: boolean) => void;
|
|
210
210
|
ariaLabel: string;
|
|
@@ -218,26 +218,26 @@ declare type Props$7 = {
|
|
|
218
218
|
small?: boolean;
|
|
219
219
|
dashed?: boolean;
|
|
220
220
|
};
|
|
221
|
-
declare const CheckboxButton: React.FC<Props$
|
|
221
|
+
declare const CheckboxButton: React.FC<Props$8>;
|
|
222
222
|
|
|
223
223
|
/**
|
|
224
224
|
* Input group with a title and space for buttons
|
|
225
225
|
* @author Gabe Abrams
|
|
226
226
|
*/
|
|
227
227
|
|
|
228
|
-
declare type Props$
|
|
228
|
+
declare type Props$7 = {
|
|
229
229
|
label: string;
|
|
230
230
|
minLabelWidth?: string;
|
|
231
231
|
children: React.ReactNode;
|
|
232
232
|
};
|
|
233
|
-
declare const ButtonInputGroup: React.FC<Props$
|
|
233
|
+
declare const ButtonInputGroup: React.FC<Props$7>;
|
|
234
234
|
|
|
235
235
|
/**
|
|
236
236
|
* A very simple, lightweight date chooser
|
|
237
237
|
* @author Gabe Abrams
|
|
238
238
|
*/
|
|
239
239
|
|
|
240
|
-
declare type Props$
|
|
240
|
+
declare type Props$6 = {
|
|
241
241
|
ariaLabel: string;
|
|
242
242
|
name: string;
|
|
243
243
|
month: number;
|
|
@@ -252,60 +252,60 @@ declare type Props$5 = {
|
|
|
252
252
|
onChange: (month: number, day: number, year: number) => void;
|
|
253
253
|
numMonthsToShow?: number;
|
|
254
254
|
};
|
|
255
|
-
declare const SimpleDateChooser: React.FC<Props$
|
|
255
|
+
declare const SimpleDateChooser: React.FC<Props$6>;
|
|
256
256
|
|
|
257
257
|
/**
|
|
258
258
|
* Drawer container
|
|
259
259
|
* @author Gabe Abrams
|
|
260
260
|
*/
|
|
261
261
|
|
|
262
|
-
declare type Props$
|
|
262
|
+
declare type Props$5 = {
|
|
263
263
|
children: React.ReactNode;
|
|
264
264
|
};
|
|
265
|
-
declare const Drawer: React.FC<Props$
|
|
265
|
+
declare const Drawer: React.FC<Props$5>;
|
|
266
266
|
|
|
267
267
|
/**
|
|
268
268
|
* Success checkmark that pops into view
|
|
269
269
|
* @author Gabe Abrams
|
|
270
270
|
*/
|
|
271
271
|
|
|
272
|
-
declare type Props$
|
|
272
|
+
declare type Props$4 = {
|
|
273
273
|
sizeRem?: number;
|
|
274
274
|
circleVariant?: string;
|
|
275
275
|
checkVariant?: string;
|
|
276
276
|
};
|
|
277
|
-
declare const PopSuccessMark: React.FC<Props$
|
|
277
|
+
declare const PopSuccessMark: React.FC<Props$4>;
|
|
278
278
|
|
|
279
279
|
/**
|
|
280
280
|
* Failure x mark that pops into view
|
|
281
281
|
* @author Gabe Abrams
|
|
282
282
|
*/
|
|
283
283
|
|
|
284
|
-
declare type Props$
|
|
284
|
+
declare type Props$3 = {
|
|
285
285
|
sizeRem?: number;
|
|
286
286
|
circleVariant?: string;
|
|
287
287
|
xVariant?: string;
|
|
288
288
|
};
|
|
289
|
-
declare const PopFailureMark: React.FC<Props$
|
|
289
|
+
declare const PopFailureMark: React.FC<Props$3>;
|
|
290
290
|
|
|
291
291
|
/**
|
|
292
292
|
* Failure pending that pops into view
|
|
293
293
|
* @author Gabe Abrams
|
|
294
294
|
*/
|
|
295
295
|
|
|
296
|
-
declare type Props$
|
|
296
|
+
declare type Props$2 = {
|
|
297
297
|
sizeRem?: number;
|
|
298
298
|
circleVariant?: string;
|
|
299
299
|
hourglassVariant?: string;
|
|
300
300
|
};
|
|
301
|
-
declare const PopPendingMark: React.FC<Props$
|
|
301
|
+
declare const PopPendingMark: React.FC<Props$2>;
|
|
302
302
|
|
|
303
303
|
/**
|
|
304
304
|
* Copiable text box
|
|
305
305
|
* @author Gabe Abrams
|
|
306
306
|
*/
|
|
307
307
|
|
|
308
|
-
declare type Props = {
|
|
308
|
+
declare type Props$1 = {
|
|
309
309
|
text: string;
|
|
310
310
|
maxTextWidthRem?: number;
|
|
311
311
|
label: string;
|
|
@@ -317,7 +317,40 @@ declare type Props = {
|
|
|
317
317
|
textAreaId?: string;
|
|
318
318
|
copyButtonId?: string;
|
|
319
319
|
};
|
|
320
|
-
declare const CopiableBox: React.FC<Props>;
|
|
320
|
+
declare const CopiableBox: React.FC<Props$1>;
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* An item that can be chosen (for use within ItemPicker)
|
|
324
|
+
* @author Gabe Abrams
|
|
325
|
+
*/
|
|
326
|
+
declare type PickableItem = ({
|
|
327
|
+
id: number | string;
|
|
328
|
+
name: string;
|
|
329
|
+
link?: string;
|
|
330
|
+
} & ({
|
|
331
|
+
isGroup: false;
|
|
332
|
+
checked: boolean;
|
|
333
|
+
} | {
|
|
334
|
+
isGroup: true;
|
|
335
|
+
children: PickableItem[];
|
|
336
|
+
}));
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Reusable nested item picker
|
|
340
|
+
* @author Yuen Ler Chow
|
|
341
|
+
*/
|
|
342
|
+
|
|
343
|
+
declare type Props = {
|
|
344
|
+
title: string;
|
|
345
|
+
items: PickableItem[];
|
|
346
|
+
/**
|
|
347
|
+
* Handler to call when item selection is changed
|
|
348
|
+
* @param updatedItems an updated copy of the list of items with checked
|
|
349
|
+
* values updated
|
|
350
|
+
*/
|
|
351
|
+
onChanged: (updatedItems: PickableItem[]) => void;
|
|
352
|
+
};
|
|
353
|
+
declare const ItemPicker: React.FC<Props>;
|
|
321
354
|
|
|
322
355
|
/**
|
|
323
356
|
* An error with a code
|
|
@@ -622,6 +655,18 @@ declare const stringsToHumanReadableList: (items: string[]) => string;
|
|
|
622
655
|
*/
|
|
623
656
|
declare const onlyKeepLetters: (str: string) => string;
|
|
624
657
|
|
|
658
|
+
/**
|
|
659
|
+
* Run tasks in parallel with a limit on how many tasks can execute at once.
|
|
660
|
+
* No guarantees are made about the order of task execution
|
|
661
|
+
* @author Gabe Abrams
|
|
662
|
+
* @param taskFunctions functions that start asynchronous tasks and optionally
|
|
663
|
+
* resolve with values
|
|
664
|
+
* @param [limit=no limit] maximum number of asynchronous tasks to permit to run at
|
|
665
|
+
* once
|
|
666
|
+
* @returns array of resolved values in the same order as the task functions
|
|
667
|
+
*/
|
|
668
|
+
declare const parallelLimit: (taskFunctions: (() => Promise<any>)[], limit?: number | undefined) => Promise<any[]>;
|
|
669
|
+
|
|
625
670
|
/**
|
|
626
671
|
* List of error codes built into the react kit
|
|
627
672
|
* @author Gabe Abrams
|
|
@@ -653,4 +698,4 @@ declare enum DayOfWeek {
|
|
|
653
698
|
Sunday = "u"
|
|
654
699
|
}
|
|
655
700
|
|
|
656
|
-
export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, LoadingSpinner, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, onlyKeepLetters, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
|
|
701
|
+
export { AppWrapper, ButtonInputGroup, CheckboxButton, CopiableBox, DAY_IN_MS, DayOfWeek, Drawer, ErrorBox, ErrorWithCode, HOUR_IN_MS, ItemPicker, LoadingSpinner, MINUTE_IN_MS, Modal, ModalButtonType, ModalSize, ModalType, ParamType, PopFailureMark, PopPendingMark, PopSuccessMark, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getHumanReadableDate, getOrdinal, getPartOfDay, getTimeInfoInET, handleError, handleSuccess, initServer, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dce-reactkit",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.7",
|
|
4
4
|
"description": "Shared components for Harvard DCE apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"scripts": {
|
|
9
|
-
"build": "rm -rf dist && rollup -c"
|
|
9
|
+
"build": "rm -rf dist && rollup -c",
|
|
10
|
+
"test": "jest"
|
|
11
|
+
},
|
|
12
|
+
"jest": {
|
|
13
|
+
"preset": "ts-jest",
|
|
14
|
+
"testEnvironment": "jsdom"
|
|
10
15
|
},
|
|
11
16
|
"repository": {
|
|
12
17
|
"type": "git",
|
|
@@ -30,6 +35,7 @@
|
|
|
30
35
|
"@rollup/plugin-json": "^4.1.0",
|
|
31
36
|
"@rollup/plugin-node-resolve": "^13.2.1",
|
|
32
37
|
"@rollup/plugin-typescript": "^8.3.2",
|
|
38
|
+
"@types/jest": "^28.1.7",
|
|
33
39
|
"@types/react": "^17.0.7",
|
|
34
40
|
"@typescript-eslint/eslint-plugin": "^5.21.0",
|
|
35
41
|
"@typescript-eslint/parser": "^5.21.0",
|
|
@@ -40,9 +46,13 @@
|
|
|
40
46
|
"eslint-plugin-jsx-a11y": "^6.5.1",
|
|
41
47
|
"eslint-plugin-react": "^7.29.4",
|
|
42
48
|
"eslint-plugin-react-hooks": "^4.4.0",
|
|
49
|
+
"jest": "^28.1.3",
|
|
50
|
+
"jest-environment-jsdom": "^28.1.3",
|
|
51
|
+
"raixa": "^1.0.19",
|
|
43
52
|
"rollup": "^2.70.2",
|
|
44
53
|
"rollup-plugin-dts": "^4.2.1",
|
|
45
54
|
"rollup-plugin-sourcemaps": "^0.6.3",
|
|
55
|
+
"ts-jest": "^28.0.8",
|
|
46
56
|
"typescript": "^4.6.3"
|
|
47
57
|
}
|
|
48
58
|
}
|
package/sandbox.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const { parallelLimit, waitMs } = require('.');
|
|
2
|
+
|
|
3
|
+
(async () => {
|
|
4
|
+
|
|
5
|
+
const tasks = [2, 4, 3, 5, 6, 20, 1, 1, 1, 1, 5, 4, 6].map((sec, i) => {
|
|
6
|
+
return async () => {
|
|
7
|
+
let prefix = '';
|
|
8
|
+
for (let j = 0; j < i; j++) {
|
|
9
|
+
prefix += '\t';
|
|
10
|
+
}
|
|
11
|
+
for (let j = 0; j < sec * 5; j++) {
|
|
12
|
+
await waitMs(200);
|
|
13
|
+
console.log(`${prefix}${j}/${sec * 5}`);
|
|
14
|
+
}
|
|
15
|
+
return i;
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const results = await parallelLimit(tasks, 1000);
|
|
20
|
+
})();
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable nested item picker
|
|
3
|
+
* @author Yuen Ler Chow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React, { useReducer } from 'react';
|
|
8
|
+
|
|
9
|
+
// Import FontAwesome
|
|
10
|
+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
11
|
+
import {
|
|
12
|
+
faChevronRight,
|
|
13
|
+
faChevronDown,
|
|
14
|
+
} from '@fortawesome/free-solid-svg-icons';
|
|
15
|
+
|
|
16
|
+
// Import shared components
|
|
17
|
+
import CheckboxButton from '../CheckboxButton';
|
|
18
|
+
import Variant from '../../types/Variant';
|
|
19
|
+
|
|
20
|
+
// Import types
|
|
21
|
+
import PickableItem from './types/PickableItem';
|
|
22
|
+
|
|
23
|
+
/*------------------------------------------------------------------------*/
|
|
24
|
+
/* Types */
|
|
25
|
+
/*------------------------------------------------------------------------*/
|
|
26
|
+
|
|
27
|
+
// Props definition
|
|
28
|
+
type Props = {
|
|
29
|
+
// List of items to show
|
|
30
|
+
items: PickableItem[],
|
|
31
|
+
/**
|
|
32
|
+
* Handler to call when item selection is changed
|
|
33
|
+
* @param updatedItems an updated copy of the list of items with checked
|
|
34
|
+
* values updated
|
|
35
|
+
*/
|
|
36
|
+
onChanged: (updatedItems: PickableItem[]) => void,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/*------------------------------------------------------------------------*/
|
|
40
|
+
/* State */
|
|
41
|
+
/*------------------------------------------------------------------------*/
|
|
42
|
+
|
|
43
|
+
/* -------- State Definition -------- */
|
|
44
|
+
|
|
45
|
+
type State = {
|
|
46
|
+
// True if children are being shown
|
|
47
|
+
isShowingItems: boolean,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/* ------------- Actions ------------ */
|
|
51
|
+
|
|
52
|
+
// Types of actions
|
|
53
|
+
enum ActionType {
|
|
54
|
+
// Toggle whether the children are being shown
|
|
55
|
+
ToggleItems = 'toggle-items',
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Action definitions
|
|
59
|
+
type Action = (
|
|
60
|
+
| {
|
|
61
|
+
// Action type
|
|
62
|
+
type: ActionType.ToggleItems,
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reducer that executes actions
|
|
68
|
+
* @author Yuen Ler Chow
|
|
69
|
+
* @param state current state
|
|
70
|
+
* @param action action to execute
|
|
71
|
+
*/
|
|
72
|
+
const reducer = (state: State, action: Action): State => {
|
|
73
|
+
switch (action.type) {
|
|
74
|
+
case ActionType.ToggleItems: {
|
|
75
|
+
return { isShowingItems: !state.isShowingItems };
|
|
76
|
+
}
|
|
77
|
+
default: {
|
|
78
|
+
return state;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/*------------------------------------------------------------------------*/
|
|
84
|
+
/* Component */
|
|
85
|
+
/*------------------------------------------------------------------------*/
|
|
86
|
+
|
|
87
|
+
const NestableItemList: React.FC<Props> = (props) => {
|
|
88
|
+
/*------------------------------------------------------------------------*/
|
|
89
|
+
/* Setup */
|
|
90
|
+
/*------------------------------------------------------------------------*/
|
|
91
|
+
|
|
92
|
+
/* -------------- Props ------------- */
|
|
93
|
+
|
|
94
|
+
// Destructure all props
|
|
95
|
+
const {
|
|
96
|
+
items,
|
|
97
|
+
onChanged,
|
|
98
|
+
} = props;
|
|
99
|
+
|
|
100
|
+
/* -------------- State ------------- */
|
|
101
|
+
|
|
102
|
+
// Initial state
|
|
103
|
+
const initialState: State = {
|
|
104
|
+
isShowingItems: true,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// Initialize state
|
|
108
|
+
const [state, dispatch] = useReducer(reducer, initialState);
|
|
109
|
+
|
|
110
|
+
// Destructure common state
|
|
111
|
+
const {
|
|
112
|
+
isShowingItems,
|
|
113
|
+
} = state;
|
|
114
|
+
|
|
115
|
+
/*------------------------------------------------------------------------*/
|
|
116
|
+
/* Component Functions */
|
|
117
|
+
/*------------------------------------------------------------------------*/
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Checks if all items in a list are checked
|
|
121
|
+
* @author Yuen Ler Chow
|
|
122
|
+
* @param pickableItems a list of items
|
|
123
|
+
* @returns true if all items are checked. If any item is a group,
|
|
124
|
+
* we recursively check its children
|
|
125
|
+
*/
|
|
126
|
+
const allChecked = (pickableItems: PickableItem[]): boolean => {
|
|
127
|
+
return pickableItems.every((item: PickableItem) => {
|
|
128
|
+
if (item.isGroup) {
|
|
129
|
+
return allChecked(item.children);
|
|
130
|
+
}
|
|
131
|
+
return item.checked;
|
|
132
|
+
});
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Checks if none of the items in a list are checked
|
|
137
|
+
* @author Yuen Ler Chow
|
|
138
|
+
* @param pickableItems a list of items
|
|
139
|
+
* @returns true if all items are unchecked. If any item is a group, we
|
|
140
|
+
* recursively check its children
|
|
141
|
+
*/
|
|
142
|
+
const noneChecked = (pickableItems: PickableItem[]): boolean => {
|
|
143
|
+
return pickableItems.every((item: PickableItem) => {
|
|
144
|
+
if (item.isGroup) {
|
|
145
|
+
return noneChecked(item.children);
|
|
146
|
+
}
|
|
147
|
+
return !item.checked;
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Change whether specific items are checked
|
|
153
|
+
* @author Yuen Ler Chow
|
|
154
|
+
* @param id the id of the item we want to check or uncheck OR true if
|
|
155
|
+
* we are checking/unchecking all items, independent of their id
|
|
156
|
+
* @param checked if true, the item will be checked.
|
|
157
|
+
* @param pickableItems the list of items we iterate through to find the item
|
|
158
|
+
* we want to check/uncheck
|
|
159
|
+
* @returns a list of items with the item now checked/unchecked.
|
|
160
|
+
* If it is a group, its children will also become checked/unchecked
|
|
161
|
+
*/
|
|
162
|
+
const changeChecked = (id: string | number | true, checked: boolean, pickableItems: PickableItem[]): PickableItem[] => {
|
|
163
|
+
const updatedItems: PickableItem[] = pickableItems.map((item: PickableItem) => {
|
|
164
|
+
if (item.id === id || id === true) {
|
|
165
|
+
if (item.isGroup) {
|
|
166
|
+
return {
|
|
167
|
+
...item,
|
|
168
|
+
children: changeChecked(true, checked, item.children),
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
...item,
|
|
173
|
+
checked,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
if (item.isGroup) {
|
|
177
|
+
return {
|
|
178
|
+
...item,
|
|
179
|
+
children: changeChecked(id, checked, item.children),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
return item;
|
|
183
|
+
});
|
|
184
|
+
return updatedItems;
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Within a tree of items, swap in the list of updated items into the spot
|
|
189
|
+
* indicated by the id
|
|
190
|
+
* @author Yuen Ler Chow
|
|
191
|
+
* @param id the id of the item group we want to change
|
|
192
|
+
* @param updatedItems the list of items we want to change the items in the group to
|
|
193
|
+
* @returns a list of all items containing the updated items
|
|
194
|
+
*/
|
|
195
|
+
const changeItems = (id: string | number, updatedItems: PickableItem[]): PickableItem[] => {
|
|
196
|
+
return items.map((item: PickableItem) => {
|
|
197
|
+
if (item.id === id) {
|
|
198
|
+
return {
|
|
199
|
+
...item,
|
|
200
|
+
children: updatedItems,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
return item;
|
|
204
|
+
});
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/*------------------------------------------------------------------------*/
|
|
208
|
+
/* Render */
|
|
209
|
+
/*------------------------------------------------------------------------*/
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<div>
|
|
213
|
+
{
|
|
214
|
+
items.map((item) => {
|
|
215
|
+
return (
|
|
216
|
+
<div key={item.id}>
|
|
217
|
+
{/* Dropdown Button */}
|
|
218
|
+
<span
|
|
219
|
+
className="NestableItemList-dropdown-button-container d-inline-block"
|
|
220
|
+
style={{
|
|
221
|
+
minWidth: '2rem',
|
|
222
|
+
}}
|
|
223
|
+
>
|
|
224
|
+
{item.isGroup && (
|
|
225
|
+
<button
|
|
226
|
+
className={`NestableItemList-dropdown-button NestableItemList-dropdown-button-${item.id}`}
|
|
227
|
+
style={{
|
|
228
|
+
border: 0,
|
|
229
|
+
backgroundColor: 'transparent',
|
|
230
|
+
}}
|
|
231
|
+
type="button"
|
|
232
|
+
onClick={() => {
|
|
233
|
+
dispatch({
|
|
234
|
+
type: ActionType.ToggleItems,
|
|
235
|
+
});
|
|
236
|
+
}}
|
|
237
|
+
aria-label={`${isShowingItems ? 'Hide' : 'Show'} items in ${item.name}`}
|
|
238
|
+
>
|
|
239
|
+
<FontAwesomeIcon
|
|
240
|
+
icon={isShowingItems ? faChevronDown : faChevronRight}
|
|
241
|
+
/>
|
|
242
|
+
</button>
|
|
243
|
+
)}
|
|
244
|
+
</span>
|
|
245
|
+
|
|
246
|
+
{/* Checkbox and Text */}
|
|
247
|
+
<CheckboxButton
|
|
248
|
+
className={`NestableItemList-CheckboxButton-${item.id}`}
|
|
249
|
+
text={item.name}
|
|
250
|
+
checked={item.isGroup ? allChecked(item.children) : item.checked}
|
|
251
|
+
dashed={item.isGroup ? !noneChecked(item.children) : false}
|
|
252
|
+
onChanged={(checked) => {
|
|
253
|
+
onChanged(changeChecked(item.id, checked, items));
|
|
254
|
+
}}
|
|
255
|
+
ariaLabel={`Select ${item.name}`}
|
|
256
|
+
checkedVariant={Variant.Light}
|
|
257
|
+
/>
|
|
258
|
+
|
|
259
|
+
{/* Children */}
|
|
260
|
+
{item.isGroup && isShowingItems && (
|
|
261
|
+
<div
|
|
262
|
+
className="NestableItemList-children-container"
|
|
263
|
+
style={{
|
|
264
|
+
paddingLeft: '2.2rem',
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
<NestableItemList
|
|
268
|
+
items={item.children}
|
|
269
|
+
onChanged={(updatedItems) => {
|
|
270
|
+
onChanged(changeItems(item.id, updatedItems));
|
|
271
|
+
}}
|
|
272
|
+
/>
|
|
273
|
+
</div>
|
|
274
|
+
)}
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
</div>
|
|
280
|
+
);
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/*------------------------------------------------------------------------*/
|
|
284
|
+
/* Wrap Up */
|
|
285
|
+
/*------------------------------------------------------------------------*/
|
|
286
|
+
|
|
287
|
+
// Export component
|
|
288
|
+
export default NestableItemList;
|