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

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 +944 -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 +22 -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 +13 -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 +933 -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 +22 -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 +13 -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 +229 -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 +222 -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 +27 -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$7 = {
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$7>;
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$6 = {
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$6>;
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$5 = {
122
140
  type?: ModalType;
123
141
  size?: ModalSize;
124
142
  title?: React.ReactNode;
@@ -147,18 +165,91 @@ 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$5>;
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$4 = {
158
176
  title: React.ReactNode;
159
177
  children: React.ReactNode;
178
+ noBottomPadding?: boolean;
179
+ };
180
+ declare const TabBox: React.FC<Props$4>;
181
+
182
+ /**
183
+ * A radio selection button
184
+ * @author Gabe Abrams
185
+ */
186
+
187
+ declare type Props$3 = {
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$3>;
200
+
201
+ /**
202
+ * A checkbox button
203
+ * @author Gabe Abrams
204
+ */
205
+
206
+ declare type Props$2 = {
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;
217
+ };
218
+ declare const CheckboxButton: React.FC<Props$2>;
219
+
220
+ /**
221
+ * Input group with a title and space for buttons
222
+ * @author Gabe Abrams
223
+ */
224
+
225
+ declare type Props$1 = {
226
+ label: string;
227
+ minLabelWidth?: string;
228
+ children: React.ReactNode;
229
+ };
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
+ ariaLabel: string;
239
+ name: string;
240
+ month: number;
241
+ day: number;
242
+ year: number;
243
+ /**
244
+ * Handler for when date changes
245
+ * @param month new 1-indexed month number
246
+ * @param day new 1-indexed day number
247
+ * @param year new full year number
248
+ */
249
+ onChange: (month: number, day: number, year: number) => void;
250
+ numMonthsToShow?: number;
160
251
  };
161
- declare const TabBox: React.FC<Props>;
252
+ declare const SimpleDateChooser: React.FC<Props>;
162
253
 
163
254
  /**
164
255
  * An error with a code
@@ -258,6 +349,132 @@ declare const sum: (nums: number[]) => number;
258
349
  */
259
350
  declare const waitMs: (ms?: number) => Promise<unknown>;
260
351
 
352
+ /**
353
+ * Visit an endpoint on the server [for client only]
354
+ * @author Gabe Abrams
355
+ * @param opts object containing all arguments
356
+ * @param opts.path - the path of the server endpoint
357
+ * @param [opts.method=GET] - the method of the endpoint
358
+ * @param [opts.params] - query/body parameters to include
359
+ * @returns response from server
360
+ */
361
+ declare const visitServerEndpoint: (opts: {
362
+ path: string;
363
+ method?: "GET" | "POST" | "DELETE" | "PUT" | undefined;
364
+ params?: {
365
+ [x: string]: any;
366
+ } | undefined;
367
+ }) => Promise<any>;
368
+
369
+ /**
370
+ * Server-side API param types
371
+ * @author Gabe Abrams
372
+ */
373
+ declare enum ParamType {
374
+ Boolean = "boolean",
375
+ BooleanOptional = "boolean-optional",
376
+ Float = "float",
377
+ FloatOptional = "float-optional",
378
+ Int = "int",
379
+ IntOptional = "int-optional",
380
+ JSON = "json",
381
+ JSONOptional = "json-optional",
382
+ String = "string",
383
+ StringOptional = "string-optional"
384
+ }
385
+
386
+ /**
387
+ * Handle an error and respond to the client
388
+ * @author Gabe Abrams
389
+ * @param res express response
390
+ * @param error error info
391
+ * @param opts.err the error to send to the client
392
+ * or the error message
393
+ * @param [opts.code] an error code (only used if err.code is not
394
+ * included)
395
+ * @param [opts.status=500] the https status code to use
396
+ * defined)
397
+ */
398
+ declare const handleError: (res: any, error: ({
399
+ message: any;
400
+ code?: string;
401
+ status?: number;
402
+ } | Error | string | any)) => undefined;
403
+
404
+ /**
405
+ * Send successful API response
406
+ * @author Gabe Abrams
407
+ * @param res express response
408
+ * @param body the body of the response to send to the client
409
+ */
410
+ declare const handleSuccess: (res: any, body: any) => undefined;
411
+
412
+ /**
413
+ * Generate an express API route handler
414
+ * @author Gabe Abrams
415
+ * @param opts object containing all arguments
416
+ * @param opts.paramTypes map containing the types for each parameter that is
417
+ * included in the request (map: param name => type)
418
+ * @param opts.handler function that processes the request
419
+ * @returns express route handler that takes the following arguments:
420
+ * params (map: param name => value), handleSuccess (function for handling
421
+ * successful requests), handleError (function for handling failed requests),
422
+ * req (express request object), res (express response object)
423
+ */
424
+ declare const genRouteHandler: (opts: {
425
+ paramTypes?: {
426
+ [k: string]: ParamType;
427
+ } | undefined;
428
+ handler: (opts: {
429
+ params: {
430
+ [k: string]: any;
431
+ };
432
+ handleSuccess: (body: any) => void;
433
+ handleError: (error: any) => void;
434
+ req: any;
435
+ res: any;
436
+ }) => void;
437
+ }) => (req: any, res: any) => Promise<undefined>;
438
+
439
+ declare type GetLaunchInfoFunction = (req: any) => {
440
+ launched: boolean;
441
+ launchInfo?: any;
442
+ };
443
+ /**
444
+ * Prepare dce-reactkit to run on the server
445
+ * @author Gabe Abrams
446
+ * @param opts object containing all arguments
447
+ * @param opts.getLaunchInfo CACCL LTI's get launch info function
448
+ */
449
+ declare const initServer: (opts: {
450
+ getLaunchInfo: GetLaunchInfoFunction;
451
+ }) => void;
452
+
453
+ /**
454
+ * Get a number's ordinal
455
+ * @author Gabe Abrams
456
+ * @param num the number being analyzed
457
+ * @returns ordinal
458
+ */
459
+ declare const getOrdinal: (num: number) => string;
460
+
461
+ /**
462
+ * Get current time info in US Boston Eastern Time, independent of machine
463
+ * timezone
464
+ * @author Gabe Abrams
465
+ * @param {Date} [date=now] the date to get info on
466
+ * @returns object with timestamp (ms since epoch) and numbers
467
+ * corresponding to ET time values for year, month, day, hour, minute
468
+ */
469
+ declare const getTimeInfoInET: (date?: Date | undefined) => {
470
+ timestamp: number;
471
+ year: number;
472
+ month: number;
473
+ day: number;
474
+ hour: number;
475
+ minute: number;
476
+ };
477
+
261
478
  /**
262
479
  * List of error codes built into the react kit
263
480
  * @author Gabe Abrams
@@ -268,7 +485,9 @@ declare enum ReactKitErrorCode {
268
485
  SessionExpired = "DRK3",
269
486
  MissingParameter = "DRK4",
270
487
  InvalidParameter = "DRK5",
271
- WrongCourse = "DRK6"
488
+ WrongCourse = "DRK6",
489
+ NoCACCLSendRequestFunction = "DRK7",
490
+ NoCACCLGetLaunchInfoFunction = "DRK8"
272
491
  }
273
492
 
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 };
493
+ 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.4",
3
+ "version": "3.0.0-beta.42",
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;