@reykjavik/hanna-react 0.10.61 → 0.10.64

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/CHANGELOG.md CHANGED
@@ -4,6 +4,25 @@
4
4
 
5
5
  - ... <!-- Add new lines here. -->
6
6
 
7
+ ## 0.10.63 – 0.10.64
8
+
9
+ _2022-08-29_
10
+
11
+ - feat: Changes to `FileInput`
12
+ - feat: Add prop `FileList` to suppress (`false`) or customize its rendering
13
+ - feat: Add props `multiple`, `accept`
14
+ - feat: Deprecate prop `dropzoneProps`
15
+ - fix: report deleted when adding files in single-file mode
16
+ - fix: Make `dropZoneProps` optional, as originally indented
17
+ - fix: Hide `Carousel` mouse-cursor scroll controls at start/end positions
18
+ - fix: Pass `id` and other HTML props to static (span) `TagPill`s
19
+
20
+ ## 0.10.62
21
+
22
+ _2022-08-23_
23
+
24
+ - feat: Add `diff` object to `FileInput`'s `onFilesUpdated` callback signature
25
+
7
26
  ## 0.10.61
8
27
 
9
28
  _2022-08-11_
@@ -0,0 +1,36 @@
1
+ export declare type CustomFile = {
2
+ preview?: string;
3
+ } & File;
4
+ /**
5
+ * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
6
+ *
7
+ * The preview's value is either a data URI (for image-type files) or `undefined`
8
+ */
9
+ export declare const addPreview: (file: CustomFile) => void;
10
+ /**
11
+ * Revokes `preview` data URIs to avoid memory leaks
12
+ *
13
+ * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
14
+ */
15
+ export declare const releasePreview: (file: CustomFile) => void;
16
+ /**
17
+ * Small+stupid file size pretty-printer.
18
+ */
19
+ export declare const formatBytes: (bytes: number, decimals?: number) => string;
20
+ /**
21
+ * Figures out how to handle adding files to a FileInput
22
+ * Which files to retaine, which too delete, and
23
+ * what the updated fileList should look like.
24
+ *
25
+ *
26
+ */
27
+ export declare const getFileListUpdate: (oldFileList: Array<File>, added: Array<File>, replaceMode: boolean) => {
28
+ fileList: File[];
29
+ diff: {
30
+ added: File[];
31
+ deleted: File[];
32
+ } | {
33
+ added: File[];
34
+ deleted?: undefined;
35
+ };
36
+ };
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getFileListUpdate = exports.formatBytes = exports.releasePreview = exports.addPreview = void 0;
4
+ /**
5
+ * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
6
+ *
7
+ * The preview's value is either a data URI (for image-type files) or `undefined`
8
+ */
9
+ const addPreview = (file) => {
10
+ if (!('preview' in file)) {
11
+ file.preview = file.type.includes('image/') ? URL.createObjectURL(file) : undefined;
12
+ }
13
+ };
14
+ exports.addPreview = addPreview;
15
+ /**
16
+ * Revokes `preview` data URIs to avoid memory leaks
17
+ *
18
+ * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
19
+ */
20
+ const releasePreview = (file) => {
21
+ file.preview && URL.revokeObjectURL(file.preview);
22
+ delete file.preview;
23
+ };
24
+ exports.releasePreview = releasePreview;
25
+ /**
26
+ * Small+stupid file size pretty-printer.
27
+ */
28
+ const formatBytes = (bytes, decimals = 2) => {
29
+ if (bytes === 0) {
30
+ return '0 Bytes';
31
+ }
32
+ const k = 1024;
33
+ const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
34
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
35
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
36
+ };
37
+ exports.formatBytes = formatBytes;
38
+ /**
39
+ * Figures out how to handle adding files to a FileInput
40
+ * Which files to retaine, which too delete, and
41
+ * what the updated fileList should look like.
42
+ *
43
+ *
44
+ */
45
+ const getFileListUpdate = (oldFileList, added,
46
+ /**
47
+ * `replaceMode: true` is the default "single-file" input behavior.
48
+ *
49
+ * Pass `false` to this argument when the "multiple" prop is true.
50
+ */
51
+ replaceMode) => {
52
+ const deleted = replaceMode ? oldFileList : [];
53
+ const retained = [];
54
+ if (!replaceMode) {
55
+ oldFileList.forEach((oldFile) => {
56
+ if (added.find(({ name }) => name === oldFile.name)) {
57
+ deleted.push(oldFile);
58
+ }
59
+ else {
60
+ retained.push(oldFile);
61
+ }
62
+ });
63
+ }
64
+ return {
65
+ fileList: retained.concat(added),
66
+ diff: deleted.length ? { added, deleted } : { added },
67
+ };
68
+ };
69
+ exports.getFileListUpdate = getFileListUpdate;
@@ -0,0 +1,11 @@
1
+ import type { formatBytes } from './_FileInput.utils';
2
+ import { CustomFile } from './_FileInput.utils';
3
+ export declare type FileListProps = {
4
+ files: Array<CustomFile>;
5
+ showFileSize?: boolean;
6
+ showImagePreviews?: boolean;
7
+ removeFileText: string;
8
+ removeFile: (file: File | string) => void;
9
+ formatBytes: typeof formatBytes;
10
+ };
11
+ export declare const DefaultFileList: (props: FileListProps) => JSX.Element | null;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DefaultFileList = void 0;
4
+ const tslib_1 = require("tslib");
5
+ const react_1 = tslib_1.__importDefault(require("react"));
6
+ const DefaultFileList = (props) => {
7
+ const { files, showFileSize, showImagePreviews, removeFileText, removeFile, formatBytes, } = props;
8
+ if (!files.length) {
9
+ return null;
10
+ }
11
+ return (react_1.default.createElement("ul", { className: "FileInput__filelist" }, files.map((file) => (react_1.default.createElement("li", { key: file.name, className: "FileInput__file" },
12
+ react_1.default.createElement("button", { className: "FileInput__file-remove", type: "button", onClick: () => removeFile(file), "aria-label": removeFileText }, removeFileText),
13
+ react_1.default.createElement("span", { className: "FileInput__fileinfo" },
14
+ showImagePreviews && file.preview && (react_1.default.createElement(react_1.default.Fragment, null,
15
+ react_1.default.createElement("span", { className: "FileInput__preview" },
16
+ react_1.default.createElement("img", { src: file.preview })),
17
+ ' ')),
18
+ react_1.default.createElement("span", { className: "FileInput__filename" }, file.name),
19
+ showFileSize && (react_1.default.createElement("small", { className: "FileInput__filesize" },
20
+ " - (",
21
+ formatBytes(file.size),
22
+ ")"))))))));
23
+ };
24
+ exports.DefaultFileList = DefaultFileList;
package/FileInput.d.ts CHANGED
@@ -1,17 +1,48 @@
1
+ import { FileListProps } from './FileInput/_FileInputFileList';
1
2
  import { FormFieldWrappingProps } from './FormField';
2
- declare type dropzonePropsProps = {
3
- accept?: string;
4
- multiple?: boolean;
5
- };
6
3
  export declare type FileInputProps = {
7
- dropzoneProps: dropzonePropsProps;
4
+ /**
5
+ * Flags if the input should accept multiple, or just a single file at a time.
6
+ *
7
+ * Default: `true`
8
+ */
9
+ multiple?: boolean;
10
+ /**
11
+ * Accepted file mime type(s).
12
+ *
13
+ * Default: no restrictions.
14
+ */
15
+ accept?: string | Array<string>;
8
16
  dropzoneText: string | JSX.Element;
17
+ removeFileText: string;
9
18
  showFileSize?: boolean;
10
19
  showImagePreviews?: boolean;
11
- removeFileText: string;
12
- onFilesUpdated?: (files: Array<File>) => void;
20
+ FileList?: false | ((props: FileListProps) => JSX.Element | null);
21
+ onFilesUpdated?: (
22
+ /** Updated, full list of Files. */
23
+ files: Array<File>,
24
+ /** Information about which Files were added or removed during with this update.
25
+ *
26
+ * NOTE: When a diff contains both added and deleted files, this indicates a
27
+ * name-conflict occurred — i.e. one of the added files has a name that
28
+ * existed in the old file list.
29
+ * In such cases the deletion is more implicit than explicit, and depending
30
+ * on the circumstances, you MIGHT wish to either warn the user, rename
31
+ * one of the files, instead of overwriting/deleting the older file, etc.
32
+ */
33
+ diff: {
34
+ deleted?: Array<File>;
35
+ added?: Array<File>;
36
+ }) => void;
13
37
  name?: string;
14
38
  value?: ReadonlyArray<File>;
39
+ /**
40
+ * @deprecated Use props `multiple`, `accept` instead (Will be removed in v0.11)
41
+ */
42
+ dropzoneProps?: {
43
+ accept?: string;
44
+ multiple?: boolean;
45
+ };
15
46
  } & FormFieldWrappingProps;
16
47
  declare const FileInput: (props: FileInputProps) => JSX.Element;
17
48
  export default FileInput;
package/FileInput.js CHANGED
@@ -5,26 +5,9 @@ const react_1 = tslib_1.__importStar(require("react"));
5
5
  const react_dropzone_1 = require("react-dropzone"); // https://react-dropzone.js.org/#!/Dropzone
6
6
  const hooks_1 = require("@hugsmidjan/react/hooks");
7
7
  const getBemClass_1 = tslib_1.__importDefault(require("@hugsmidjan/react/utils/getBemClass"));
8
+ const _FileInput_utils_1 = require("./FileInput/_FileInput.utils");
9
+ const _FileInputFileList_1 = require("./FileInput/_FileInputFileList");
8
10
  const FormField_1 = tslib_1.__importDefault(require("./FormField"));
9
- /**
10
- * Attaches a `preview` prop to file objects that don't already have a `preview` key defined
11
- *
12
- * The preview's value is either a data URI (for image-type files) or `undefined`
13
- */
14
- const addPreview = (file) => {
15
- if (!('preview' in file)) {
16
- file.preview = file.type.includes('image/') ? URL.createObjectURL(file) : undefined;
17
- }
18
- };
19
- /**
20
- * Revokes `preview` data URIs to avoid memory leaks
21
- *
22
- * (See: https://developer.mozilla.org/en-US/docs/Web/API/URL/revokeObjectURL)
23
- */
24
- const releasePreview = (file) => {
25
- file.preview && URL.revokeObjectURL(file.preview);
26
- delete file.preview;
27
- };
28
11
  const arrayToFileList = (arr) => {
29
12
  const fileList = new DataTransfer();
30
13
  arr.forEach((item) => {
@@ -32,115 +15,97 @@ const arrayToFileList = (arr) => {
32
15
  });
33
16
  return fileList.files;
34
17
  };
35
- const dedupeFilesArray = (files) => {
36
- const newArray = [];
37
- const found = {};
38
- files.forEach((file) => {
39
- if (!(file.name in found)) {
40
- newArray.push(file);
41
- found[file.name] = true;
42
- }
43
- else {
44
- releasePreview(file);
45
- }
46
- });
47
- return newArray;
48
- };
49
- const formatBytes = (bytes, decimals = 2) => {
50
- if (bytes === 0) {
51
- return '0 Bytes';
52
- }
53
- const k = 1024;
54
- const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
55
- const i = Math.floor(Math.log(bytes) / Math.log(k));
56
- return parseFloat((bytes / Math.pow(k, i)).toFixed(decimals)) + ' ' + sizes[i];
57
- };
58
18
  const FileInput = (props) => {
59
- const { className, id, label, hideLabel, dropzoneProps = { multiple: true }, dropzoneText, removeFileText, assistText, disabled, invalid, errorMessage, required, reqText, onFilesUpdated = () => undefined, showFileSize, showImagePreviews, value = [] } = props, inputElementProps = tslib_1.__rest(props, ["className", "id", "label", "hideLabel", "dropzoneProps", "dropzoneText", "removeFileText", "assistText", "disabled", "invalid", "errorMessage", "required", "reqText", "onFilesUpdated", "showFileSize", "showImagePreviews", "value"]);
19
+ const { className, id, label, hideLabel, dropzoneProps = { multiple: true }, multiple = dropzoneProps.multiple, accept, dropzoneText, removeFileText, assistText, disabled, invalid, errorMessage, required, reqText, FileList = _FileInputFileList_1.DefaultFileList, onFilesUpdated = () => undefined, showFileSize, showImagePreviews, value = [] } = props, inputElementProps = tslib_1.__rest(props, ["className", "id", "label", "hideLabel", "dropzoneProps", "multiple", "accept", "dropzoneText", "removeFileText", "assistText", "disabled", "invalid", "errorMessage", "required", "reqText", "FileList", "onFilesUpdated", "showFileSize", "showImagePreviews", "value"]);
60
20
  const domid = (0, hooks_1.useDomid)(id);
61
21
  const fileInputWrapper = (0, react_1.useRef)(null);
62
22
  const fileInput = (0, react_1.useRef)(null);
63
23
  const files = value;
64
24
  const [isHover, setIsHover] = (0, react_1.useState)(false);
65
- const { getRootProps, getInputProps, isDragReject, inputRef } = (0, react_dropzone_1.useDropzone)(Object.assign({ onDrop: (acceptedFiles) => {
25
+ const { getRootProps, getInputProps, isDragReject, inputRef } = (0, react_dropzone_1.useDropzone)({
26
+ onDrop: (acceptedFiles) => {
66
27
  acceptedFiles = acceptedFiles.map((file) => {
67
- addPreview(file);
28
+ (0, _FileInput_utils_1.addPreview)(file);
68
29
  return file;
69
30
  });
70
31
  addFiles(acceptedFiles); // eslint-disable-line
71
32
  setIsHover(false);
72
- }, onDropRejected: (rejectedFiles) => {
33
+ },
34
+ onDropRejected: (rejectedFiles) => {
73
35
  window.alert('Error:\n' +
74
36
  rejectedFiles
75
37
  .map((elm) => {
76
38
  return elm.name;
77
39
  })
78
40
  .join(', '));
79
- }, onDragEnter: () => {
41
+ },
42
+ onDragEnter: () => {
80
43
  // 'dragLeave' always fires right after 'dragEnter', use 'dragOver' instead
81
44
  // console.log('enter');
82
45
  // setIsHover(true);
83
- }, onDragLeave: () => {
46
+ },
47
+ onDragLeave: () => {
84
48
  // console.log('leave');
85
49
  setIsHover(false);
86
- }, onDragOver: () => {
50
+ },
51
+ onDragOver: () => {
87
52
  // TODO: add error icon? 'isDragReject' gives unstable results
88
53
  // console.log(isDragReject);
89
54
  setIsHover(true);
90
- } }, dropzoneProps));
55
+ },
56
+ multiple,
57
+ accept,
58
+ });
91
59
  // Add previews on incoming files
92
60
  // (NOTE: `addPreview` ignores files that already have preview.)
93
- files.forEach(addPreview);
61
+ files.forEach(_FileInput_utils_1.addPreview);
94
62
  (0, react_1.useEffect)(() => () => {
95
63
  // Make sure to revoke the data uris on unmount to avoid memory leaks
96
- files.forEach(releasePreview);
64
+ files.forEach(_FileInput_utils_1.releasePreview);
97
65
  }, [files]);
98
- const removeFile = (name) => {
66
+ const removeFile = (removeTarget) => {
99
67
  if (fileInput.current) {
68
+ const deleted = [];
69
+ const targetName = typeof removeTarget !== 'string' ? removeTarget.name : removeTarget;
100
70
  const newFileList = files.filter((file) => {
101
- if (file.name !== name) {
71
+ if (file.name !== targetName) {
102
72
  return true;
103
73
  }
104
- releasePreview(file);
74
+ deleted.push(file);
75
+ (0, _FileInput_utils_1.releasePreview)(file);
76
+ return false;
105
77
  });
106
78
  fileInput.current.files = arrayToFileList(newFileList);
107
- onFilesUpdated(newFileList);
79
+ onFilesUpdated(newFileList, { deleted });
108
80
  }
109
81
  };
110
- const addFiles = (filelist) => {
82
+ const addFiles = (added) => {
111
83
  if (fileInput.current) {
112
- const newFileList = dropzoneProps.multiple
113
- ? dedupeFilesArray(files.concat(filelist))
114
- : filelist;
115
- fileInput.current.files = arrayToFileList(newFileList);
116
- onFilesUpdated(newFileList);
84
+ const { fileList, diff } = (0, _FileInput_utils_1.getFileListUpdate)(files, added, !multiple);
85
+ fileInput.current.files = arrayToFileList(fileList);
86
+ onFilesUpdated(fileList, diff);
117
87
  }
118
88
  if (inputRef.current) {
119
89
  // Empty on every add
120
90
  inputRef.current.files = arrayToFileList([]);
121
91
  }
122
92
  };
123
- const filesList = files.map((file) => (react_1.default.createElement("li", { key: file.name, className: "FileInput__file" },
124
- react_1.default.createElement("button", { className: "FileInput__file-remove", type: "button", onClick: () => removeFile(file.name), "aria-label": removeFileText }, removeFileText),
125
- react_1.default.createElement("span", { className: "FileInput__fileinfo" },
126
- showImagePreviews && file.preview && (react_1.default.createElement(react_1.default.Fragment, null,
127
- react_1.default.createElement("span", { className: "FileInput__preview" },
128
- react_1.default.createElement("img", { src: file.preview })),
129
- ' ')),
130
- react_1.default.createElement("span", { className: "FileInput__filename" }, file.name),
131
- showFileSize && (react_1.default.createElement("small", { className: "FileInput__filesize" },
132
- " - (",
133
- formatBytes(file.size),
134
- ")"))))));
135
- return (react_1.default.createElement(FormField_1.default, { className: (0, getBemClass_1.default)('FileInput', [dropzoneProps.multiple && 'multi'], className), label: label, id: domid + '-fake', LabelTag: "h4", assistText: assistText, hideLabel: hideLabel, disabled: disabled, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, renderInput: (className, inputProps /* , addFocusProps */) => {
93
+ return (react_1.default.createElement(FormField_1.default, { className: (0, getBemClass_1.default)('FileInput', [multiple && 'multi'], className), label: label, id: domid + '-fake', LabelTag: "h4", assistText: assistText, hideLabel: hideLabel, disabled: disabled, invalid: invalid, errorMessage: errorMessage, required: required, reqText: reqText, renderInput: (className, inputProps /* , addFocusProps */) => {
136
94
  return (react_1.default.createElement("div", { className: className.control, ref: fileInputWrapper },
137
- react_1.default.createElement("input", { className: "FileInput__input", name: inputElementProps.name, id: domid, ref: fileInput, type: "file", style: { display: 'none' }, multiple: dropzoneProps.multiple || undefined, required: inputProps.required }),
95
+ react_1.default.createElement("input", { className: "FileInput__input", name: inputElementProps.name, id: domid, ref: fileInput, type: "file", style: { display: 'none' }, multiple: multiple || undefined, required: inputProps.required }),
138
96
  ' ',
139
- react_1.default.createElement("input", Object.assign({ className: "FileInput__input--fake" }, getInputProps(), { tabIndex: undefined, style: undefined, multiple: dropzoneProps.multiple || undefined }, inputProps, { required: undefined })),
97
+ react_1.default.createElement("input", Object.assign({ className: "FileInput__input--fake" }, getInputProps(), { tabIndex: undefined, style: undefined, multiple: multiple || undefined }, inputProps, { required: undefined })),
140
98
  ' ',
141
99
  react_1.default.createElement("div", Object.assign({ className: (0, getBemClass_1.default)('FileInput__dropzone', [isHover && 'highlight']) }, getRootProps({ isDragReject }), { tabIndex: undefined }),
142
100
  react_1.default.createElement("p", { className: "FileInput__droptext" }, dropzoneText)),
143
- filesList.length ? react_1.default.createElement("ul", { className: "FileInput__filelist" }, filesList) : ''));
101
+ FileList && (react_1.default.createElement(FileList, Object.assign({}, {
102
+ files,
103
+ showFileSize,
104
+ showImagePreviews,
105
+ removeFileText,
106
+ removeFile,
107
+ formatBytes: _FileInput_utils_1.formatBytes,
108
+ })))));
144
109
  } }));
145
110
  };
146
111
  exports.default = FileInput;
package/TagPill.js CHANGED
@@ -18,10 +18,11 @@ const TagPill = (props) => {
18
18
  removable &&
19
19
  isStatic &&
20
20
  !onRemove &&
21
- console.warn('static (non-button) `TagPill`s must not be removable');
21
+ console.warn('Removable static (non-button) `TagPill`s ' +
22
+ 'must have an `onRemove` handler defined');
22
23
  const modifiers = [modifier, large && 'large', colors[color]];
23
24
  const removeBtn = removable && (react_1.default.createElement("button", { className: "TagPill__remove", onClick: onRemove && (() => onRemove()), "aria-label": removeLabelLong, type: "button" }, removeLabel));
24
- return isStatic ? (react_1.default.createElement("span", { className: (0, getBemClass_1.default)('TagPill', modifiers) },
25
+ return isStatic ? (react_1.default.createElement("span", Object.assign({ className: (0, getBemClass_1.default)('TagPill', modifiers) }, buttonProps),
25
26
  label,
26
27
  " ",
27
28
  removeBtn)) : onRemove ? (react_1.default.createElement("span", { className: (0, getBemClass_1.default)('TagPill', modifiers) },
@@ -95,14 +95,14 @@ const AbstractCarousel = (props) => {
95
95
  title && react_1.default.createElement("h2", { className: bem + '__title' }, title),
96
96
  isBrowser ? (react_1.default.createElement("div", { className: bem + '__itemlist-wrapper' },
97
97
  itemList,
98
- react_1.default.createElement("div", { className: bem + '__itemlist-goLeft', onClick: () => {
98
+ activeItem > 0 && (react_1.default.createElement("div", { className: bem + '__itemlist-goLeft', onClick: () => {
99
99
  delayedScrollLeft.cancel();
100
100
  scrollToItem(activeItem - 1);
101
- }, onMouseOver: () => delayedScrollLeft(activeItem), onMouseOut: () => delayedScrollLeft.cancel() }),
102
- react_1.default.createElement("div", { className: bem + '__itemlist-goRight', onClick: () => {
101
+ }, onMouseOver: () => delayedScrollLeft(activeItem), onMouseOut: () => delayedScrollLeft.cancel() })),
102
+ activeItem < itemCount - 1 && (react_1.default.createElement("div", { className: bem + '__itemlist-goRight', onClick: () => {
103
103
  delayedScrollRight.cancel();
104
104
  scrollToItem(activeItem + 1);
105
- }, onMouseOver: () => delayedScrollRight(activeItem), onMouseOut: () => delayedScrollRight.cancel() }))) : (itemList),
105
+ }, onMouseOver: () => delayedScrollRight(activeItem), onMouseOut: () => delayedScrollRight.cancel() })))) : (itemList),
106
106
  isBrowser && (react_1.default.createElement(CarouselStepper_1.default, { itemCount: itemCount, setCurrent: scrollToItem, current: activeItem }))));
107
107
  };
108
108
  exports.default = AbstractCarousel;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reykjavik/hanna-react",
3
- "version": "0.10.61",
3
+ "version": "0.10.64",
4
4
  "author": "Reykjavík (http://www.reykjavik.is)",
5
5
  "contributors": [
6
6
  "Hugsmiðjan ehf (http://www.hugsmidjan.is)",
@@ -15,8 +15,8 @@
15
15
  "dependencies": {
16
16
  "@hugsmidjan/qj": "^4.10.2",
17
17
  "@hugsmidjan/react": "^0.4.17",
18
- "@reykjavik/hanna-css": "^0.3.3",
19
- "@reykjavik/hanna-utils": "^0.1.7",
18
+ "@reykjavik/hanna-css": "^0.3.7",
19
+ "@reykjavik/hanna-utils": "^0.1.11",
20
20
  "@types/react": "^17.0.24",
21
21
  "@types/react-autosuggest": "^10.1.0",
22
22
  "@types/react-datepicker": "^3.0.2",