@gpa-gemstone/react-forms 1.1.92 → 1.1.93

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.
@@ -2,7 +2,7 @@ interface IProps {
2
2
  /**
3
3
  * Callback function that will be called when a file is uploaded
4
4
  * */
5
- OnLoadHandler: (result: File) => void;
5
+ OnLoadHandler: (result: File) => Promise<any>;
6
6
  /**
7
7
  * Callback function that will be called when clear button is clicked
8
8
  * */
package/lib/FileUpload.js CHANGED
@@ -28,9 +28,12 @@ var FileUpload = function (props) {
28
28
  var _a = React.useState(null), fileName = _a[0], setFileName = _a[1];
29
29
  var _b = React.useState(null), fileSize = _b[0], setFileSize = _b[1];
30
30
  var _c = React.useState(false), isFileUpload = _c[0], setIsFileUploaded = _c[1];
31
+ var _d = React.useState('unintiated'), uploadStatus = _d[0], setUploadStatus = _d[1];
31
32
  var handleFileUpload = function (evt) {
32
- if (evt.target == null || evt.target.files == null || evt.target.files.length === 0)
33
+ if (evt.target == null || evt.target.files == null || evt.target.files.length === 0) {
34
+ setUploadStatus('unintiated');
33
35
  return;
36
+ }
34
37
  var file = evt.target.files[0];
35
38
  setMetaData(file);
36
39
  };
@@ -49,6 +52,7 @@ var FileUpload = function (props) {
49
52
  e.stopPropagation();
50
53
  };
51
54
  var handleOnClear = function () {
55
+ setUploadStatus('unintiated');
52
56
  setIsFileUploaded(false);
53
57
  setFileName(null);
54
58
  setFileSize(null);
@@ -57,31 +61,42 @@ var FileUpload = function (props) {
57
61
  var setMetaData = function (file) {
58
62
  setFileName(file.name);
59
63
  setFileSize(file.size);
60
- props.OnLoadHandler(file);
61
- setIsFileUploaded(true);
64
+ setUploadStatus('loading');
65
+ props.OnLoadHandler(file).then(function () {
66
+ setIsFileUploaded(true);
67
+ setUploadStatus('idle');
68
+ }).catch(function () {
69
+ setIsFileUploaded(false);
70
+ setUploadStatus('error');
71
+ });
62
72
  };
63
73
  return (React.createElement(React.Fragment, null,
64
74
  React.createElement("div", { className: 'row' },
65
75
  React.createElement("div", { className: 'col-auto mt-2 pl-0' },
66
76
  React.createElement("label", { style: { cursor: 'pointer' } },
67
77
  React.createElement(gpa_symbols_1.ReactIcons.ShareArrow, { Color: 'var(--info)' }),
68
- React.createElement("input", { type: "file", onChange: handleFileUpload, accept: props.FileTypeAttribute, style: { display: 'none' } }))),
78
+ React.createElement("input", { type: "file", accept: props.FileTypeAttribute, style: { display: 'none' }, onChange: handleFileUpload }))),
69
79
  React.createElement("div", { className: 'col-auto pl-0' },
70
80
  React.createElement("button", { className: 'btn', onClick: handleOnClear },
71
81
  React.createElement(gpa_symbols_1.ReactIcons.CircledX, { Color: 'red' })))),
72
- isFileUpload ?
73
- React.createElement(React.Fragment, null,
74
- React.createElement("div", { className: 'row align-items-center justify-content-center', style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } },
75
- React.createElement("div", { className: 'col-auto' },
76
- "File Name: ", fileName !== null && fileName !== void 0 ? fileName : ''),
77
- React.createElement("div", { className: 'col-auto' },
78
- "File Size: ",
79
- formatFileSize(fileSize))))
82
+ uploadStatus === 'error' ?
83
+ React.createElement("div", { className: "alert alert-danger fade show" }, "An error occured while uploading file.")
84
+ : null,
85
+ React.createElement("div", { className: 'row', onDragOver: handleDragOver, onDrop: handleDrop, style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } }, uploadStatus === 'loading' ?
86
+ React.createElement("div", { className: 'd-flex col-12 align-items-center justify-content-center' },
87
+ React.createElement(gpa_symbols_1.ReactIcons.SpiningIcon, { Size: 200 }))
80
88
  :
81
- React.createElement("div", { className: 'row', onDragOver: handleDragOver, onDrop: handleDrop, style: { border: '2px dashed var(--secondary)', borderRadius: '0.5em' } },
82
- React.createElement("div", { className: 'col-12 pt-3 pb-3 d-flex justify-content-center align-items-center' },
83
- React.createElement(gpa_symbols_1.ReactIcons.Image, { Size: 100 }),
84
- React.createElement("span", null, "Drag and Drop")))));
89
+ isFileUpload ?
90
+ React.createElement(React.Fragment, null,
91
+ React.createElement("div", { className: 'col-auto' },
92
+ "File Name: ", fileName !== null && fileName !== void 0 ? fileName : ''),
93
+ React.createElement("div", { className: 'col-auto' },
94
+ "File Size: ",
95
+ formatFileSize(fileSize)))
96
+ :
97
+ React.createElement("div", { className: 'col-12 pt-3 pb-3 d-flex justify-content-center align-items-center' },
98
+ React.createElement(gpa_symbols_1.ReactIcons.Image, { Size: 100 }),
99
+ React.createElement("span", null, "Drag and Drop")))));
85
100
  };
86
101
  //Helper Functions
87
102
  var formatFileSize = function (size) {
@@ -108,7 +108,7 @@ var MultiSelect = function (props) {
108
108
  ((_a = props.ItemTooltip) !== null && _a !== void 0 ? _a : 'no-tip') !== 'no-tip' ?
109
109
  React.createElement(ToolTip_1.default, { Show: showItems, Target: guid, Position: "bottom" },
110
110
  React.createElement("p", null, "Selected Options:"),
111
- selectedOptions.slice(0, 10).map(function (opt) { return React.createElement("p", null, opt.Label); }),
111
+ selectedOptions.slice(0, 10).map(function (opt, i) { return React.createElement("p", { key: i }, opt.Label); }),
112
112
  selectedOptions.length > 10 ? React.createElement("p", null, "and ".concat(selectedOptions.length - 10, " other(s)")) : null)
113
113
  : null,
114
114
  React.createElement("div", { ref: multiSelect, style: { position: 'relative', display: 'block', width: 'inherit' } },
@@ -4,6 +4,18 @@ export interface AbortablePromise<T> extends PromiseLike<T> {
4
4
  abort?: () => void;
5
5
  }
6
6
  interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
7
+ /**
8
+ * Function to determine the validity of a field
9
+ * @param field - Field of the record to check
10
+ * @returns {boolean}
11
+ */
12
+ Valid?: (field: keyof T) => boolean;
13
+ /**
14
+ * Feedback message to show when input is invalid
15
+ * @type {string}
16
+ * @optional
17
+ */
18
+ Feedback?: string;
7
19
  /**
8
20
  * Flag to allow custom input values
9
21
  * @type {boolean}
@@ -30,7 +42,7 @@ interface IProps<T> extends Gemstone.TSX.Interfaces.IBaseFormProps<T> {
30
42
  BtnStyle?: React.CSSProperties;
31
43
  GetLabel?: () => AbortablePromise<string>;
32
44
  /**
33
- * Flag to reset search text to an empty string when a user selects an option. Defaulting to false
45
+ * Flag to reset search text to an empty string when a user selects an option or when the element loses focus. Defaulting to false
34
46
  */
35
47
  ResetSearchOnSelect?: boolean;
36
48
  }
@@ -30,33 +30,35 @@ var getInitialSearchText = function (useBlankString, recordValue) {
30
30
  return useBlankString ? '' : recordValue;
31
31
  };
32
32
  function SearchableSelect(props) {
33
- var _a, _b;
34
- var _c = React.useState(function () { var _a, _b, _c; return getInitialSearchText((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false, (_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : ''); }), search = _c[0], setSearch = _c[1];
35
- var _d = React.useState((_b = (_a = props.Record[props.Field]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : ''), label = _d[0], setLabel = _d[1];
36
- var _e = React.useState([]), results = _e[0], setResults = _e[1];
37
- var _f = React.useState(false), loading = _f[0], setLoading = _f[1];
38
- React.useEffect(function () {
33
+ var _a = React.useState(function () { var _a, _b, _c; return getInitialSearchText((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false, (_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : ''); }), search = _a[0], setSearch = _a[1];
34
+ var _b = React.useState([]), searchOptions = _b[0], setSearchOptions = _b[1];
35
+ var _c = React.useState(false), loading = _c[0], setLoading = _c[1];
36
+ var setter = React.useCallback(function (record, selectedOption) {
37
+ handleSetSearch(selectedOption);
38
+ props.Setter(record);
39
+ }, [props.Setter, props.Field]);
40
+ var handleSetSearch = React.useCallback(function (selectedOption) {
39
41
  var _a, _b, _c;
40
- if (props.GetLabel === undefined) {
41
- if ((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false)
42
- setSearch('');
43
- else
44
- setLabel((_c = (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '');
42
+ if ((_a = props.ResetSearchOnSelect) !== null && _a !== void 0 ? _a : false) {
43
+ setSearch('');
44
+ return;
45
45
  }
46
- else {
47
- setLoading(true);
48
- var handle_1 = props.GetLabel();
49
- handle_1.then(function (lab) {
50
- setLabel(lab);
51
- setSearch(lab);
52
- setLoading(false);
53
- });
54
- return function () {
55
- if ((handle_1 === null || handle_1 === void 0 ? void 0 : handle_1.abort) != null)
56
- handle_1.abort();
57
- };
46
+ if (props.GetLabel === undefined) {
47
+ var newSearch = (_c = selectedOption !== null && selectedOption !== void 0 ? selectedOption : (_b = props.Record[props.Field]) === null || _b === void 0 ? void 0 : _b.toString()) !== null && _c !== void 0 ? _c : '';
48
+ setSearch(newSearch);
49
+ return;
58
50
  }
59
- }, [props.GetLabel, props.Record[props.Field], props.ResetSearchOnSelect]);
51
+ setLoading(true);
52
+ var handle = props.GetLabel();
53
+ handle.then(function (lab) {
54
+ setSearch(lab);
55
+ setLoading(false);
56
+ }, function () { return setLoading(false); });
57
+ }, [props.ResetSearchOnSelect, props.GetLabel, props.Record[props.Field]]);
58
+ //Effect to set search when props.Record[props.Field] changes externally
59
+ React.useEffect(function () {
60
+ handleSetSearch();
61
+ }, [props.Record[props.Field], handleSetSearch]);
60
62
  // Call props.Search every 500ms to avoid hammering the server while typing
61
63
  React.useEffect(function () {
62
64
  setLoading(true);
@@ -64,7 +66,7 @@ function SearchableSelect(props) {
64
66
  var timeoutHandle = setTimeout(function () {
65
67
  searchHandle = props.Search(search);
66
68
  searchHandle.then(function (d) {
67
- setResults(d.map(function (o) { return ({ Value: o.Value, Element: o.Label }); }));
69
+ setSearchOptions(d.map(function (o) { return ({ Value: o.Value, Element: o.Label }); }));
68
70
  setLoading(false);
69
71
  }, function () {
70
72
  setLoading(false);
@@ -77,38 +79,25 @@ function SearchableSelect(props) {
77
79
  clearTimeout(timeoutHandle);
78
80
  };
79
81
  }, [search]);
80
- var update = React.useCallback(function (record, selectedOption) {
81
- var _a, _b, _c;
82
- var stringVal = (_b = (_a = record[props.Field]) === null || _a === void 0 ? void 0 : _a.toString()) !== null && _b !== void 0 ? _b : '';
83
- var newLabel = stringVal;
84
- if (!React.isValidElement(selectedOption.Element))
85
- newLabel = selectedOption.Element;
86
- setLabel(newLabel);
87
- props.Setter(record);
88
- if ((_c = props.ResetSearchOnSelect) !== null && _c !== void 0 ? _c : false)
89
- setSearch('');
90
- else
91
- setSearch(newLabel);
92
- }, [props.Setter, props.Field, label]);
93
82
  var options = React.useMemo(function () {
94
- var _a, _b;
83
+ var _a, _b, _c, _d;
95
84
  var ops = [];
96
85
  ops.push({
97
86
  Value: props.Record[props.Field],
98
87
  Element: React.createElement("div", { className: 'input-group' },
99
- React.createElement("input", { type: "text", className: "form-control", value: search, onChange: function (d) { return setSearch(d.target.value); }, onBlur: function () { return setSearch(label); }, onClick: function (evt) { evt.preventDefault(); evt.stopPropagation(); }, disabled: (_a = props.Disabled) !== null && _a !== void 0 ? _a : false }),
88
+ React.createElement("input", { type: "text", className: "form-control ".concat(((_b = (_a = props.Valid) === null || _a === void 0 ? void 0 : _a.call(props, props.Field)) !== null && _b !== void 0 ? _b : true) ? '' : 'border-danger'), value: search, onChange: function (d) { return setSearch(d.target.value); }, onBlur: function () { return handleSetSearch(); }, onClick: function (evt) { evt.preventDefault(); evt.stopPropagation(); }, disabled: (_c = props.Disabled) !== null && _c !== void 0 ? _c : false }),
100
89
  loading ?
101
90
  React.createElement("div", { className: "input-group-append" },
102
91
  React.createElement("span", { className: "input-group-text" },
103
92
  React.createElement(gpa_symbols_1.ReactIcons.SpiningIcon, null)))
104
93
  : null)
105
94
  });
106
- if ((_b = props.AllowCustom) !== null && _b !== void 0 ? _b : false)
95
+ if ((_d = props.AllowCustom) !== null && _d !== void 0 ? _d : false)
107
96
  ops.push({ Value: search, Element: React.createElement(React.Fragment, null,
108
97
  search,
109
98
  " (Entered Value)") });
110
- ops.push.apply(ops, results.filter(function (f) { return f.Value !== search && f.Value !== props.Record[props.Field]; }));
99
+ ops.push.apply(ops, searchOptions.filter(function (f) { return f.Value !== search && f.Value !== props.Record[props.Field]; }));
111
100
  return ops;
112
- }, [search, props.Record[props.Field], results, props.Disabled, loading, label]);
113
- return React.createElement(StylableSelect_1.default, { Record: props.Record, Field: props.Field, Setter: update, Label: props.Label, Disabled: props.Disabled, Help: props.Help, Style: props.Style, Options: options, BtnStyle: props.BtnStyle });
101
+ }, [search, props.Record[props.Field], props.Field, searchOptions, props.Disabled, loading, props.Valid, handleSetSearch]);
102
+ return React.createElement(StylableSelect_1.default, { Record: props.Record, Field: props.Field, Setter: setter, Label: props.Label, Disabled: props.Disabled, Help: props.Help, Style: props.Style, Options: options, BtnStyle: props.BtnStyle, Valid: props.Valid, Feedback: props.Feedback });
114
103
  }
@@ -4,6 +4,18 @@ export interface IOption {
4
4
  Element: React.ReactElement<any> | string;
5
5
  }
6
6
  interface IProps<T> {
7
+ /**
8
+ * Function to determine the validity of a field
9
+ * @param field - Field of the record to check
10
+ * @returns {boolean}
11
+ */
12
+ Valid?: (field: keyof T) => boolean;
13
+ /**
14
+ * Feedback message to show when input is invalid
15
+ * @type {string}
16
+ * @optional
17
+ */
18
+ Feedback?: string;
7
19
  /**
8
20
  * Record to be used in form
9
21
  * @type {T}
@@ -42,17 +42,17 @@ var react_portal_1 = require("react-portal");
42
42
  var _ = require("lodash");
43
43
  var gpa_symbols_1 = require("@gpa-gemstone/gpa-symbols");
44
44
  function StylableSelect(props) {
45
- var _a;
45
+ var _a, _b, _c;
46
46
  // State hooks and ref for managing component state and interactions.
47
47
  var stylableSelect = React.useRef(null);
48
48
  var selectTable = React.useRef(null);
49
49
  var tableContainer = React.useRef(null);
50
- var _b = React.useState(false), show = _b[0], setShow = _b[1];
51
- var _c = React.useState(props.Options[0].Element), selected = _c[0], setSelected = _c[1];
52
- var _d = React.useState(""), guid = _d[0], setGuid = _d[1];
53
- var _e = React.useState(false), showHelp = _e[0], setShowHelp = _e[1];
54
- var _f = React.useState({ Top: 0, Left: 0, Width: 0, Height: 0 }), position = _f[0], setPosition = _f[1];
55
- React.useEffect(function () {
50
+ var _d = React.useState(false), show = _d[0], setShow = _d[1];
51
+ var _e = React.useState(props.Options[0].Element), selected = _e[0], setSelected = _e[1];
52
+ var _f = React.useState(""), guid = _f[0], setGuid = _f[1];
53
+ var _g = React.useState(false), showHelp = _g[0], setShowHelp = _g[1];
54
+ var _h = React.useState({ Top: 0, Left: 0, Width: 0, Height: 0 }), position = _h[0], setPosition = _h[1];
55
+ React.useLayoutEffect(function () {
56
56
  var updatePosition = _.debounce(function () {
57
57
  if (stylableSelect.current != null) {
58
58
  var rect = stylableSelect.current.getBoundingClientRect();
@@ -82,6 +82,16 @@ function StylableSelect(props) {
82
82
  // Ignore if disabled or not a mousedown event
83
83
  if ((props.Disabled === undefined ? false : props.Disabled) || evt.type !== 'mousedown' || stylableSelect.current == null)
84
84
  return;
85
+ // if we’re about to OPEN it, measure right now
86
+ if (!show && stylableSelect.current != null) {
87
+ var rect = stylableSelect.current.getBoundingClientRect();
88
+ setPosition({
89
+ Top: rect.bottom,
90
+ Left: rect.left,
91
+ Width: rect.width,
92
+ Height: rect.height
93
+ });
94
+ }
85
95
  //ignore the click if it was inside the table or table container
86
96
  if ((selectTable.current != null && selectTable.current.contains(evt.target)) || (tableContainer.current != null && tableContainer.current.contains(evt.target)))
87
97
  return;
@@ -131,8 +141,9 @@ function StylableSelect(props) {
131
141
  props.Help !== undefined ?
132
142
  React.createElement(ToolTip_1.default, { Show: showHelp, Target: guid, Class: "info", Position: "bottom" }, props.Help)
133
143
  : null,
134
- React.createElement("button", { type: "button", style: __assign({ border: '1px solid #ced4da', padding: '.375rem .75rem', fontSize: '1rem', borderRadius: '.25rem' }, ((_a = props.BtnStyle) !== null && _a !== void 0 ? _a : {})), className: "btn form-control dropdown-toggle", onClick: HandleShow, disabled: props.Disabled === undefined ? false : props.Disabled },
144
+ React.createElement("button", { type: "button", style: __assign({ padding: '.375rem .75rem' }, ((_a = props.BtnStyle) !== null && _a !== void 0 ? _a : {})), className: "dropdown-toggle form-control ".concat(((_c = (_b = props.Valid) === null || _b === void 0 ? void 0 : _b.call(props, props.Field)) !== null && _c !== void 0 ? _c : true) ? '' : 'is-invalid'), onClick: HandleShow, disabled: props.Disabled === undefined ? false : props.Disabled },
135
145
  React.createElement("div", { style: props.Style }, selected)),
146
+ React.createElement("div", { className: "invalid-feedback" }, props.Feedback == null ? props.Field.toString() + ' is a required field.' : props.Feedback),
136
147
  React.createElement(react_portal_1.Portal, null,
137
148
  React.createElement("div", { ref: tableContainer, className: 'popover', style: {
138
149
  maxHeight: window.innerHeight - position.Top,
@@ -45,7 +45,7 @@ function ToggleSwitch(props) {
45
45
  // Variables to control the rendering of label and help icon.
46
46
  var showHelpIcon = props.Help !== undefined;
47
47
  var label = props.Label === undefined ? props.Field : props.Label;
48
- return (React.createElement("div", { className: "custom-control custom-switch", style: props.Style },
48
+ return (React.createElement("div", { className: "custom-control custom-switch form-group", style: props.Style },
49
49
  React.createElement("input", { type: "checkbox", className: "custom-control-input", onChange: function (evt) {
50
50
  var record = __assign({}, props.Record);
51
51
  record[props.Field] = evt.target.checked;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gpa-gemstone/react-forms",
3
- "version": "1.1.92",
3
+ "version": "1.1.93",
4
4
  "description": "React Form modules for gpa webapps",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -45,9 +45,9 @@
45
45
  "typescript": "5.5.3"
46
46
  },
47
47
  "dependencies": {
48
- "@gpa-gemstone/application-typings": "0.0.86",
48
+ "@gpa-gemstone/application-typings": "0.0.87",
49
49
  "@gpa-gemstone/gpa-symbols": "0.0.52",
50
- "@gpa-gemstone/helper-functions": "0.0.43",
50
+ "@gpa-gemstone/helper-functions": "0.0.44",
51
51
  "@types/react": "^17.0.14",
52
52
  "@types/styled-components": "^5.1.11",
53
53
  "lodash": "^4.17.21",