dce-reactkit 3.0.0-beta.4 → 3.0.0-beta.40

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 (60) hide show
  1. package/.eslintrc.js +95 -0
  2. package/README.md +1 -546
  3. package/dist/cjs/index.js +784 -85
  4. package/dist/cjs/index.js.map +1 -1
  5. package/dist/cjs/types/components/AppWrapper.d.ts +25 -2
  6. package/dist/cjs/types/components/ButtonInputGroup.d.ts +12 -0
  7. package/dist/cjs/types/components/CheckboxButton.d.ts +20 -0
  8. package/dist/cjs/types/components/RadioButton.d.ts +20 -0
  9. package/dist/cjs/types/components/SimpleDateChooser.d.ts +21 -0
  10. package/dist/cjs/types/components/TabBox.d.ts +1 -0
  11. package/dist/cjs/types/helpers/genRouteHandler.d.ts +30 -0
  12. package/dist/cjs/types/helpers/getOrdinal.d.ts +8 -0
  13. package/dist/cjs/types/helpers/getTimeInfoInET.d.ts +17 -0
  14. package/dist/cjs/types/helpers/handleError.d.ts +18 -0
  15. package/dist/cjs/types/helpers/handleSuccess.d.ts +8 -0
  16. package/dist/cjs/types/helpers/visitServerEndpoint.d.ts +23 -0
  17. package/dist/cjs/types/index.d.ts +10 -1
  18. package/dist/cjs/types/server/initServer.d.ts +21 -0
  19. package/dist/cjs/types/types/ParamType.d.ts +17 -0
  20. package/dist/cjs/types/types/ReactKitErrorCode.d.ts +3 -1
  21. package/dist/esm/index.js +776 -86
  22. package/dist/esm/index.js.map +1 -1
  23. package/dist/esm/types/components/AppWrapper.d.ts +25 -2
  24. package/dist/esm/types/components/ButtonInputGroup.d.ts +12 -0
  25. package/dist/esm/types/components/CheckboxButton.d.ts +20 -0
  26. package/dist/esm/types/components/RadioButton.d.ts +20 -0
  27. package/dist/esm/types/components/SimpleDateChooser.d.ts +21 -0
  28. package/dist/esm/types/components/TabBox.d.ts +1 -0
  29. package/dist/esm/types/helpers/genRouteHandler.d.ts +30 -0
  30. package/dist/esm/types/helpers/getOrdinal.d.ts +8 -0
  31. package/dist/esm/types/helpers/getTimeInfoInET.d.ts +17 -0
  32. package/dist/esm/types/helpers/handleError.d.ts +18 -0
  33. package/dist/esm/types/helpers/handleSuccess.d.ts +8 -0
  34. package/dist/esm/types/helpers/visitServerEndpoint.d.ts +23 -0
  35. package/dist/esm/types/index.d.ts +10 -1
  36. package/dist/esm/types/server/initServer.d.ts +21 -0
  37. package/dist/esm/types/types/ParamType.d.ts +17 -0
  38. package/dist/esm/types/types/ReactKitErrorCode.d.ts +3 -1
  39. package/dist/index.d.ts +182 -10
  40. package/package.json +13 -1
  41. package/rollup.config.js +0 -1
  42. package/src/components/AppWrapper.tsx +73 -15
  43. package/src/components/ButtonInputGroup.tsx +89 -0
  44. package/src/components/CheckboxButton.tsx +100 -0
  45. package/src/components/ErrorBox.tsx +4 -4
  46. package/src/components/LoadingSpinner.tsx +3 -3
  47. package/src/components/Modal.tsx +185 -71
  48. package/src/components/RadioButton.tsx +102 -0
  49. package/src/components/SimpleDateChooser.tsx +217 -0
  50. package/src/components/TabBox.tsx +65 -58
  51. package/src/helpers/genRouteHandler.ts +326 -0
  52. package/src/helpers/getOrdinal.tsx +13 -0
  53. package/src/helpers/getTimeInfoInET.tsx +56 -0
  54. package/src/helpers/handleError.ts +65 -0
  55. package/src/helpers/handleSuccess.ts +18 -0
  56. package/src/helpers/visitServerEndpoint.tsx +110 -0
  57. package/src/index.ts +21 -0
  58. package/src/server/initServer.ts +53 -0
  59. package/src/types/ParamType.ts +18 -0
  60. package/src/types/ReactKitErrorCode.tsx +3 -1
package/dist/index.d.ts CHANGED
@@ -7,11 +7,29 @@ import React from 'react';
7
7
  * @author Gabe Abrams
8
8
  */
9
9
 
10
- declare type Props$3 = {
10
+ declare type Props$6 = {
11
11
  children: React.ReactNode;
12
+ sendRequest: SendRequestFunction;
12
13
  dark?: boolean;
13
14
  sessionExpiredMessage?: string;
14
15
  };
16
+ declare type SendRequestFunction = (opts: {
17
+ path: string;
18
+ method: ('GET' | 'POST' | 'DELETE' | 'PUT');
19
+ params?: {
20
+ [x: string]: any;
21
+ } | undefined;
22
+ headers?: {
23
+ [x: string]: any;
24
+ } | undefined;
25
+ numRetries?: number | undefined;
26
+ }) => Promise<{
27
+ body: any;
28
+ status: number;
29
+ headers: {
30
+ [x: string]: any;
31
+ };
32
+ }>;
15
33
  /**
16
34
  * Show an alert modal with an "Okay" button
17
35
  * @author Gabe Abrams
@@ -34,7 +52,7 @@ declare const confirm: (title: string, text: string) => Promise<boolean>;
34
52
  * @param [errorTitle] title of the error box
35
53
  */
36
54
  declare const showFatalError: (error: any, errorTitle?: string) => undefined;
37
- declare const AppWrapper: React.FC<Props$3>;
55
+ declare const AppWrapper: React.FC<Props$6>;
38
56
 
39
57
  /**
40
58
  * Loading spinner/indicator
@@ -47,12 +65,12 @@ declare const LoadingSpinner: () => JSX.Element;
47
65
  * @author Gabe Abrams
48
66
  */
49
67
 
50
- declare type Props$2 = {
68
+ declare type Props$5 = {
51
69
  error: any;
52
70
  title?: string;
53
71
  onClose?: () => void;
54
72
  };
55
- declare const ErrorBox: React.FC<Props$2>;
73
+ declare const ErrorBox: React.FC<Props$5>;
56
74
 
57
75
  /**
58
76
  * Bootstrap variants
@@ -118,7 +136,7 @@ declare enum ModalType {
118
136
  * @author Gabe Abrams
119
137
  */
120
138
 
121
- declare type Props$1 = {
139
+ declare type Props$4 = {
122
140
  type?: ModalType;
123
141
  size?: ModalSize;
124
142
  title?: React.ReactNode;
@@ -147,18 +165,69 @@ declare type Props$1 = {
147
165
  confirmVariant?: Variant;
148
166
  onTopOfOtherModals?: boolean;
149
167
  };
150
- declare const Modal: React.FC<Props$1>;
168
+ declare const Modal: React.FC<Props$4>;
151
169
 
152
170
  /**
153
171
  * A box with a tab on the top that holds buttons and other content
154
172
  * @author Gabe Abrams
155
173
  */
156
174
 
157
- declare type Props = {
175
+ declare type Props$3 = {
158
176
  title: React.ReactNode;
159
177
  children: React.ReactNode;
178
+ noBottomPadding?: boolean;
179
+ };
180
+ declare const TabBox: React.FC<Props$3>;
181
+
182
+ /**
183
+ * A radio selection button
184
+ * @author Gabe Abrams
185
+ */
186
+
187
+ declare type Props$2 = {
188
+ text: string;
189
+ onSelected: () => void;
190
+ ariaLabel: string;
191
+ title?: string;
192
+ selected?: boolean;
193
+ id?: string;
194
+ noMarginOnRight?: boolean;
195
+ selectedVariant?: Variant;
196
+ unselectedVariant?: Variant;
197
+ small?: boolean;
198
+ };
199
+ declare const RadioButton: React.FC<Props$2>;
200
+
201
+ /**
202
+ * A checkbox button
203
+ * @author Gabe Abrams
204
+ */
205
+
206
+ declare type Props$1 = {
207
+ text: string;
208
+ onChanged: (checked: boolean) => void;
209
+ ariaLabel: string;
210
+ title?: string;
211
+ checked?: boolean;
212
+ id?: string;
213
+ noMarginOnRight?: boolean;
214
+ checkedVariant?: Variant;
215
+ uncheckedVariant?: Variant;
216
+ small?: boolean;
160
217
  };
161
- declare const TabBox: React.FC<Props>;
218
+ declare const CheckboxButton: React.FC<Props$1>;
219
+
220
+ /**
221
+ * Input group with a title and space for buttons
222
+ * @author Gabe Abrams
223
+ */
224
+
225
+ declare type Props = {
226
+ label: string;
227
+ minLabelWidth?: string;
228
+ children: React.ReactNode;
229
+ };
230
+ declare const ButtonInputGroup: React.FC<Props>;
162
231
 
163
232
  /**
164
233
  * An error with a code
@@ -258,6 +327,107 @@ declare const sum: (nums: number[]) => number;
258
327
  */
259
328
  declare const waitMs: (ms?: number) => Promise<unknown>;
260
329
 
330
+ /**
331
+ * Visit an endpoint on the server [for client only]
332
+ * @author Gabe Abrams
333
+ * @param opts object containing all arguments
334
+ * @param opts.path - the path of the server endpoint
335
+ * @param [opts.method=GET] - the method of the endpoint
336
+ * @param [opts.params] - query/body parameters to include
337
+ * @returns response from server
338
+ */
339
+ declare const visitServerEndpoint: (opts: {
340
+ path: string;
341
+ method?: "GET" | "POST" | "DELETE" | "PUT" | undefined;
342
+ params?: {
343
+ [x: string]: any;
344
+ } | undefined;
345
+ }) => Promise<any>;
346
+
347
+ /**
348
+ * Server-side API param types
349
+ * @author Gabe Abrams
350
+ */
351
+ declare enum ParamType {
352
+ Boolean = "boolean",
353
+ BooleanOptional = "boolean-optional",
354
+ Float = "float",
355
+ FloatOptional = "float-optional",
356
+ Int = "int",
357
+ IntOptional = "int-optional",
358
+ JSON = "json",
359
+ JSONOptional = "json-optional",
360
+ String = "string",
361
+ StringOptional = "string-optional"
362
+ }
363
+
364
+ /**
365
+ * Handle an error and respond to the client
366
+ * @author Gabe Abrams
367
+ * @param res express response
368
+ * @param error error info
369
+ * @param opts.err the error to send to the client
370
+ * or the error message
371
+ * @param [opts.code] an error code (only used if err.code is not
372
+ * included)
373
+ * @param [opts.status=500] the https status code to use
374
+ * defined)
375
+ */
376
+ declare const handleError: (res: any, error: ({
377
+ message: any;
378
+ code?: string;
379
+ status?: number;
380
+ } | Error | string | any)) => undefined;
381
+
382
+ /**
383
+ * Send successful API response
384
+ * @author Gabe Abrams
385
+ * @param res express response
386
+ * @param body the body of the response to send to the client
387
+ */
388
+ declare const handleSuccess: (res: any, body: any) => undefined;
389
+
390
+ /**
391
+ * Generate an express API route handler
392
+ * @author Gabe Abrams
393
+ * @param opts object containing all arguments
394
+ * @param opts.paramTypes map containing the types for each parameter that is
395
+ * included in the request (map: param name => type)
396
+ * @param opts.handler function that processes the request
397
+ * @returns express route handler that takes the following arguments:
398
+ * params (map: param name => value), handleSuccess (function for handling
399
+ * successful requests), handleError (function for handling failed requests),
400
+ * req (express request object), res (express response object)
401
+ */
402
+ declare const genRouteHandler: (opts: {
403
+ paramTypes?: {
404
+ [k: string]: ParamType;
405
+ } | undefined;
406
+ handler: (opts: {
407
+ params: {
408
+ [k: string]: any;
409
+ };
410
+ handleSuccess: (body: any) => void;
411
+ handleError: (error: any) => void;
412
+ req: any;
413
+ res: any;
414
+ }) => void;
415
+ }) => (req: any, res: any) => Promise<undefined>;
416
+
417
+ declare type GetLaunchInfoFunction = (req: any) => {
418
+ launched: boolean;
419
+ launchInfo?: any;
420
+ };
421
+ /**
422
+ * Prepare dce-reactkit to run on the server
423
+ * @author Gabe Abrams
424
+ * @param opts object containing all arguments
425
+ * @param opts.getLaunchInfo CACCL LTI's get launch info function
426
+ */
427
+ declare const initServer: (opts: {
428
+ getLaunchInfo: GetLaunchInfoFunction;
429
+ }) => void;
430
+
261
431
  /**
262
432
  * List of error codes built into the react kit
263
433
  * @author Gabe Abrams
@@ -268,7 +438,9 @@ declare enum ReactKitErrorCode {
268
438
  SessionExpired = "DRK3",
269
439
  MissingParameter = "DRK4",
270
440
  InvalidParameter = "DRK5",
271
- WrongCourse = "DRK6"
441
+ WrongCourse = "DRK6",
442
+ NoCACCLSendRequestFunction = "DRK7",
443
+ NoCACCLGetLaunchInfoFunction = "DRK8"
272
444
  }
273
445
 
274
- export { AppWrapper, ErrorBox, ErrorWithCode, LoadingSpinner, Modal, ModalButtonType, ModalSize, ModalType, ReactKitErrorCode, TabBox, Variant, abbreviate, alert, avg, ceilToNumDecimals, confirm, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, showFatalError, sum, waitMs };
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dce-reactkit",
3
- "version": "3.0.0-beta.4",
3
+ "version": "3.0.0-beta.40",
4
4
  "description": "Shared components for Harvard DCE apps",
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -19,15 +19,27 @@
19
19
  },
20
20
  "homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
21
21
  "peerDependencies": {
22
+ "@fortawesome/free-regular-svg-icons": "^6.1.1",
22
23
  "@fortawesome/free-solid-svg-icons": "^6.1.1",
23
24
  "@fortawesome/react-fontawesome": "^0.1.18",
25
+ "bootstrap": "^5.1.3",
24
26
  "react": "^18.0.0"
25
27
  },
26
28
  "devDependencies": {
27
29
  "@rollup/plugin-commonjs": "^22.0.0",
30
+ "@rollup/plugin-json": "^4.1.0",
28
31
  "@rollup/plugin-node-resolve": "^13.2.1",
29
32
  "@rollup/plugin-typescript": "^8.3.2",
30
33
  "@types/react": "^18.0.7",
34
+ "@typescript-eslint/eslint-plugin": "^5.21.0",
35
+ "@typescript-eslint/parser": "^5.21.0",
36
+ "eslint": "^8.14.0",
37
+ "eslint-config-airbnb": "^19.0.4",
38
+ "eslint-config-airbnb-typescript": "^17.0.0",
39
+ "eslint-plugin-import": "^2.26.0",
40
+ "eslint-plugin-jsx-a11y": "^6.5.1",
41
+ "eslint-plugin-react": "^7.29.4",
42
+ "eslint-plugin-react-hooks": "^4.4.0",
31
43
  "rollup": "^2.70.2",
32
44
  "rollup-plugin-dts": "^4.2.1",
33
45
  "rollup-plugin-sourcemaps": "^0.6.3",
package/rollup.config.js CHANGED
@@ -4,7 +4,6 @@ import typescript from "@rollup/plugin-typescript";
4
4
  import dts from "rollup-plugin-dts";
5
5
  import sourcemaps from 'rollup-plugin-sourcemaps';
6
6
 
7
-
8
7
  const packageJson = require("./package.json");
9
8
 
10
9
  export default [
@@ -7,10 +7,6 @@
7
7
  // Import React
8
8
  import React, { useState } from 'react';
9
9
 
10
- // Import bootstrap stylesheet and javascript
11
- import 'bootstrap/dist/css/bootstrap.min.css';
12
- import 'bootstrap/dist/js/bootstrap.min';
13
-
14
10
  // Import shared components
15
11
  import ErrorBox from './ErrorBox';
16
12
 
@@ -32,22 +28,70 @@ import ErrorWithCode from '../errors/ErrorWithCode';
32
28
  type Props = {
33
29
  // The entire app
34
30
  children: React.ReactNode,
31
+ // Copy of CACCL's send request function
32
+ sendRequest: SendRequestFunction,
35
33
  // True if this app is a dark-themed app
36
34
  dark?: boolean,
37
35
  // Custom session expired message
38
36
  sessionExpiredMessage?: string,
39
37
  };
40
38
 
39
+ // Type of CACCL's send request function
40
+ type SendRequestFunction = (
41
+ opts: {
42
+ path: string;
43
+ method: ('GET' | 'POST' | 'DELETE' | 'PUT');
44
+ params?: {
45
+ [x: string]: any;
46
+ } | undefined;
47
+ headers?: {
48
+ [x: string]: any;
49
+ } | undefined;
50
+ numRetries?: number | undefined;
51
+ },
52
+ ) => Promise<{
53
+ body: any;
54
+ status: number;
55
+ headers: {
56
+ [x: string]: any;
57
+ };
58
+ }>;
59
+
41
60
  /*------------------------------------------------------------------------*/
42
61
  /* Static Helpers */
43
62
  /*------------------------------------------------------------------------*/
44
63
 
64
+ /*----------------------------------------*/
65
+ /* Send Request */
66
+ /*----------------------------------------*/
67
+
68
+ // Store copy of caccl send request
69
+ let _cacclSendRequest: SendRequestFunction;
70
+
71
+ /**
72
+ * Send a request using caccl's send request feature
73
+ * @author Gabe Abrams
74
+ * @param opts send request options
75
+ * @returns send request response
76
+ */
77
+ export const cacclSendRequest: SendRequestFunction = async (opts) => {
78
+ // Make sure send request has been passed in
79
+ if (!_cacclSendRequest) {
80
+ throw new ErrorWithCode(
81
+ 'The request could not be sent because the AppWrapper component does not have a copy of sendRequest from CACCL.',
82
+ ReactKitErrorCode.NoCACCLSendRequestFunction,
83
+ );
84
+ }
85
+
86
+ return _cacclSendRequest(opts);
87
+ };
88
+
45
89
  /*----------------------------------------*/
46
90
  /* Alert */
47
91
  /*----------------------------------------*/
48
92
 
49
93
  // Stored copies of setters
50
- let setAlertInfo: (info: { title: string, text: string }) => void;
94
+ let setAlertInfo: (info: undefined | { title: string, text: string }) => void;
51
95
  let onAlertClosed: () => void;
52
96
 
53
97
  /**
@@ -83,7 +127,7 @@ export const alert = async (title: string, text: string): Promise<undefined> =>
83
127
  /*----------------------------------------*/
84
128
 
85
129
  // Stored copies of setters
86
- let setConfirmInfo: (info: { title: string, text: string }) => void;
130
+ let setConfirmInfo: (info: undefined | { title: string, text: string }) => void;
87
131
  let onConfirmClosed: (confirmed: boolean) => void;
88
132
 
89
133
  /**
@@ -194,10 +238,14 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
194
238
 
195
239
  const {
196
240
  children,
241
+ sendRequest,
197
242
  dark,
198
243
  sessionExpiredMessage = 'Your session has expired. Please go back to Canvas and start over.',
199
244
  } = props;
200
245
 
246
+ // Store copy of send request
247
+ _cacclSendRequest = sendRequest;
248
+
201
249
  /* -------------- State ------------- */
202
250
 
203
251
  // Fatal error
@@ -221,20 +269,26 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
221
269
  const [
222
270
  alertInfo,
223
271
  setAlertInfoInner,
224
- ] = useState<{
225
- title: string,
226
- text: string
227
- }>();
272
+ ] = useState<
273
+ undefined
274
+ | {
275
+ title: string,
276
+ text: string
277
+ }
278
+ >(undefined);
228
279
  setAlertInfo = setAlertInfoInner;
229
280
 
230
281
  // Confirm
231
282
  const [
232
283
  confirmInfo,
233
284
  setConfirmInfoInner,
234
- ] = useState<{
235
- title: string,
236
- text: string
237
- }>();
285
+ ] = useState<
286
+ undefined
287
+ | {
288
+ title: string,
289
+ text: string
290
+ }
291
+ >(undefined);
238
292
  setConfirmInfo = setConfirmInfoInner;
239
293
 
240
294
  // Session expired
@@ -260,10 +314,12 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
260
314
  if (alertInfo) {
261
315
  modal = (
262
316
  <Modal
317
+ key={`alert-${alertInfo.title}-${alertInfo.text}`}
263
318
  title={alertInfo.title}
264
319
  type={ModalType.Okay}
265
320
  onClose={() => {
266
321
  // Alert closed
322
+ setAlertInfo(undefined);
267
323
  if (onAlertClosed) {
268
324
  onAlertClosed();
269
325
  }
@@ -280,16 +336,18 @@ const AppWrapper: React.FC<Props> = (props: Props): React.ReactElement => {
280
336
  if (confirmInfo) {
281
337
  modal = (
282
338
  <Modal
339
+ key={`confirm-${confirmInfo.title}-${confirmInfo.text}`}
283
340
  title={confirmInfo.title}
284
341
  type={ModalType.OkayCancel}
285
342
  onClose={(buttonType) => {
343
+ setConfirmInfo(undefined);
286
344
  if (onConfirmClosed) {
287
345
  onConfirmClosed(buttonType === ModalButtonType.Okay);
288
346
  }
289
347
  }}
290
348
  dontAllowBackdropExit
291
349
  >
292
-
350
+ {confirmInfo.text}
293
351
  </Modal>
294
352
  );
295
353
  }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Input group with a title and space for buttons
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ /*------------------------------------------------------------------------*/
10
+ /* Types */
11
+ /*------------------------------------------------------------------------*/
12
+
13
+ // Props definition
14
+ type Props = {
15
+ // Label text
16
+ label: string,
17
+ // Minimum width of the label (css units)
18
+ minLabelWidth?: string,
19
+ // Buttons
20
+ children: React.ReactNode,
21
+ };
22
+
23
+ /*------------------------------------------------------------------------*/
24
+ /* Component */
25
+ /*------------------------------------------------------------------------*/
26
+
27
+ const ButtonInputGroup: React.FC<Props> = (props) => {
28
+ /*------------------------------------------------------------------------*/
29
+ /* Setup */
30
+ /*------------------------------------------------------------------------*/
31
+
32
+ /* -------------- Props ------------- */
33
+
34
+ // Destructure all props
35
+ const {
36
+ label,
37
+ minLabelWidth,
38
+ children,
39
+ } = props;
40
+
41
+ /*------------------------------------------------------------------------*/
42
+ /* Render */
43
+ /*------------------------------------------------------------------------*/
44
+
45
+ /*----------------------------------------*/
46
+ /* Main UI */
47
+ /*----------------------------------------*/
48
+
49
+ return (
50
+ <div className="input-group">
51
+ <div className="input-group-prepend d-flex w-100">
52
+ {/* Label */}
53
+ <span
54
+ className="input-group-text"
55
+ style={{
56
+ minWidth: (
57
+ minLabelWidth
58
+ ?? undefined
59
+ ),
60
+ borderTopRightRadius: 0,
61
+ borderBottomRightRadius: 0,
62
+ }}
63
+ >
64
+ {label}
65
+ </span>
66
+
67
+ {/* Contents */}
68
+ <span
69
+ className="input-group-text flex-grow-1 rounded-right"
70
+ style={{
71
+ backgroundColor: 'white',
72
+ borderTopLeftRadius: 0,
73
+ borderBottomLeftRadius: 0,
74
+ borderLeftWidth: 0,
75
+ }}
76
+ >
77
+ {children}
78
+ </span>
79
+ </div>
80
+ </div>
81
+ );
82
+ };
83
+
84
+ /*------------------------------------------------------------------------*/
85
+ /* Wrap Up */
86
+ /*------------------------------------------------------------------------*/
87
+
88
+ // Export component
89
+ export default ButtonInputGroup;
@@ -0,0 +1,100 @@
1
+ /**
2
+ * A checkbox button
3
+ * @author Gabe Abrams
4
+ */
5
+
6
+ // Import React
7
+ import React from 'react';
8
+
9
+ // Import FontAwesome Icons
10
+ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
11
+ import { faCheckSquare } from '@fortawesome/free-solid-svg-icons';
12
+ import { faSquare } from '@fortawesome/free-regular-svg-icons';
13
+
14
+ // Import shared types
15
+ import Variant from '../types/Variant';
16
+
17
+ /*------------------------------------------------------------------------*/
18
+ /* Types */
19
+ /*------------------------------------------------------------------------*/
20
+
21
+ type Props = {
22
+ // Text of the button
23
+ text: string,
24
+ // Handler for when this item is toggled
25
+ onChanged: (checked: boolean) => void,
26
+ // Aria label
27
+ ariaLabel: string,
28
+ // Button title
29
+ title?: string,
30
+ // True if checked
31
+ checked?: boolean,
32
+ // The id of the button
33
+ id?: string,
34
+ // If true, no margin on right
35
+ noMarginOnRight?: boolean,
36
+ // Variant for when checkbox is checked
37
+ checkedVariant?: Variant,
38
+ // Variant for when checkbox is not checked
39
+ uncheckedVariant?: Variant,
40
+ // True if using a small button
41
+ small?: boolean,
42
+ };
43
+
44
+ /*------------------------------------------------------------------------*/
45
+ /* Component */
46
+ /*------------------------------------------------------------------------*/
47
+
48
+ const CheckboxButton: React.FC<Props> = (props) => {
49
+ /*------------------------------------------------------------------------*/
50
+ /* Setup */
51
+ /*------------------------------------------------------------------------*/
52
+
53
+ /* -------------- Props ------------- */
54
+
55
+ const {
56
+ text,
57
+ onChanged,
58
+ ariaLabel,
59
+ title,
60
+ checked,
61
+ id,
62
+ noMarginOnRight,
63
+ checkedVariant = Variant.Secondary,
64
+ uncheckedVariant = Variant.Light,
65
+ small,
66
+ } = props;
67
+
68
+ /*------------------------------------------------------------------------*/
69
+ /* Render */
70
+ /*------------------------------------------------------------------------*/
71
+
72
+ /*----------------------------------------*/
73
+ /* Main UI */
74
+ /*----------------------------------------*/
75
+
76
+ return (
77
+ <button
78
+ type="button"
79
+ id={id}
80
+ title={title}
81
+ className={`btn btn-${checked ? checkedVariant : uncheckedVariant}${checked ? ' selected' : ''}${small ? ' btn-sm' : ''} m-0${noMarginOnRight ? '' : ' me-1'}`}
82
+ aria-label={`${ariaLabel}${checked ? ': currently checked' : ''}`}
83
+ onClick={() => {
84
+ onChanged(!checked);
85
+ }}
86
+ >
87
+ <FontAwesomeIcon
88
+ icon={checked ? faCheckSquare : faSquare}
89
+ className="me-1"
90
+ />
91
+ {text}
92
+ </button>
93
+ );
94
+ };
95
+
96
+ /*------------------------------------------------------------------------*/
97
+ /* Wrap up */
98
+ /*------------------------------------------------------------------------*/
99
+
100
+ export default CheckboxButton;
@@ -63,9 +63,9 @@ const ErrorBox: React.FC<Props> = (props) => {
63
63
  <span
64
64
  style={{
65
65
  backgroundColor: 'white',
66
- borderRadius: '5px',
67
- paddingLeft: '3px',
68
- paddingRight: '3px',
66
+ borderRadius: '0.3rem',
67
+ paddingLeft: '0.2rem',
68
+ paddingRight: '0.2rem',
69
69
  color: '#DC4150',
70
70
  fontVariant: 'small-caps',
71
71
  fontSize: '80%',
@@ -85,7 +85,7 @@ const ErrorBox: React.FC<Props> = (props) => {
85
85
  <div
86
86
  className="alert alert-danger text-center"
87
87
  style={{
88
- maxWidth: '650px',
88
+ maxWidth: '40rem',
89
89
  margin: 'auto',
90
90
  }}
91
91
  >
@@ -20,10 +20,10 @@ const style = `
20
20
  .LoadingSpinner-blip-2,
21
21
  .LoadingSpinner-blip-3,
22
22
  .LoadingSpinner-blip-4 {
23
- font-size: 25px;
23
+ font-size: 1.8rem;
24
24
  opacity: 0.6;
25
- margin-top: 20px;
26
- margin-bottom: 20px;
25
+ margin-top: 1rem;
26
+ margin-bottom: 1rem;
27
27
  }
28
28
 
29
29
  /* First Blip */