dce-reactkit 3.9.0-beta-loading-modal.2 → 3.9.0-beta-loading-modal.3

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.
Files changed (62) hide show
  1. package/dist/cjs/index.js +783 -201
  2. package/dist/cjs/index.js.map +1 -1
  3. package/dist/cjs/types/client/initClient.d.ts +16 -2
  4. package/dist/cjs/types/components/AppWrapper.d.ts +32 -0
  5. package/dist/cjs/types/components/CheckboxButton.d.ts +1 -0
  6. package/dist/cjs/types/components/Dropdown.d.ts +32 -0
  7. package/dist/cjs/types/components/RadioButton.d.ts +2 -0
  8. package/dist/cjs/types/helpers/genRouteHandler.d.ts +8 -0
  9. package/dist/cjs/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
  10. package/dist/cjs/types/helpers/validators/validURL.d.ts +8 -0
  11. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/index.d.ts +24 -0
  12. package/dist/cjs/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
  13. package/dist/cjs/types/index.d.ts +5 -2
  14. package/dist/cjs/types/types/DropdownItemType.d.ts +6 -0
  15. package/dist/cjs/types/types/ModalType.d.ts +1 -0
  16. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +6 -1
  17. package/dist/esm/index.js +779 -202
  18. package/dist/esm/index.js.map +1 -1
  19. package/dist/esm/types/client/initClient.d.ts +16 -2
  20. package/dist/esm/types/components/AppWrapper.d.ts +32 -0
  21. package/dist/esm/types/components/CheckboxButton.d.ts +1 -0
  22. package/dist/esm/types/components/Dropdown.d.ts +32 -0
  23. package/dist/esm/types/components/RadioButton.d.ts +2 -0
  24. package/dist/esm/types/helpers/genRouteHandler.d.ts +8 -0
  25. package/dist/esm/types/helpers/validators/ChicagoTitleCase.d.ts +9 -0
  26. package/dist/esm/types/helpers/validators/validURL.d.ts +8 -0
  27. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/index.d.ts +24 -0
  28. package/dist/esm/types/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.d.ts +33 -0
  29. package/dist/esm/types/index.d.ts +5 -2
  30. package/dist/esm/types/types/DropdownItemType.d.ts +6 -0
  31. package/dist/esm/types/types/ModalType.d.ts +1 -0
  32. package/dist/esm/types/types/ReactKitErrorCode.d.ts +6 -1
  33. package/dist/index.d.ts +163 -46
  34. package/package.json +2 -1
  35. package/src/client/initClient.tsx +49 -11
  36. package/src/components/AppWrapper.tsx +258 -2
  37. package/src/components/CheckboxButton.tsx +24 -6
  38. package/src/components/Dropdown.tsx +317 -0
  39. package/src/components/IntelliTable.tsx +1 -1
  40. package/src/components/ItemPicker/NestableItemList.tsx +1 -0
  41. package/src/components/LogReviewer.tsx +17 -4
  42. package/src/components/Modal/index.tsx +4 -1
  43. package/src/components/MultiSwitch.tsx +1 -1
  44. package/src/components/RadioButton.tsx +33 -6
  45. package/src/components/SimpleDateChooser.tsx +8 -2
  46. package/src/helpers/genRouteHandler.ts +116 -17
  47. package/src/helpers/logClientEvent.tsx +8 -0
  48. package/src/helpers/validators/ChicagoTitleCase.test.ts +85 -0
  49. package/src/helpers/validators/ChicagoTitleCase.ts +32 -0
  50. package/src/helpers/validators/findURL.test.ts +83 -0
  51. package/src/helpers/validators/findURL.ts +43 -0
  52. package/src/helpers/validators/validURL.test.ts +90 -0
  53. package/src/helpers/validators/validURL.ts +18 -0
  54. package/src/helpers/visitEndpointOnAnotherServer/index.ts +93 -0
  55. package/src/helpers/visitEndpointOnAnotherServer/sendServerToServerRequest.ts +164 -0
  56. package/src/helpers/visitServerEndpoint.tsx +1 -0
  57. package/src/index.ts +8 -0
  58. package/src/server/initLogCollection.ts +5 -0
  59. package/src/server/initServer.ts +11 -5
  60. package/src/types/DropdownItemType.ts +11 -0
  61. package/src/types/ModalType.tsx +1 -0
  62. package/src/types/ReactKitErrorCode.tsx +8 -1
@@ -240,6 +240,147 @@ export const confirm = async (
240
240
  });
241
241
  };
242
242
 
243
+ /*----------------------------------------*/
244
+ /* --------------- Prompt -------------- */
245
+ /*----------------------------------------*/
246
+
247
+ // Stored copies of setters
248
+ let setPromptInfo: (
249
+ info: (
250
+ | undefined
251
+ | {
252
+ title: string,
253
+ currentInputFieldText: string,
254
+ opts: {
255
+ textAboveInputField?: string,
256
+ placeholder?: string,
257
+ defaultText?: string,
258
+ confirmButtonText?: string,
259
+ confirmButtonVariant?: Variant,
260
+ cancelButtonText?: string,
261
+ cancelButtonVariant?: Variant,
262
+ minNumChars?: number,
263
+ findValidationError?: (text: string) => string | undefined,
264
+ ariaLabel?: string,
265
+ },
266
+ }
267
+ ),
268
+ ) => void;
269
+
270
+ // Function to call when prompt is closed
271
+ let onPromptClosed: (result: string | null) => void;
272
+
273
+ /**
274
+ * Show a prompt modal asking the user for input
275
+ * @author Yuen Ler Chow
276
+ * @param title the title text to display at the top of the prompt
277
+ * @param [opts={}] additional options for the prompt dialog
278
+ * @param [opts.textAboveInputField] the text to display in the prompt
279
+ * @param [opts.defaultText] the default text for the input field
280
+ * @param [opts.placeholder] the placeholder text for the input field
281
+ * @param [opts.confirmButtonText=Okay] the text of the confirm button
282
+ * @param [opts.confirmButtonVariant=Variant.Dark] the variant of the confirm button
283
+ * @param [opts.cancelButtonText=Cancel] the text of the cancel button
284
+ * @param [opts.cancelButtonVariant=Variant.Secondary] the variant of the cancel button
285
+ * @param [opts.minNumChars] the minimum number of characters required for
286
+ * the input to be valid
287
+ * @param [opts.findValidationError] a function that takes the input text and
288
+ * returns an error message if the input is invalid, returns undefined if the
289
+ * input is valid
290
+ * @param [opts.ariaLabel] the aria label for the input field
291
+ * @returns Promise that resolves with the input string or null if canceled
292
+ */
293
+ export const prompt = async (
294
+ title: string,
295
+ opts?: {
296
+ textAboveInputField?: string,
297
+ defaultText?: string,
298
+ placeholder?: string,
299
+ confirmButtonText?: string,
300
+ confirmButtonVariant?: Variant,
301
+ cancelButtonText?: string,
302
+ cancelButtonVariant?: Variant,
303
+ minNumChars?: number,
304
+ findValidationError?: (text: string) => string | undefined,
305
+ ariaLabel?: string,
306
+ },
307
+ ): Promise<string | null> => {
308
+ // Wait for helper to exist
309
+ await waitForHelper(() => {
310
+ return !!setPromptInfo;
311
+ });
312
+
313
+ // Fallback if prompt is not available
314
+ if (!setPromptInfo) {
315
+ const resultPassesValidation = false;
316
+ while (!resultPassesValidation) {
317
+ // eslint-disable-next-line no-alert
318
+ const result = window.prompt(
319
+ `${title}\n\n${opts?.textAboveInputField ?? ''}`,
320
+ opts?.defaultText ?? '',
321
+ );
322
+
323
+ // Exit loop if user cancels
324
+ if (result === null) {
325
+ return null;
326
+ }
327
+
328
+ // Validate min num chars
329
+ const minNumCharsValidationError = (
330
+ (opts?.minNumChars && result.length < opts.minNumChars)
331
+ ? `Please enter at least ${opts.minNumChars} characters.`
332
+ : undefined
333
+ );
334
+
335
+ // Run custom validation
336
+ const customValidationError = (
337
+ opts?.findValidationError
338
+ && opts.findValidationError(result)
339
+ );
340
+
341
+ // Show validation issue
342
+ if (minNumCharsValidationError || customValidationError) {
343
+ // Create error message
344
+ const errorMessage = (
345
+ [
346
+ minNumCharsValidationError,
347
+ customValidationError,
348
+ ]
349
+ // Filter out undefined messages
350
+ .filter((msg) => {
351
+ return !!msg;
352
+ })
353
+ // Join messages with newlines
354
+ .join('\n')
355
+ );
356
+
357
+ // Show alert
358
+ alert(
359
+ 'Invalid Input',
360
+ errorMessage,
361
+ );
362
+ } else {
363
+ return result;
364
+ }
365
+ }
366
+ }
367
+
368
+ // Return promise that resolves with result of prompt
369
+ return new Promise((resolve) => {
370
+ // Setup handler
371
+ onPromptClosed = (result: string | null) => {
372
+ resolve(result);
373
+ };
374
+
375
+ // Show the prompt
376
+ setPromptInfo({
377
+ title,
378
+ currentInputFieldText: (opts?.defaultText ?? ''),
379
+ opts: (opts ?? {}),
380
+ });
381
+ });
382
+ };
383
+
243
384
  /*----------------------------------------*/
244
385
  /* ------------- Fatal Error ------------ */
245
386
  /*----------------------------------------*/
@@ -465,6 +606,28 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
465
606
  >(undefined);
466
607
  setConfirmInfo = setConfirmInfoInner;
467
608
 
609
+ // Prompt
610
+ const [promptInfo, setPromptInfoInner] = useState<
611
+ | undefined
612
+ | {
613
+ title: string,
614
+ currentInputFieldText: string,
615
+ opts: {
616
+ textAboveInputField?: string,
617
+ placeholder?: string,
618
+ defaultText?: string,
619
+ confirmButtonText?: string,
620
+ confirmButtonVariant?: Variant,
621
+ cancelButtonText?: string,
622
+ cancelButtonVariant?: Variant,
623
+ minNumChars?: number,
624
+ findValidationError?: (text: string) => string | undefined,
625
+ ariaLabel?: string,
626
+ }
627
+ }
628
+ >(undefined);
629
+ setPromptInfo = setPromptInfoInner;
630
+
468
631
  // Session expired
469
632
  const [
470
633
  sessionHasExpired,
@@ -531,6 +694,99 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
531
694
  );
532
695
  }
533
696
 
697
+ /* ------------- Prompt ------------ */
698
+
699
+ if (promptInfo) {
700
+ // Run min char validation
701
+ const minNumCharsValidationError = (
702
+ (
703
+ promptInfo.opts.minNumChars
704
+ && promptInfo.currentInputFieldText.length < promptInfo.opts.minNumChars
705
+ )
706
+ ? `Please enter at least ${promptInfo.opts.minNumChars} characters.`
707
+ : undefined
708
+ );
709
+
710
+ // Run custom validation
711
+ const customValidationError = (
712
+ promptInfo.opts.findValidationError
713
+ && promptInfo.opts.findValidationError(promptInfo.currentInputFieldText)
714
+ );
715
+
716
+ modal = (
717
+ <ModalForWrapper
718
+ key={`prompt-${promptInfo.title}`}
719
+ title={promptInfo.title}
720
+ // Don't show ok button if there is a validation error
721
+ type={(
722
+ (customValidationError || minNumCharsValidationError)
723
+ ? ModalType.Cancel
724
+ : ModalType.OkayCancel
725
+ )}
726
+ okayLabel={promptInfo.opts.confirmButtonText}
727
+ okayVariant={promptInfo.opts.confirmButtonVariant}
728
+ cancelLabel={promptInfo.opts.cancelButtonText}
729
+ cancelVariant={promptInfo.opts.cancelButtonVariant}
730
+ onClose={(buttonType) => {
731
+ // Get result
732
+ const result = (
733
+ buttonType === ModalButtonType.Okay
734
+ ? promptInfo.currentInputFieldText
735
+ : null
736
+ );
737
+
738
+ // Close prompt
739
+ setPromptInfo(undefined);
740
+
741
+ // Call handler
742
+ if (onPromptClosed) {
743
+ onPromptClosed(result);
744
+ }
745
+ }}
746
+ onTopOfOtherModals
747
+ dontAllowBackdropExit
748
+ >
749
+ <div>
750
+ {/* Message above input field */}
751
+ {promptInfo.opts.textAboveInputField && (
752
+ <div>
753
+ {promptInfo.opts.textAboveInputField}
754
+ </div>
755
+ )}
756
+
757
+ {/* Input field */}
758
+ <input
759
+ type="text"
760
+ className="form-control"
761
+ aria-label={promptInfo.opts.ariaLabel}
762
+ placeholder={promptInfo.opts.placeholder}
763
+ value={promptInfo.currentInputFieldText}
764
+ onChange={(e) => {
765
+ return setPromptInfo({
766
+ ...promptInfo,
767
+ currentInputFieldText: e.target.value,
768
+ });
769
+ }}
770
+ // eslint-disable-next-line jsx-a11y/no-autofocus
771
+ autoFocus
772
+ />
773
+
774
+ {/* Validation errors */}
775
+ {minNumCharsValidationError && (
776
+ <div className="text-danger fw-bold mt-2">
777
+ {minNumCharsValidationError}
778
+ </div>
779
+ )}
780
+ {customValidationError && (
781
+ <div className="text-danger fw-bold mt-2">
782
+ {customValidationError}
783
+ </div>
784
+ )}
785
+ </div>
786
+ </ModalForWrapper>
787
+ );
788
+ }
789
+
534
790
  /* ------ Custom Modal Portals ------ */
535
791
 
536
792
  // Custom modal portals
@@ -662,7 +918,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
662
918
  ? `
663
919
  .tooltip-inner {
664
920
  background-color: white;
665
- color: black;
921
+ color: black !important;
666
922
  border: 0.1rem solid black;
667
923
  pointer-events: none;
668
924
  }
@@ -675,7 +931,7 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
675
931
  : `
676
932
  .tooltip-inner {
677
933
  background-color: black;
678
- color: white;
934
+ color: white !important;
679
935
  border: 0.1rem solid white;
680
936
  pointer-events: none;
681
937
  }
@@ -46,6 +46,8 @@ type Props = {
46
46
  small?: boolean,
47
47
  // If true, show a dash in the checkbox (only relevant if unchecked)
48
48
  dashed?: boolean,
49
+ // If true, use complex formatting (tabs, newlines) in the text
50
+ useComplexFormatting?: boolean,
49
51
  };
50
52
 
51
53
  /*------------------------------------------------------------------------*/
@@ -80,6 +82,7 @@ const CheckboxButton: React.FC<Props> = (props) => {
80
82
  ),
81
83
  small,
82
84
  dashed,
85
+ useComplexFormatting,
83
86
  } = props;
84
87
 
85
88
  /*------------------------------------------------------------------------*/
@@ -114,16 +117,31 @@ const CheckboxButton: React.FC<Props> = (props) => {
114
117
  onChanged(!checked);
115
118
  }}
116
119
  >
117
- <div className="d-flex">
118
- <div className="align-items-center">
120
+ <div className="d-flex align-items-center">
121
+ <div className="me-1">
119
122
  <FontAwesomeIcon
120
123
  icon={icon}
121
- className="me-1"
122
124
  />
123
125
  </div>
124
- <div className="flex-grow-1">
125
- {text}
126
- </div>
126
+ {
127
+ useComplexFormatting
128
+ ? (
129
+ <pre
130
+ className="ps-1 text-start text-break m-0"
131
+ style={{
132
+ whiteSpace: 'pre-wrap',
133
+ tabSize: 2,
134
+ }}
135
+ >
136
+ {text}
137
+ </pre>
138
+ )
139
+ : (
140
+ <div className="flex-grow-1 text-start text-break">
141
+ {text}
142
+ </div>
143
+ )
144
+ }
127
145
  </div>
128
146
  </button>
129
147
  );
@@ -0,0 +1,317 @@
1
+ /**
2
+ * A simple dropdown menu
3
+ * @author Alessandra De Lucas
4
+ * @author Yuen Ler Chow
5
+ * @author Gabe Abrams
6
+ */
7
+
8
+ // Import React
9
+ import React, { useEffect, useReducer, useRef } from 'react';
10
+
11
+ // Import helpers
12
+ import combineClassNames from '../helpers/combineClassNames';
13
+
14
+ // Import shared types
15
+ import Variant from '../types/Variant';
16
+ import DropdownItemType from '../types/DropdownItemType';
17
+
18
+ // Check dark mode
19
+ import { isDarkModeOn } from '../client/initClient';
20
+
21
+ /*------------------------------------------------------------------------*/
22
+ /* -------------------------------- Types ------------------------------- */
23
+ /*------------------------------------------------------------------------*/
24
+
25
+ // Props definition
26
+ type Props = {
27
+ // List of items in the dropdown menu
28
+ items: DropdownItem[],
29
+ // Information about the dropdown button
30
+ dropdownButton: {
31
+ // Aria label for accessibility
32
+ ariaLabel: string,
33
+ // Unique ID
34
+ id: string,
35
+ // Button content
36
+ content?: React.ReactNode,
37
+ // Variant
38
+ variant?: Variant,
39
+ },
40
+ // If true, no arrow shows in dropdown
41
+ // noArrow?: boolean,
42
+ // Size of dropdown
43
+ // size?: DropdownSize,
44
+ // Direction of dropdown
45
+ // direction?: DropdownDirection,
46
+ };
47
+
48
+ // Details about a dropdown item
49
+ type DropdownItem = (
50
+ | {
51
+ // Header element
52
+ type: DropdownItemType.Header,
53
+ // Header content
54
+ content: React.ReactNode,
55
+ }
56
+ | {
57
+ // Divider element
58
+ type: DropdownItemType.Divider,
59
+ }
60
+ | {
61
+ // Item from dropdown menu
62
+ type: DropdownItemType.Item,
63
+ // Item content
64
+ content: React.ReactNode,
65
+ // Item aria label
66
+ ariaLabel: string,
67
+ // Unique item ID
68
+ id: string,
69
+ // Click function for dropdown item
70
+ onClick: () => void,
71
+ }
72
+ );
73
+
74
+ // Dropdown menu sizes
75
+ // enum DropdownSize {
76
+ // // Small dropdown size
77
+ // Small = 'Small',
78
+ // // Medium dropdown size
79
+ // Medium = 'Medium',
80
+ // // Large dropdown size
81
+ // Large = 'Large',
82
+ // }
83
+
84
+ // Dropdown menu direction
85
+ // enum DropdownDirection {
86
+ // // Dropdown menu appears centered
87
+ // Centered = 'Centered',
88
+ // // Dropdown menu appears to right of button
89
+ // Right = 'Right',
90
+ // // Dropdown menu appears to left of button
91
+ // Left = 'Left',
92
+ // // Dropdown menu appears above the button
93
+ // Up = 'Up',
94
+ // // Dropdown menu appears below the button
95
+ // Down = 'Down',
96
+ // }
97
+
98
+ /*------------------------------------------------------------------------*/
99
+ /* -------------------------------- State ------------------------------- */
100
+ /*------------------------------------------------------------------------*/
101
+
102
+ /* -------- State Definition -------- */
103
+
104
+ type State = {
105
+ // If true, the dropdown menu is open
106
+ isDropdownOpen: boolean,
107
+ };
108
+
109
+ /* ------------- Actions ------------ */
110
+
111
+ // Types of actions
112
+ enum ActionType {
113
+ // Toggle opening the dropdown menu
114
+ ToggleDropdown = 'ToggleDropdown',
115
+ // Close the dropdown menu
116
+ CloseDropdown = 'CloseDropdown',
117
+ }
118
+
119
+ // Action definitions
120
+ type Action = (
121
+ | {
122
+ type: ActionType.ToggleDropdown,
123
+ }
124
+ | {
125
+ type: ActionType.CloseDropdown,
126
+ }
127
+ );
128
+
129
+ /**
130
+ * Reducer that executes actions
131
+ * @author Alessandra De Lucas
132
+ * @author Yuen Ler Chow
133
+ * @param state current state
134
+ * @param action action to execute
135
+ */
136
+ const reducer = (state: State, action: Action): State => {
137
+ switch (action.type) {
138
+ case ActionType.ToggleDropdown: {
139
+ return {
140
+ ...state,
141
+ isDropdownOpen: !state.isDropdownOpen,
142
+ };
143
+ }
144
+ case ActionType.CloseDropdown: {
145
+ return {
146
+ ...state,
147
+ isDropdownOpen: false,
148
+ };
149
+ }
150
+ default: {
151
+ return state;
152
+ }
153
+ }
154
+ };
155
+
156
+ /*------------------------------------------------------------------------*/
157
+ /* ------------------------------ Component ----------------------------- */
158
+ /*------------------------------------------------------------------------*/
159
+
160
+ const Dropdown: React.FC<Props> = (props) => {
161
+ /*------------------------------------------------------------------------*/
162
+ /* -------------------------------- Setup ------------------------------- */
163
+ /*------------------------------------------------------------------------*/
164
+
165
+ /* -------------- Props ------------- */
166
+
167
+ // Destructure all props
168
+ const {
169
+ dropdownButton,
170
+ items,
171
+ } = props;
172
+
173
+ /* -------------- State ------------- */
174
+
175
+ // Initial state
176
+ const initialState: State = {
177
+ isDropdownOpen: false,
178
+ };
179
+
180
+ // Initialize state
181
+ const [state, dispatch] = useReducer(reducer, initialState);
182
+
183
+ // Destructure common state
184
+ const {
185
+ isDropdownOpen,
186
+ } = state;
187
+
188
+ /* -------------- Refs -------------- */
189
+
190
+ // Initialize refs
191
+ const dropdownRef = useRef<HTMLDivElement>(null);
192
+
193
+ /*------------------------------------------------------------------------*/
194
+ /* ------------------------- Component Functions ------------------------ */
195
+ /*------------------------------------------------------------------------*/
196
+
197
+ /**
198
+ * Handle clicking outside of the dropdown menu to close it
199
+ * @author Yuen Ler Chow
200
+ * @param event the mouse event
201
+ */
202
+ const handleClickOutside = (event: MouseEvent) => {
203
+ if (
204
+ // Dropdown has been rendered
205
+ dropdownRef.current
206
+ // Click occurred outside the dropdown
207
+ && !dropdownRef.current.contains(event.target as Element)
208
+ ) {
209
+ dispatch({ type: ActionType.CloseDropdown });
210
+ }
211
+ };
212
+
213
+ /*------------------------------------------------------------------------*/
214
+ /* ------------------------- Lifecycle Functions ------------------------ */
215
+ /*------------------------------------------------------------------------*/
216
+
217
+ /**
218
+ * Mount
219
+ * @author Yuen Ler Chow
220
+ */
221
+ useEffect(() => {
222
+ // Add event listener to close dropdown when clicking outside
223
+ document.addEventListener('mousedown', handleClickOutside);
224
+
225
+ // Cleanup
226
+ return () => {
227
+ document.removeEventListener('mousedown', handleClickOutside);
228
+ };
229
+ }, []);
230
+
231
+ /*----------------------------------------*/
232
+ /* --------------- Main UI -------------- */
233
+ /*----------------------------------------*/
234
+
235
+ return (
236
+ <div
237
+ className="dropdown"
238
+ ref={dropdownRef}
239
+ data-bs-theme={isDarkModeOn() ? 'dark' : undefined}
240
+ >
241
+ <button
242
+ className={
243
+ combineClassNames([
244
+ 'btn dropdown-toggle border',
245
+ isDropdownOpen && 'show',
246
+ `btn-${dropdownButton.variant}`,
247
+ dropdownButton.variant === Variant.Light && 'text-dark',
248
+ ])
249
+ }
250
+ type="button"
251
+ id={dropdownButton.id}
252
+ aria-expanded={isDropdownOpen}
253
+ aria-label={dropdownButton.ariaLabel}
254
+ onClick={() => {
255
+ dispatch({
256
+ type: ActionType.ToggleDropdown,
257
+ });
258
+ }}
259
+ >
260
+ {
261
+ dropdownButton.content
262
+ }
263
+ </button>
264
+ <ul
265
+ className={
266
+ combineClassNames([
267
+ 'dropdown-menu',
268
+ isDarkModeOn() && 'dropdown-menu-dark',
269
+ isDropdownOpen && 'show',
270
+ ])
271
+ }
272
+ >
273
+ {Object.values(items).map((item) => {
274
+ if (item.type === DropdownItemType.Header) {
275
+ return (
276
+ // TODO: Implement header
277
+ <span />
278
+ );
279
+ }
280
+ if (item.type === DropdownItemType.Divider) {
281
+ return (
282
+ // TODO: Implement divider
283
+ <span />
284
+ );
285
+ }
286
+ return (
287
+ <li
288
+ key={item.id}
289
+ >
290
+ <button
291
+ type="button"
292
+ aria-label={item.ariaLabel}
293
+ className="dropdown-item"
294
+ onClick={(e) => {
295
+ e.preventDefault();
296
+ dispatch({
297
+ type: ActionType.CloseDropdown,
298
+ });
299
+ item.onClick();
300
+ }}
301
+ >
302
+ {item.content}
303
+ </button>
304
+ </li>
305
+ );
306
+ })}
307
+ </ul>
308
+ </div>
309
+ );
310
+ };
311
+
312
+ /*------------------------------------------------------------------------*/
313
+ /* ------------------------------- Wrap Up ------------------------------ */
314
+ /*------------------------------------------------------------------------*/
315
+
316
+ // Export component
317
+ export default Dropdown;
@@ -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.Secondary}
318
+ uncheckedVariant={Variant.Light}
319
319
  />
320
320
  );
321
321
  })
@@ -272,6 +272,7 @@ const NestableItemList: React.FC<Props> = (props) => {
272
272
  }}
273
273
  ariaLabel={`Select ${item.name}`}
274
274
  checkedVariant={Variant.Light}
275
+ uncheckedVariant={Variant.Light}
275
276
  />
276
277
 
277
278
  {/* Children */}
@@ -959,10 +959,23 @@ const LogReviewer: React.FC<Props> = (props) => {
959
959
  const { year, month } = toLoad[i];
960
960
 
961
961
  // Load
962
- const logs = await visitServerEndpoint({
963
- path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
964
- method: 'GET',
965
- });
962
+ let logs: Log[] = [];
963
+ let pageNumber = 1;
964
+ let hasAnotherPage = true;
965
+
966
+ while (hasAnotherPage) {
967
+ const response = await visitServerEndpoint({
968
+ path: `${LOG_REVIEW_ROUTE_PATH_PREFIX}/years/${year}/months/${month}`,
969
+ method: 'GET',
970
+ params: {
971
+ pageNumber,
972
+ },
973
+ });
974
+
975
+ logs = logs.concat(response.items);
976
+ hasAnotherPage = response.hasAnotherPage;
977
+ pageNumber += 1;
978
+ }
966
979
 
967
980
  // Add to map
968
981
  if (!logMap[year]) {