dce-reactkit 3.0.0-beta.39 → 3.0.0-beta.41

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/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$6 = {
10
+ declare type Props$7 = {
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$6>;
55
+ declare const AppWrapper: React.FC<Props$7>;
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$5 = {
68
+ declare type Props$6 = {
69
69
  error: any;
70
70
  title?: string;
71
71
  onClose?: () => void;
72
72
  };
73
- declare const ErrorBox: React.FC<Props$5>;
73
+ declare const ErrorBox: React.FC<Props$6>;
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$4 = {
139
+ declare type Props$5 = {
140
140
  type?: ModalType;
141
141
  size?: ModalSize;
142
142
  title?: React.ReactNode;
@@ -165,26 +165,26 @@ declare type Props$4 = {
165
165
  confirmVariant?: Variant;
166
166
  onTopOfOtherModals?: boolean;
167
167
  };
168
- declare const Modal: React.FC<Props$4>;
168
+ declare const Modal: React.FC<Props$5>;
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$3 = {
175
+ declare type Props$4 = {
176
176
  title: React.ReactNode;
177
177
  children: React.ReactNode;
178
178
  noBottomPadding?: boolean;
179
179
  };
180
- declare const TabBox: React.FC<Props$3>;
180
+ declare const TabBox: React.FC<Props$4>;
181
181
 
182
182
  /**
183
183
  * A radio selection button
184
184
  * @author Gabe Abrams
185
185
  */
186
186
 
187
- declare type Props$2 = {
187
+ declare type Props$3 = {
188
188
  text: string;
189
189
  onSelected: () => void;
190
190
  ariaLabel: string;
@@ -196,14 +196,14 @@ declare type Props$2 = {
196
196
  unselectedVariant?: Variant;
197
197
  small?: boolean;
198
198
  };
199
- declare const RadioButton: React.FC<Props$2>;
199
+ declare const RadioButton: React.FC<Props$3>;
200
200
 
201
201
  /**
202
202
  * A checkbox button
203
203
  * @author Gabe Abrams
204
204
  */
205
205
 
206
- declare type Props$1 = {
206
+ declare type Props$2 = {
207
207
  text: string;
208
208
  onChanged: (checked: boolean) => void;
209
209
  ariaLabel: string;
@@ -215,19 +215,40 @@ declare type Props$1 = {
215
215
  uncheckedVariant?: Variant;
216
216
  small?: boolean;
217
217
  };
218
- declare const CheckboxButton: React.FC<Props$1>;
218
+ declare const CheckboxButton: React.FC<Props$2>;
219
219
 
220
220
  /**
221
221
  * Input group with a title and space for buttons
222
222
  * @author Gabe Abrams
223
223
  */
224
224
 
225
- declare type Props = {
225
+ declare type Props$1 = {
226
226
  label: string;
227
227
  minLabelWidth?: string;
228
228
  children: React.ReactNode;
229
229
  };
230
- declare const ButtonInputGroup: React.FC<Props>;
230
+ declare const ButtonInputGroup: React.FC<Props$1>;
231
+
232
+ /**
233
+ * A very simple, lightweight date chooser
234
+ * @author Gabe Abrams
235
+ */
236
+
237
+ declare type Props = {
238
+ name: string;
239
+ month: number;
240
+ day: number;
241
+ year: number;
242
+ /**
243
+ * Handler for when date changes
244
+ * @param month new 1-indexed month number
245
+ * @param day new 1-indexed day number
246
+ * @param year new full year number
247
+ */
248
+ onChange: (month: number, day: number, year: number) => void;
249
+ numMonthsToShow?: number;
250
+ };
251
+ declare const SimpleDateChooser: React.FC<Props>;
231
252
 
232
253
  /**
233
254
  * An error with a code
@@ -428,6 +449,31 @@ declare const initServer: (opts: {
428
449
  getLaunchInfo: GetLaunchInfoFunction;
429
450
  }) => void;
430
451
 
452
+ /**
453
+ * Get a number's ordinal
454
+ * @author Gabe Abrams
455
+ * @param num the number being analyzed
456
+ * @returns ordinal
457
+ */
458
+ declare const getOrdinal: (num: number) => string;
459
+
460
+ /**
461
+ * Get current time info in US Boston Eastern Time, independent of machine
462
+ * timezone
463
+ * @author Gabe Abrams
464
+ * @param {Date} [date=now] the date to get info on
465
+ * @returns object with timestamp (ms since epoch) and numbers
466
+ * corresponding to ET time values for year, month, day, hour, minute
467
+ */
468
+ declare const getTimeInfoInET: (date?: Date | undefined) => {
469
+ timestamp: number;
470
+ year: number;
471
+ month: number;
472
+ day: number;
473
+ hour: number;
474
+ minute: number;
475
+ };
476
+
431
477
  /**
432
478
  * List of error codes built into the react kit
433
479
  * @author Gabe Abrams
@@ -443,4 +489,4 @@ declare enum ReactKitErrorCode {
443
489
  NoCACCLGetLaunchInfoFunction = "DRK8"
444
490
  }
445
491
 
446
- export { AppWrapper, ButtonInputGroup, CheckboxButton, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType, ModalSize, ModalType, ParamType, RadioButton, ReactKitErrorCode, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, handleError, handleSuccess, initServer, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, visitServerEndpoint, waitMs };
492
+ export { AppWrapper, ButtonInputGroup, CheckboxButton, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType, ModalSize, ModalType, ParamType, RadioButton, ReactKitErrorCode, SimpleDateChooser, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, genRouteHandler, getOrdinal, getTimeInfoInET, handleError, handleSuccess, initServer, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, visitServerEndpoint, waitMs };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.0.0-beta.39",
3
+ "version": "3.0.0-beta.41",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -0,0 +1,217 @@
1
+ /**
2
+ * A very simple, lightweight date chooser
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import helpers
10
+ import getOrdinal from '../helpers/getOrdinal';
11
+ import getTimeInfoInET from '../helpers/getTimeInfoInET';
12
+
13
+ /*------------------------------------------------------------------------*/
14
+ /* Types */
15
+ /*------------------------------------------------------------------------*/
16
+
17
+ type Props = {
18
+ // Name of the chooser (machine-readable, hyphenated)
19
+ name: string,
20
+ // Currently selected month
21
+ month: number,
22
+ // Currently selected day
23
+ day: number,
24
+ // Currently selected year
25
+ year: number,
26
+ /**
27
+ * Handler for when date changes
28
+ * @param month new 1-indexed month number
29
+ * @param day new 1-indexed day number
30
+ * @param year new full year number
31
+ */
32
+ onChange: (month: number, day: number, year: number) => void,
33
+ // Number of months in the future to allow the user to schedule into
34
+ // (max is 12)
35
+ numMonthsToShow?: number,
36
+ };
37
+
38
+ /*------------------------------------------------------------------------*/
39
+ /* Constants */
40
+ /*------------------------------------------------------------------------*/
41
+
42
+ // Constants
43
+ const MONTH_NAMES = [
44
+ 'January',
45
+ 'February',
46
+ 'March',
47
+ 'April',
48
+ 'May',
49
+ 'June',
50
+ 'July',
51
+ 'August',
52
+ 'September',
53
+ 'October',
54
+ 'November',
55
+ 'December',
56
+ ];
57
+
58
+ /*------------------------------------------------------------------------*/
59
+ /* Component */
60
+ /*------------------------------------------------------------------------*/
61
+
62
+ const SimpleDateChooser: React.FC<Props> = (props) => {
63
+ /*------------------------------------------------------------------------*/
64
+ /* Setup */
65
+ /*------------------------------------------------------------------------*/
66
+
67
+ /* -------------- Props ------------- */
68
+
69
+ const {
70
+ name,
71
+ month,
72
+ day,
73
+ year,
74
+ onChange,
75
+ } = props;
76
+ const numMonthsToShow = Math.min(props.numMonthsToShow ?? 6, 12);
77
+
78
+ /*------------------------------------------------------------------------*/
79
+ /* Render */
80
+ /*------------------------------------------------------------------------*/
81
+
82
+ /*----------------------------------------*/
83
+ /* Main UI */
84
+ /*----------------------------------------*/
85
+
86
+ // Determine the set of choices allowed
87
+ const today = getTimeInfoInET();
88
+ const choices: {
89
+ choiceName: string,
90
+ month: number,
91
+ days: number[],
92
+ year: number,
93
+ }[] = [];
94
+ for (let i = 0; i < numMonthsToShow; i++) {
95
+ // Get month and year info
96
+ const unmoddedMonth = (today.month + i);
97
+ const month = (
98
+ unmoddedMonth > 12
99
+ ? unmoddedMonth - 12
100
+ : unmoddedMonth
101
+ );
102
+ const monthName = MONTH_NAMES[month - 1];
103
+ const year = (
104
+ unmoddedMonth > 12
105
+ ? today.year + 1
106
+ : today.year
107
+ );
108
+
109
+ // Figure out which days are allowed
110
+ const days = [];
111
+ const numDaysInMonth = (new Date(year, month, 0)).getDate();
112
+ const firstDay = (
113
+ month === today.month
114
+ ? today.day // Current month: start at current date
115
+ : 1 // Future month: start at beginning of month
116
+ );
117
+ for (let day = firstDay; day <= numDaysInMonth; day++) {
118
+ days.push(day);
119
+ }
120
+
121
+ choices.push({
122
+ choiceName: `${monthName} ${year}`,
123
+ month,
124
+ year,
125
+ days,
126
+ });
127
+ }
128
+
129
+ // Create choice options
130
+ const monthOptions: React.ReactNode[] = [];
131
+ const dayOptions: React.ReactNode[] = [];
132
+ choices.forEach((choice) => {
133
+ // Create month option
134
+ monthOptions.push(
135
+ <option
136
+ key={choice.month}
137
+ value={choice.month}
138
+ aria-label={`choose ${choice.choiceName}`}
139
+ onSelect={() => {
140
+ onChange(choice.month, choice.days[0], choice.year);
141
+ }}
142
+ >
143
+ {choice.choiceName}
144
+ </option>
145
+ );
146
+
147
+ if (month === choice.month) {
148
+ // This is the currently selected month
149
+ // Create day options
150
+ choice.days.forEach((dayChoice) => {
151
+ const ordinal = getOrdinal(dayChoice);
152
+ dayOptions.push(
153
+ <option
154
+ key={dayChoice}
155
+ value={dayChoice}
156
+ aria-label={`choose date ${dayChoice}`}
157
+ >
158
+ {dayChoice}
159
+ {ordinal}
160
+ </option>
161
+ );
162
+ });
163
+ }
164
+ });
165
+
166
+ return (
167
+ <div
168
+ className="SimpleDateChooser d-inline-block"
169
+ aria-label={`date chooser with selected date: ${month} ${day}, ${year}`}
170
+ >
171
+ {/* Month Chooser */}
172
+ <select
173
+ className="custom-select d-inline-block mr-1"
174
+ style={{ width: 'auto' }}
175
+ id={`SimpleDateChooser-${name}-month`}
176
+ value={month}
177
+ onChange={(e) => {
178
+ const choice = choices[e.target.selectedIndex];
179
+
180
+ // Change day, month, and year
181
+ onChange(
182
+ choice.month,
183
+ choice.days[0],
184
+ choice.year,
185
+ );
186
+ }}
187
+ >
188
+ {monthOptions}
189
+ </select>
190
+
191
+ {/* Day Chooser */}
192
+ <select
193
+ className="custom-select d-inline-block"
194
+ style={{ width: 'auto' }}
195
+ id={`SimpleDateChooser-${name}-day`}
196
+ value={day}
197
+ onChange={(e) => {
198
+ // Only change the day
199
+ onChange(
200
+ month,
201
+ Number.parseInt(e.target.value, 10),
202
+ year,
203
+ );
204
+ }}
205
+ >
206
+ {dayOptions}
207
+ </select>
208
+ </div>
209
+ );
210
+ };
211
+
212
+ /*------------------------------------------------------------------------*/
213
+ /* Wrap Up */
214
+ /*------------------------------------------------------------------------*/
215
+
216
+ // Export component
217
+ export default SimpleDateChooser;
@@ -0,0 +1,13 @@
1
+ const ORDINALS = ['th', 'st', 'nd', 'rd'];
2
+
3
+ /**
4
+ * Get a number's ordinal
5
+ * @author Gabe Abrams
6
+ * @param num the number being analyzed
7
+ * @returns ordinal
8
+ */
9
+ const getOrdinal = (num: number): string => {
10
+ return (ORDINALS[(num - 20) % 10] ?? ORDINALS[num] ?? ORDINALS[0]);
11
+ };
12
+
13
+ export default getOrdinal;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Get current time info in US Boston Eastern Time, independent of machine
3
+ * timezone
4
+ * @author Gabe Abrams
5
+ * @param {Date} [date=now] the date to get info on
6
+ * @returns object with timestamp (ms since epoch) and numbers
7
+ * corresponding to ET time values for year, month, day, hour, minute
8
+ */
9
+ const getTimeInfoInET = (date?: Date): {
10
+ timestamp: number,
11
+ year: number,
12
+ month: number,
13
+ day: number,
14
+ hour: number,
15
+ minute: number,
16
+ } => {
17
+ // Create a time string
18
+ const d = (date || new Date());
19
+ const str = d.toLocaleString(
20
+ 'en-US', // Using US encoding (it's the only one installed on containers)
21
+ { timeZone: 'America/New_York' } // Force EST timezone
22
+ );
23
+
24
+ // Parse the string for the date/time info
25
+ const [dateStr, timeStr] = str.split(', '); // Format: MM/DD/YYYY, HH:MM:SS AM
26
+ const [monthStr, dayStr, yearStr] = dateStr.split('/'); // Format: MM/DD/YYYY
27
+ const [hourStr, minStr, ending] = timeStr.split(':'); // Format: HH:MM:SS AM
28
+
29
+ // Create all time numbers
30
+ const timestamp = d.getTime();
31
+ const year = Number.parseInt(yearStr, 10);
32
+ const month = Number.parseInt(monthStr, 10);
33
+ const day = Number.parseInt(dayStr, 10);
34
+ const minute = Number.parseInt(minStr, 10);
35
+ let hour = Number.parseInt(hourStr, 10);
36
+ // Convert from am/pm to 24hr
37
+ const isAM = ending.toLowerCase().includes('am');
38
+ const isPM = !isAM;
39
+ if (isPM && hour !== 12) {
40
+ hour += 12;
41
+ } else if (isAM && hour === 12) {
42
+ hour = 0;
43
+ }
44
+
45
+ // Return
46
+ return {
47
+ timestamp,
48
+ year,
49
+ month,
50
+ day,
51
+ hour,
52
+ minute,
53
+ };
54
+ };
55
+
56
+ export default getTimeInfoInET;
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ import TabBox from './components/TabBox';
7
7
  import RadioButton from './components/RadioButton';
8
8
  import CheckboxButton from './components/CheckboxButton';
9
9
  import ButtonInputGroup from './components/ButtonInputGroup';
10
+ import SimpleDateChooser from './components/SimpleDateChooser';
10
11
 
11
12
  // Import errors
12
13
  import ErrorWithCode from './errors/ErrorWithCode';
@@ -27,6 +28,8 @@ import genRouteHandler from './helpers/genRouteHandler';
27
28
  import handleError from './helpers/handleError';
28
29
  import handleSuccess from './helpers/handleSuccess';
29
30
  import initServer from './server/initServer';
31
+ import getOrdinal from './helpers/getOrdinal';
32
+ import getTimeInfoInET from './helpers/getTimeInfoInET';
30
33
 
31
34
  // Import types
32
35
  import ModalButtonType from './types/ModalButtonType';
@@ -47,6 +50,7 @@ export {
47
50
  RadioButton,
48
51
  CheckboxButton,
49
52
  ButtonInputGroup,
53
+ SimpleDateChooser,
50
54
  // Global functions
51
55
  alert,
52
56
  confirm,
@@ -64,6 +68,8 @@ export {
64
68
  roundToNumDecimals,
65
69
  sum,
66
70
  waitMs,
71
+ getOrdinal,
72
+ getTimeInfoInET,
67
73
  // Client helpers
68
74
  visitServerEndpoint,
69
75
  // Server helpers
File without changes