dce-reactkit 5.0.1 → 5.0.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.
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Fake progress bar that approaches completion but never fully finishes.
3
+ * Built on top of the existing ProgressBar component.
4
+ *
5
+ * Two phases:
6
+ * 1. Fast phase (0–90%): random jumps of 1–5% at randomized intervals,
7
+ * paced to reach ~90% in roughly estimatedTimeSec seconds.
8
+ * 2. Slow phase (90–99%): crawls 1% at a time with longer random delays,
9
+ * never reaching 100% on its own.
10
+ *
11
+ * When isFinished becomes true, the bar immediately jumps to 100%.
12
+ * @author Yuen Ler Chow
13
+ */
14
+
15
+ // Import React
16
+ import React, { useReducer, useEffect } from 'react';
17
+
18
+ // Import helpers
19
+ import { waitMs } from 'dce-commonkit';
20
+
21
+ // Import types
22
+ import Variant from '../types/Variant';
23
+ import ProgressBarSize from '../types/ProgressBarSize';
24
+
25
+ // Import components
26
+ import ProgressBar from './ProgressBar';
27
+
28
+ /*------------------------------------------------------------------------*/
29
+ /* ------------------------------ Constants ----------------------------- */
30
+ /*------------------------------------------------------------------------*/
31
+
32
+ // Percent where the bar switches from fast random jumps to a slow crawl
33
+ const SLOW_PHASE_START_PERCENT = 90;
34
+
35
+ // Highest percent the bar will reach on its own (before isFinished is true)
36
+ const MAX_UNFINISHED_PERCENT = 99;
37
+
38
+ // Average step size during the fast phase (uniform random from 1–5)
39
+ const AVG_FAST_PHASE_STEP = 3;
40
+
41
+ // Minimum delay (ms) between steps during the slow phase
42
+ const SLOW_PHASE_MIN_DELAY_MS = 2000;
43
+
44
+ // Maximum delay (ms) between steps during the slow phase
45
+ const SLOW_PHASE_MAX_DELAY_MS = 5000;
46
+
47
+ /*------------------------------------------------------------------------*/
48
+ /* -------------------------------- Types ------------------------------- */
49
+ /*------------------------------------------------------------------------*/
50
+
51
+ // Props definition
52
+ type Props = {
53
+ // True if the task is finished (triggers animation to 100%)
54
+ isFinished?: boolean,
55
+ // Estimated time (in seconds) to finish the task (default 8)
56
+ estimatedTimeSec?: number,
57
+ // Whether to show striped effect (default false)
58
+ striped?: boolean,
59
+ // Variant of the progress bar (default warning)
60
+ variant?: Variant,
61
+ // Background variants (default secondary subtle)
62
+ bgVariant?: Variant,
63
+ // Hide outline (default false)
64
+ showOutline?: boolean,
65
+ // Size of the progress bar (default md)
66
+ size?: ProgressBarSize,
67
+ };
68
+
69
+ /*------------------------------------------------------------------------*/
70
+ /* -------------------------------- State ------------------------------- */
71
+ /*------------------------------------------------------------------------*/
72
+
73
+ /* -------- State Definition -------- */
74
+
75
+ type State = {
76
+ // Current progress percentage (0-99)
77
+ progress: number,
78
+ };
79
+
80
+ /* ------------- Actions ------------ */
81
+
82
+ // Types of actions
83
+ enum ActionType {
84
+ // Advance the progress by a given step
85
+ Advance = 'Advance',
86
+ // Set progress to 100% (task complete)
87
+ Complete = 'Complete',
88
+ }
89
+
90
+ // Action definitions
91
+ type Action = (
92
+ | {
93
+ // Action type
94
+ type: ActionType.Advance,
95
+ // Number of percent points to advance
96
+ step: number,
97
+ }
98
+ | {
99
+ // Action type
100
+ type: ActionType.Complete,
101
+ }
102
+ );
103
+
104
+ /**
105
+ * Reducer that executes actions
106
+ * @author Yuen Ler Chow
107
+ * @param state current state
108
+ * @param action action to execute
109
+ */
110
+ const reducer = (state: State, action: Action): State => {
111
+ switch (action.type) {
112
+ case ActionType.Advance: {
113
+ return {
114
+ ...state,
115
+ progress: Math.min(100, state.progress + action.step),
116
+ };
117
+ }
118
+ case ActionType.Complete: {
119
+ return {
120
+ ...state,
121
+ progress: 100,
122
+ };
123
+ }
124
+ default: {
125
+ return state;
126
+ }
127
+ }
128
+ };
129
+
130
+ /*------------------------------------------------------------------------*/
131
+ /* ------------------------------ Component ----------------------------- */
132
+ /*------------------------------------------------------------------------*/
133
+
134
+ const FakeProgressBar: React.FC<Props> = (props) => {
135
+ /*------------------------------------------------------------------------*/
136
+ /* -------------------------------- Setup ------------------------------- */
137
+ /*------------------------------------------------------------------------*/
138
+
139
+ /* -------------- Props ------------- */
140
+
141
+ // Destructure props
142
+ const {
143
+ isFinished,
144
+ estimatedTimeSec = 8,
145
+ striped,
146
+ variant,
147
+ bgVariant,
148
+ showOutline,
149
+ size,
150
+ } = props;
151
+
152
+ /* -------------- State ------------- */
153
+
154
+ // Initial state
155
+ const initialState: State = {
156
+ progress: 0,
157
+ };
158
+
159
+ // Initialize state
160
+ const [state, dispatch] = useReducer(reducer, initialState);
161
+
162
+ // Destructure common state
163
+ const {
164
+ progress,
165
+ } = state;
166
+
167
+ // Convert estimate to milliseconds
168
+ const estimatedMs = estimatedTimeSec * 1000;
169
+
170
+ // Average interval between updates to reach ~90% in the estimated time
171
+ const avgIntervalMs = (estimatedMs / SLOW_PHASE_START_PERCENT) * AVG_FAST_PHASE_STEP;
172
+
173
+ /*------------------------------------------------------------------------*/
174
+ /* ------------------------- Lifecycle Functions ------------------------ */
175
+ /*------------------------------------------------------------------------*/
176
+
177
+ /**
178
+ * Animate to 100% when isFinished becomes true
179
+ * @author Yuen Ler Chow
180
+ */
181
+ useEffect(
182
+ () => {
183
+ if (isFinished) {
184
+ dispatch({
185
+ type: ActionType.Complete,
186
+ });
187
+ }
188
+ },
189
+ [isFinished],
190
+ );
191
+
192
+ /**
193
+ * Advance the progress in two phases:
194
+ * 1) Random jumps up to 90%
195
+ * 2) Slow crawl from 90% to 99%
196
+ * @author Yuen Ler Chow
197
+ */
198
+ useEffect(
199
+ () => {
200
+ // Stop if already finished or at max
201
+ if (isFinished || progress >= MAX_UNFINISHED_PERCENT) {
202
+ return;
203
+ }
204
+
205
+ // Determine which phase we are in
206
+ const inFastPhase = progress < SLOW_PHASE_START_PERCENT;
207
+
208
+ // Default values
209
+ let delay = avgIntervalMs;
210
+ let step = 1;
211
+
212
+ if (inFastPhase) {
213
+ // Randomize delay around the average interval for variability
214
+ const randomnessMultiplier = 0.5 + (Math.random());
215
+ delay = avgIntervalMs * randomnessMultiplier;
216
+
217
+ // Randomly move forward by 1-5%, capped at 90%
218
+ step = Math.min(
219
+ 1 + Math.floor(Math.random() * 5),
220
+ SLOW_PHASE_START_PERCENT - progress,
221
+ );
222
+ } else {
223
+ // Crawl slowly 1% at a time, clamped so we don't exceed 99%
224
+ delay = SLOW_PHASE_MIN_DELAY_MS + (Math.random() * (SLOW_PHASE_MAX_DELAY_MS - SLOW_PHASE_MIN_DELAY_MS));
225
+ step = Math.min(1, MAX_UNFINISHED_PERCENT - progress);
226
+ }
227
+
228
+ // Track whether this effect invocation is still current
229
+ let cancelled = false;
230
+
231
+ // Wait then dispatch the next progress update
232
+ (async () => {
233
+ await waitMs(delay);
234
+ if (!cancelled) {
235
+ dispatch({
236
+ type: ActionType.Advance,
237
+ step,
238
+ });
239
+ }
240
+ })();
241
+
242
+ return () => {
243
+ cancelled = true;
244
+ };
245
+ },
246
+ [progress, avgIntervalMs, isFinished],
247
+ );
248
+
249
+ /*------------------------------------------------------------------------*/
250
+ /* ------------------------------- Render ------------------------------- */
251
+ /*------------------------------------------------------------------------*/
252
+
253
+ /*----------------------------------------*/
254
+ /* --------------- Main UI -------------- */
255
+ /*----------------------------------------*/
256
+
257
+ return (
258
+ <ProgressBar
259
+ percentProgress={progress}
260
+ striped={striped}
261
+ variant={variant}
262
+ bgVariant={bgVariant}
263
+ showOutline={showOutline}
264
+ size={size}
265
+ />
266
+ );
267
+ };
268
+
269
+ /*------------------------------------------------------------------------*/
270
+ /* ------------------------------- Wrap Up ------------------------------ */
271
+ /*------------------------------------------------------------------------*/
272
+
273
+ // Export component
274
+ export default FakeProgressBar;
@@ -51,6 +51,8 @@ type Props = {
51
51
  dontAllowFuture?: boolean,
52
52
  // If true, the chooser is disabled
53
53
  isDisabled?: boolean,
54
+ // If true, hide the "day" dropdown and just show the month and year
55
+ hideDay?: boolean,
54
56
  };
55
57
 
56
58
  /*------------------------------------------------------------------------*/
@@ -292,6 +294,7 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
292
294
  day,
293
295
  year,
294
296
  isDisabled = false,
297
+ hideDay,
295
298
  } = props;
296
299
 
297
300
  // Get choices
@@ -442,24 +445,26 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
442
445
  </select>
443
446
 
444
447
  {/* Day Chooser */}
445
- <select
446
- aria-label={`day for ${ariaLabel}`}
447
- className="custom-select form-select d-inline-block"
448
- style={{ width: 'auto' }}
449
- id={`SimpleDateChooser-${name}-day`}
450
- value={day}
451
- onChange={(e) => {
452
- // Only change the day
453
- onChange(
454
- month,
455
- Number.parseInt(e.target.value, 10),
456
- year,
457
- );
458
- }}
459
- disabled={isDisabled}
460
- >
461
- {dayOptions}
462
- </select>
448
+ {!hideDay && (
449
+ <select
450
+ aria-label={`day for ${ariaLabel}`}
451
+ className="custom-select form-select d-inline-block"
452
+ style={{ width: 'auto' }}
453
+ id={`SimpleDateChooser-${name}-day`}
454
+ value={day}
455
+ onChange={(e) => {
456
+ // Only change the day
457
+ onChange(
458
+ month,
459
+ Number.parseInt(e.target.value, 10),
460
+ year,
461
+ );
462
+ }}
463
+ disabled={isDisabled}
464
+ >
465
+ {dayOptions}
466
+ </select>
467
+ )}
463
468
  </div>
464
469
  );
465
470
  }
@@ -476,10 +481,14 @@ const SimpleDateChooser: React.FC<Props> = (props) => {
476
481
  aria-label={`edit date for ${ariaLabel}`}
477
482
  >
478
483
  {getMonthName(month).full}
479
- {' '}
480
- {day}
481
- {getOrdinal(day)}
482
- ,
484
+ {!hideDay && (
485
+ <>
486
+ {' '}
487
+ {day}
488
+ {getOrdinal(day)}
489
+ ,
490
+ </>
491
+ )}
483
492
  {' '}
484
493
  {year}
485
494
  </button>
@@ -0,0 +1,98 @@
1
+ /**
2
+ * A very simple, lightweight month chooser
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import other components
10
+ import SimpleDateChooser from './SimpleDateChooser';
11
+
12
+ /*------------------------------------------------------------------------*/
13
+ /* -------------------------------- Types ------------------------------- */
14
+ /*------------------------------------------------------------------------*/
15
+
16
+ type Props = {
17
+ // Aria label
18
+ ariaLabel: string,
19
+ // Name of the chooser (machine-readable, hyphenated)
20
+ name: string,
21
+ // Currently selected month
22
+ month: number,
23
+ // Currently selected year
24
+ year: number,
25
+ /**
26
+ * Handler for when month changes
27
+ * @param month new 1-indexed month number
28
+ * @param year new full year number
29
+ */
30
+ onChange: (month: number, year: number) => void,
31
+ // Number of months in either the past or future to allow the user to choose from
32
+ // If we aren't allowing the past or the future, we will throw an error
33
+ // (max is 12, default is 6 in the past and 6 in the future)
34
+ numMonthsToShow?: number,
35
+ // If true, the user isn't allowed to select dates in the past
36
+ dontAllowPast?: boolean,
37
+ // If true, the user isn't allowed to select dates in the future
38
+ dontAllowFuture?: boolean,
39
+ // If true, the chooser is disabled
40
+ isDisabled?: boolean,
41
+ };
42
+
43
+ /*------------------------------------------------------------------------*/
44
+ /* ------------------------------ Component ----------------------------- */
45
+ /*------------------------------------------------------------------------*/
46
+
47
+ const SimpleMonthChooser: React.FC<Props> = (props) => {
48
+ /*------------------------------------------------------------------------*/
49
+ /* -------------------------------- Setup ------------------------------- */
50
+ /*------------------------------------------------------------------------*/
51
+
52
+ /* -------------- Props ------------- */
53
+
54
+ const {
55
+ ariaLabel,
56
+ name,
57
+ dontAllowPast,
58
+ dontAllowFuture,
59
+ numMonthsToShow,
60
+ onChange,
61
+ month,
62
+ year,
63
+ isDisabled,
64
+ } = props;
65
+
66
+ /*------------------------------------------------------------------------*/
67
+ /* ------------------------------- Render ------------------------------- */
68
+ /*------------------------------------------------------------------------*/
69
+
70
+ /*----------------------------------------*/
71
+ /* --------------- Main UI -------------- */
72
+ /*----------------------------------------*/
73
+
74
+ return (
75
+ <SimpleDateChooser
76
+ ariaLabel={ariaLabel}
77
+ name={name}
78
+ month={month}
79
+ day={1}
80
+ year={year}
81
+ numMonthsToShow={numMonthsToShow}
82
+ dontAllowPast={dontAllowPast}
83
+ dontAllowFuture={dontAllowFuture}
84
+ isDisabled={isDisabled}
85
+ hideDay
86
+ onChange={(newMonth, newDay, newYear) => {
87
+ onChange(newMonth, newYear);
88
+ }}
89
+ />
90
+ );
91
+ };
92
+
93
+ /*------------------------------------------------------------------------*/
94
+ /* ------------------------------- Wrap Up ------------------------------ */
95
+ /*------------------------------------------------------------------------*/
96
+
97
+ // Export component
98
+ export default SimpleMonthChooser;
package/src/index.ts CHANGED
@@ -86,6 +86,7 @@ import RadioButton from './components/RadioButton';
86
86
  import CheckboxButton from './components/CheckboxButton';
87
87
  import ButtonInputGroup from './components/ButtonInputGroup';
88
88
  import SimpleDateChooser from './components/SimpleDateChooser';
89
+ import SimpleMonthChooser from './components/SimpleMonthChooser';
89
90
  import SimpleTimeChooser from './components/SimpleTimeChooser';
90
91
  import Drawer from './components/Drawer';
91
92
  import PopSuccessMark from './components/PopSuccessMark';
@@ -103,6 +104,7 @@ import AutoscrollToBottomContainer from './components/AutoscrollToBottomContaine
103
104
  import MultiSwitch from './components/MultiSwitch';
104
105
  import Dropdown from './components/Dropdown';
105
106
  import ProgressBar from './components/ProgressBar';
107
+ import FakeProgressBar from './components/FakeProgressBar';
106
108
 
107
109
  // Import dynamic constants
108
110
  import DynamicWord from './dynamicConstants/DynamicWord';
@@ -146,6 +148,7 @@ export {
146
148
  CheckboxButton,
147
149
  ButtonInputGroup,
148
150
  SimpleDateChooser,
151
+ SimpleMonthChooser,
149
152
  SimpleTimeChooser,
150
153
  Drawer,
151
154
  PopSuccessMark,
@@ -163,6 +166,7 @@ export {
163
166
  MultiSwitch,
164
167
  Dropdown,
165
168
  ProgressBar,
169
+ FakeProgressBar,
166
170
  // Global functions
167
171
  alert,
168
172
  prompt,