dce-reactkit 3.1.2 → 3.1.5
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 +199 -10
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/components/CheckboxButton.d.ts +1 -0
- 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/types/PickableItem.d.ts +16 -0
- package/dist/cjs/types/helpers/onlyKeepLetters.d.ts +8 -0
- package/dist/cjs/types/index.d.ts +3 -1
- package/dist/esm/index.js +199 -12
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/components/CheckboxButton.d.ts +1 -0
- 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/types/PickableItem.d.ts +16 -0
- package/dist/esm/types/helpers/onlyKeepLetters.d.ts +8 -0
- package/dist/esm/types/index.d.ts +3 -1
- package/dist/index.d.ts +69 -27
- package/package.json +1 -1
- package/src/components/CheckboxButton.tsx +4 -1
- package/src/components/ItemPicker/NestableItemList.tsx +288 -0
- package/src/components/ItemPicker/index.tsx +85 -0
- package/src/components/ItemPicker/types/PickableItem.tsx +30 -0
- package/src/helpers/onlyKeepLetters.ts +11 -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,46 +197,47 @@ 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;
|
|
211
211
|
title?: string;
|
|
212
212
|
checked?: boolean;
|
|
213
213
|
id?: string;
|
|
214
|
+
className?: string;
|
|
214
215
|
noMarginOnRight?: boolean;
|
|
215
216
|
checkedVariant?: Variant;
|
|
216
217
|
uncheckedVariant?: Variant;
|
|
217
218
|
small?: boolean;
|
|
218
219
|
dashed?: boolean;
|
|
219
220
|
};
|
|
220
|
-
declare const CheckboxButton: React.FC<Props$
|
|
221
|
+
declare const CheckboxButton: React.FC<Props$8>;
|
|
221
222
|
|
|
222
223
|
/**
|
|
223
224
|
* Input group with a title and space for buttons
|
|
224
225
|
* @author Gabe Abrams
|
|
225
226
|
*/
|
|
226
227
|
|
|
227
|
-
declare type Props$
|
|
228
|
+
declare type Props$7 = {
|
|
228
229
|
label: string;
|
|
229
230
|
minLabelWidth?: string;
|
|
230
231
|
children: React.ReactNode;
|
|
231
232
|
};
|
|
232
|
-
declare const ButtonInputGroup: React.FC<Props$
|
|
233
|
+
declare const ButtonInputGroup: React.FC<Props$7>;
|
|
233
234
|
|
|
234
235
|
/**
|
|
235
236
|
* A very simple, lightweight date chooser
|
|
236
237
|
* @author Gabe Abrams
|
|
237
238
|
*/
|
|
238
239
|
|
|
239
|
-
declare type Props$
|
|
240
|
+
declare type Props$6 = {
|
|
240
241
|
ariaLabel: string;
|
|
241
242
|
name: string;
|
|
242
243
|
month: number;
|
|
@@ -251,60 +252,60 @@ declare type Props$5 = {
|
|
|
251
252
|
onChange: (month: number, day: number, year: number) => void;
|
|
252
253
|
numMonthsToShow?: number;
|
|
253
254
|
};
|
|
254
|
-
declare const SimpleDateChooser: React.FC<Props$
|
|
255
|
+
declare const SimpleDateChooser: React.FC<Props$6>;
|
|
255
256
|
|
|
256
257
|
/**
|
|
257
258
|
* Drawer container
|
|
258
259
|
* @author Gabe Abrams
|
|
259
260
|
*/
|
|
260
261
|
|
|
261
|
-
declare type Props$
|
|
262
|
+
declare type Props$5 = {
|
|
262
263
|
children: React.ReactNode;
|
|
263
264
|
};
|
|
264
|
-
declare const Drawer: React.FC<Props$
|
|
265
|
+
declare const Drawer: React.FC<Props$5>;
|
|
265
266
|
|
|
266
267
|
/**
|
|
267
268
|
* Success checkmark that pops into view
|
|
268
269
|
* @author Gabe Abrams
|
|
269
270
|
*/
|
|
270
271
|
|
|
271
|
-
declare type Props$
|
|
272
|
+
declare type Props$4 = {
|
|
272
273
|
sizeRem?: number;
|
|
273
274
|
circleVariant?: string;
|
|
274
275
|
checkVariant?: string;
|
|
275
276
|
};
|
|
276
|
-
declare const PopSuccessMark: React.FC<Props$
|
|
277
|
+
declare const PopSuccessMark: React.FC<Props$4>;
|
|
277
278
|
|
|
278
279
|
/**
|
|
279
280
|
* Failure x mark that pops into view
|
|
280
281
|
* @author Gabe Abrams
|
|
281
282
|
*/
|
|
282
283
|
|
|
283
|
-
declare type Props$
|
|
284
|
+
declare type Props$3 = {
|
|
284
285
|
sizeRem?: number;
|
|
285
286
|
circleVariant?: string;
|
|
286
287
|
xVariant?: string;
|
|
287
288
|
};
|
|
288
|
-
declare const PopFailureMark: React.FC<Props$
|
|
289
|
+
declare const PopFailureMark: React.FC<Props$3>;
|
|
289
290
|
|
|
290
291
|
/**
|
|
291
292
|
* Failure pending that pops into view
|
|
292
293
|
* @author Gabe Abrams
|
|
293
294
|
*/
|
|
294
295
|
|
|
295
|
-
declare type Props$
|
|
296
|
+
declare type Props$2 = {
|
|
296
297
|
sizeRem?: number;
|
|
297
298
|
circleVariant?: string;
|
|
298
299
|
hourglassVariant?: string;
|
|
299
300
|
};
|
|
300
|
-
declare const PopPendingMark: React.FC<Props$
|
|
301
|
+
declare const PopPendingMark: React.FC<Props$2>;
|
|
301
302
|
|
|
302
303
|
/**
|
|
303
304
|
* Copiable text box
|
|
304
305
|
* @author Gabe Abrams
|
|
305
306
|
*/
|
|
306
307
|
|
|
307
|
-
declare type Props = {
|
|
308
|
+
declare type Props$1 = {
|
|
308
309
|
text: string;
|
|
309
310
|
maxTextWidthRem?: number;
|
|
310
311
|
label: string;
|
|
@@ -316,7 +317,40 @@ declare type Props = {
|
|
|
316
317
|
textAreaId?: string;
|
|
317
318
|
copyButtonId?: string;
|
|
318
319
|
};
|
|
319
|
-
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>;
|
|
320
354
|
|
|
321
355
|
/**
|
|
322
356
|
* An error with a code
|
|
@@ -613,6 +647,14 @@ declare const getPartOfDay: () => string;
|
|
|
613
647
|
*/
|
|
614
648
|
declare const stringsToHumanReadableList: (items: string[]) => string;
|
|
615
649
|
|
|
650
|
+
/**
|
|
651
|
+
* Given a string, only keep the letters inside it
|
|
652
|
+
* @author Gabe Abrams
|
|
653
|
+
* @param str the string to parse
|
|
654
|
+
* @returns only the letters inside of the string
|
|
655
|
+
*/
|
|
656
|
+
declare const onlyKeepLetters: (str: string) => string;
|
|
657
|
+
|
|
616
658
|
/**
|
|
617
659
|
* List of error codes built into the react kit
|
|
618
660
|
* @author Gabe Abrams
|
|
@@ -644,4 +686,4 @@ declare enum DayOfWeek {
|
|
|
644
686
|
Sunday = "u"
|
|
645
687
|
}
|
|
646
688
|
|
|
647
|
-
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, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
|
|
689
|
+
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, roundToNumDecimals, showFatalError, startMinWait, stringsToHumanReadableList, stubServerEndpoint, sum, visitServerEndpoint, waitMs };
|
package/package.json
CHANGED
|
@@ -31,6 +31,8 @@ type Props = {
|
|
|
31
31
|
checked?: boolean,
|
|
32
32
|
// The id of the button
|
|
33
33
|
id?: string,
|
|
34
|
+
// Custom class name of the button
|
|
35
|
+
className?: string,
|
|
34
36
|
// If true, no margin on right
|
|
35
37
|
noMarginOnRight?: boolean,
|
|
36
38
|
// Variant for when checkbox is checked
|
|
@@ -61,6 +63,7 @@ const CheckboxButton: React.FC<Props> = (props) => {
|
|
|
61
63
|
title,
|
|
62
64
|
checked,
|
|
63
65
|
id,
|
|
66
|
+
className,
|
|
64
67
|
noMarginOnRight,
|
|
65
68
|
checkedVariant = Variant.Secondary,
|
|
66
69
|
uncheckedVariant = Variant.Light,
|
|
@@ -94,7 +97,7 @@ const CheckboxButton: React.FC<Props> = (props) => {
|
|
|
94
97
|
type="button"
|
|
95
98
|
id={id}
|
|
96
99
|
title={title}
|
|
97
|
-
className={`CheckboxButton-status-${checked ? 'checked' : 'unchecked'} ${dashed ? 'CheckboxButton-dashed ' : ''}btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`}
|
|
100
|
+
className={`CheckboxButton-status-${checked ? 'checked' : 'unchecked'} ${dashed ? 'CheckboxButton-dashed ' : ''}btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'} ${className ?? ''}`}
|
|
98
101
|
aria-label={`${ariaLabel}${checked ? ': currently checked' : ''}`}
|
|
99
102
|
onClick={() => {
|
|
100
103
|
onChanged(!checked);
|
|
@@ -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;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reusable nested item picker
|
|
3
|
+
* @author Yuen Ler Chow
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Import React
|
|
7
|
+
import React from 'react';
|
|
8
|
+
|
|
9
|
+
// Import shared components
|
|
10
|
+
import TabBox from '../TabBox';
|
|
11
|
+
|
|
12
|
+
// Import types
|
|
13
|
+
import PickableItem from './types/PickableItem';
|
|
14
|
+
|
|
15
|
+
// Import sub-components
|
|
16
|
+
import NestableItemList from './NestableItemList';
|
|
17
|
+
|
|
18
|
+
/*------------------------------------------------------------------------*/
|
|
19
|
+
/* Types */
|
|
20
|
+
/*------------------------------------------------------------------------*/
|
|
21
|
+
|
|
22
|
+
// Props definition
|
|
23
|
+
type Props = {
|
|
24
|
+
// Title of the picker
|
|
25
|
+
title: string,
|
|
26
|
+
// List of items to show
|
|
27
|
+
items: PickableItem[],
|
|
28
|
+
/**
|
|
29
|
+
* Handler to call when item selection is changed
|
|
30
|
+
* @param updatedItems an updated copy of the list of items with checked
|
|
31
|
+
* values updated
|
|
32
|
+
*/
|
|
33
|
+
onChanged: (updatedItems: PickableItem[]) => void,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/*------------------------------------------------------------------------*/
|
|
37
|
+
/* Component */
|
|
38
|
+
/*------------------------------------------------------------------------*/
|
|
39
|
+
|
|
40
|
+
const ItemPicker: React.FC<Props> = (props) => {
|
|
41
|
+
/*------------------------------------------------------------------------*/
|
|
42
|
+
/* Setup */
|
|
43
|
+
/*------------------------------------------------------------------------*/
|
|
44
|
+
|
|
45
|
+
/* -------------- Props ------------- */
|
|
46
|
+
|
|
47
|
+
// Destructure all props
|
|
48
|
+
const {
|
|
49
|
+
title,
|
|
50
|
+
items,
|
|
51
|
+
onChanged,
|
|
52
|
+
} = props;
|
|
53
|
+
|
|
54
|
+
/*------------------------------------------------------------------------*/
|
|
55
|
+
/* Component Functions */
|
|
56
|
+
/*------------------------------------------------------------------------*/
|
|
57
|
+
|
|
58
|
+
/*------------------------------------------------------------------------*/
|
|
59
|
+
/* Render */
|
|
60
|
+
/*------------------------------------------------------------------------*/
|
|
61
|
+
|
|
62
|
+
/*----------------------------------------*/
|
|
63
|
+
/* Main UI */
|
|
64
|
+
/*----------------------------------------*/
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<TabBox
|
|
68
|
+
title={title}
|
|
69
|
+
>
|
|
70
|
+
<div style={{ overflowX: 'scroll' }}>
|
|
71
|
+
<NestableItemList
|
|
72
|
+
items={items}
|
|
73
|
+
onChanged={onChanged}
|
|
74
|
+
/>
|
|
75
|
+
</div>
|
|
76
|
+
</TabBox>
|
|
77
|
+
);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/*------------------------------------------------------------------------*/
|
|
81
|
+
/* Wrap Up */
|
|
82
|
+
/*------------------------------------------------------------------------*/
|
|
83
|
+
|
|
84
|
+
// Export component
|
|
85
|
+
export default ItemPicker;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An item that can be chosen (for use within ItemPicker)
|
|
3
|
+
* @author Gabe Abrams
|
|
4
|
+
*/
|
|
5
|
+
type PickableItem = (
|
|
6
|
+
{
|
|
7
|
+
// Unique id of the item
|
|
8
|
+
id: number | string,
|
|
9
|
+
// Name of the item (human readable)
|
|
10
|
+
name: string,
|
|
11
|
+
// Link to view the item in the browser
|
|
12
|
+
link?: string,
|
|
13
|
+
}
|
|
14
|
+
& (
|
|
15
|
+
| {
|
|
16
|
+
// True if this is a group of items
|
|
17
|
+
isGroup: false,
|
|
18
|
+
// True if item is checked
|
|
19
|
+
checked: boolean,
|
|
20
|
+
}
|
|
21
|
+
| {
|
|
22
|
+
// True if this is a group of items
|
|
23
|
+
isGroup: true,
|
|
24
|
+
// List of sub-items
|
|
25
|
+
children: PickableItem[],
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
export default PickableItem;
|