@ncds/ui-admin 1.8.15 → 1.8.17

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 (42) hide show
  1. package/dist/cjs/src/components/forms-and-input/file-input/FileInput.js +25 -7
  2. package/dist/cjs/src/components/forms-and-input/file-input/__tests__/FileInput.test.js +168 -0
  3. package/dist/cjs/src/components/forms-and-input/image-file-input/ImageFileInput.js +76 -19
  4. package/dist/cjs/src/components/forms-and-input/image-file-input/__tests__/ImageFileInput.test.js +533 -0
  5. package/dist/cjs/src/components/forms-and-input/image-file-input/components/ImagePreview.js +10 -3
  6. package/dist/cjs/src/hooks/__tests__/useObjectUrl.test.js +120 -0
  7. package/dist/cjs/src/hooks/index.js +11 -0
  8. package/dist/cjs/src/hooks/useObjectUrl.js +39 -0
  9. package/dist/esm/src/components/forms-and-input/file-input/FileInput.js +25 -7
  10. package/dist/esm/src/components/forms-and-input/file-input/__tests__/FileInput.test.js +165 -0
  11. package/dist/esm/src/components/forms-and-input/image-file-input/ImageFileInput.js +71 -19
  12. package/dist/esm/src/components/forms-and-input/image-file-input/__tests__/ImageFileInput.test.js +530 -0
  13. package/dist/esm/src/components/forms-and-input/image-file-input/components/ImagePreview.js +10 -3
  14. package/dist/esm/src/hooks/__tests__/useObjectUrl.test.js +117 -0
  15. package/dist/esm/src/hooks/index.js +2 -1
  16. package/dist/esm/src/hooks/useObjectUrl.js +32 -0
  17. package/dist/temp/src/components/forms-and-input/file-input/FileInput.d.ts +8 -0
  18. package/dist/temp/src/components/forms-and-input/file-input/FileInput.js +16 -3
  19. package/dist/temp/src/components/forms-and-input/file-input/__tests__/FileInput.test.d.ts +1 -0
  20. package/dist/temp/src/components/forms-and-input/file-input/__tests__/FileInput.test.js +139 -0
  21. package/dist/temp/src/components/forms-and-input/image-file-input/ImageFileInput.d.ts +33 -1
  22. package/dist/temp/src/components/forms-and-input/image-file-input/ImageFileInput.js +63 -18
  23. package/dist/temp/src/components/forms-and-input/image-file-input/__tests__/ImageFileInput.test.d.ts +1 -0
  24. package/dist/temp/src/components/forms-and-input/image-file-input/__tests__/ImageFileInput.test.js +435 -0
  25. package/dist/temp/src/components/forms-and-input/image-file-input/components/ImagePreview.d.ts +20 -2
  26. package/dist/temp/src/components/forms-and-input/image-file-input/components/ImagePreview.js +7 -2
  27. package/dist/temp/src/hooks/__tests__/useObjectUrl.test.d.ts +1 -0
  28. package/dist/temp/src/hooks/__tests__/useObjectUrl.test.js +96 -0
  29. package/dist/temp/src/hooks/index.d.ts +1 -0
  30. package/dist/temp/src/hooks/index.js +1 -0
  31. package/dist/temp/src/hooks/useObjectUrl.d.ts +16 -0
  32. package/dist/temp/src/hooks/useObjectUrl.js +32 -0
  33. package/dist/types/src/components/forms-and-input/file-input/FileInput.d.ts +8 -0
  34. package/dist/types/src/components/forms-and-input/file-input/__tests__/FileInput.test.d.ts +1 -0
  35. package/dist/types/src/components/forms-and-input/image-file-input/ImageFileInput.d.ts +33 -1
  36. package/dist/types/src/components/forms-and-input/image-file-input/__tests__/ImageFileInput.test.d.ts +1 -0
  37. package/dist/types/src/components/forms-and-input/image-file-input/components/ImagePreview.d.ts +20 -2
  38. package/dist/types/src/hooks/__tests__/useObjectUrl.test.d.ts +1 -0
  39. package/dist/types/src/hooks/index.d.ts +1 -0
  40. package/dist/types/src/hooks/useObjectUrl.d.ts +16 -0
  41. package/dist/ui-admin/assets/styles/style.css +21 -5
  42. package/package.json +1 -1
@@ -8,17 +8,36 @@ var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _uiAdminIcon = require("@ncds/ui-admin-icon");
9
9
  var _classnames = _interopRequireDefault(require("classnames"));
10
10
  var _react = require("react");
11
+ var _useObjectUrl = require("../../../hooks/useObjectUrl");
11
12
  var _button = require("../../action/button");
12
13
  var _Tag = require("../../feedback-and-status/tag/Tag");
13
14
  var _shared = require("../../shared");
14
15
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
16
+ /**
17
+ * Thumbnail next to a file tag.
18
+ *
19
+ * Split out so the object URL can live in a hook — hooks cannot be called
20
+ * from inside the file list's map callback.
21
+ */
22
+ const FileTagImage = _ref2 => {
23
+ let {
24
+ file
25
+ } = _ref2;
26
+ const objectUrl = (0, _useObjectUrl.useObjectUrl)(file);
27
+ if (!objectUrl) return null;
28
+ return (0, _jsxRuntime.jsx)("img", {
29
+ className: "ncua-file-input__file-image",
30
+ src: objectUrl,
31
+ alt: file.name
32
+ });
33
+ };
15
34
  var FileInputErrorType;
16
35
  (function (FileInputErrorType) {
17
36
  FileInputErrorType["ALREADY_UPLOADED"] = "ALREADY_UPLOADED";
18
37
  FileInputErrorType["EXCEED_MAX_FILE_SIZE"] = "EXCEED_MAX_FILE_SIZE";
19
38
  FileInputErrorType["EXCEED_MAX_FILE_COUNT"] = "EXCEED_MAX_FILE_COUNT";
20
39
  })(FileInputErrorType || (exports.FileInputErrorType = FileInputErrorType = {}));
21
- const FileInput = exports.FileInput = /*#__PURE__*/(0, _react.forwardRef)((_ref2, _ref) => {
40
+ const FileInput = exports.FileInput = /*#__PURE__*/(0, _react.forwardRef)((_ref3, _ref) => {
22
41
  let {
23
42
  size = 'xs',
24
43
  accept,
@@ -38,8 +57,9 @@ const FileInput = exports.FileInput = /*#__PURE__*/(0, _react.forwardRef)((_ref2
38
57
  isRequired,
39
58
  showHelpIcon,
40
59
  hintText,
60
+ showFileTags = true,
41
61
  ...props
42
- } = _ref2;
62
+ } = _ref3;
43
63
  const fileInputRef = (0, _react.useRef)(null);
44
64
  const [internalFiles, setInternalFiles] = (0, _react.useState)([]);
45
65
  // Determine if component is controlled or uncontrolled
@@ -115,7 +135,7 @@ const FileInput = exports.FileInput = /*#__PURE__*/(0, _react.forwardRef)((_ref2
115
135
  updateFiles(newFiles);
116
136
  };
117
137
  const renderFileTagList = () => {
118
- if (files.length === 0) return null;
138
+ if (!showFileTags || files.length === 0) return null;
119
139
  return (0, _jsxRuntime.jsx)("div", {
120
140
  className: "ncua-file-input__file-tags",
121
141
  children: files.map((file, index) => (0, _jsxRuntime.jsxs)("div", {
@@ -125,10 +145,8 @@ const FileInput = exports.FileInput = /*#__PURE__*/(0, _react.forwardRef)((_ref2
125
145
  size: size === 'xs' ? 'sm' : 'md',
126
146
  close: true,
127
147
  onButtonClick: () => handleRemoveFile(index)
128
- }), file.type.startsWith('image/') && (0, _jsxRuntime.jsx)("img", {
129
- className: "ncua-file-input__file-image",
130
- src: URL.createObjectURL(file),
131
- alt: file.name
148
+ }), file.type.startsWith('image/') && (0, _jsxRuntime.jsx)(FileTagImage, {
149
+ file: file
132
150
  })]
133
151
  }, `${file.name}-${index}`))
134
152
  });
@@ -0,0 +1,168 @@
1
+ "use strict";
2
+
3
+ var _react = require("react");
4
+ var _client = require("react-dom/client");
5
+ var _testUtils = require("react-dom/test-utils");
6
+ var _vitest = require("vitest");
7
+ var _FileInput = require("../FileInput");
8
+ // @vitest-environment jsdom
9
+
10
+ const originalCreateObjectURL = URL.createObjectURL;
11
+ const originalRevokeObjectURL = URL.revokeObjectURL;
12
+ let createObjectURL;
13
+ let revokeObjectURL;
14
+ (0, _vitest.beforeEach)(() => {
15
+ let issued = 0;
16
+ createObjectURL = _vitest.vi.fn(() => `blob:mock/${++issued}`);
17
+ revokeObjectURL = _vitest.vi.fn();
18
+ URL.createObjectURL = createObjectURL;
19
+ URL.revokeObjectURL = revokeObjectURL;
20
+ });
21
+ (0, _vitest.afterEach)(() => {
22
+ URL.createObjectURL = originalCreateObjectURL;
23
+ URL.revokeObjectURL = originalRevokeObjectURL;
24
+ });
25
+ /** 내용은 검증 대상이 아니므로 최소 크기만 채운다 */
26
+ const DUMMY_FILE_BYTES = 4;
27
+ const makeFile = (name, type) => new File([new Uint8Array(DUMMY_FILE_BYTES)], name, {
28
+ type
29
+ });
30
+ function mountFileInput(files) {
31
+ let extraProps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
32
+ const container = document.createElement('div');
33
+ document.body.appendChild(container);
34
+ const root = (0, _client.createRoot)(container);
35
+ const render = () => {
36
+ (0, _testUtils.act)(() => {
37
+ root.render(/*#__PURE__*/(0, _react.createElement)(_FileInput.FileInput, {
38
+ value: files,
39
+ onChange: () => undefined,
40
+ ...extraProps
41
+ }));
42
+ });
43
+ };
44
+ render();
45
+ const unmount = () => {
46
+ (0, _testUtils.act)(() => {
47
+ root.unmount();
48
+ });
49
+ container.remove();
50
+ };
51
+ return {
52
+ container,
53
+ render,
54
+ unmount
55
+ };
56
+ }
57
+ (0, _vitest.describe)('FileInput — 이미지 썸네일 object URL 해제', () => {
58
+ (0, _vitest.it)('이미지 파일의 썸네일을 렌더한다', () => {
59
+ const view = mountFileInput([makeFile('a.jpg', 'image/jpeg')]);
60
+ const img = view.container.querySelector('.ncua-file-input__file-image');
61
+ (0, _vitest.expect)(img).not.toBeNull();
62
+ (0, _vitest.expect)(img?.getAttribute('src')).toBe('blob:mock/1');
63
+ (0, _vitest.expect)(img?.getAttribute('alt')).toBe('a.jpg');
64
+ view.unmount();
65
+ });
66
+ (0, _vitest.it)('리렌더를 반복해도 파일당 object URL을 한 번만 만든다', () => {
67
+ const view = mountFileInput([makeFile('a.jpg', 'image/jpeg')]);
68
+ view.render();
69
+ view.render();
70
+ view.render();
71
+ (0, _vitest.expect)(createObjectURL).toHaveBeenCalledTimes(1);
72
+ view.unmount();
73
+ });
74
+ (0, _vitest.it)('언마운트하면 만들었던 object URL을 해제한다', () => {
75
+ const view = mountFileInput([makeFile('a.jpg', 'image/jpeg')]);
76
+ (0, _vitest.expect)(revokeObjectURL).not.toHaveBeenCalled();
77
+ view.unmount();
78
+ (0, _vitest.expect)(revokeObjectURL).toHaveBeenCalledTimes(1);
79
+ (0, _vitest.expect)(revokeObjectURL).toHaveBeenCalledWith('blob:mock/1');
80
+ });
81
+ (0, _vitest.it)('이미지가 아닌 파일에는 object URL을 만들지 않는다', () => {
82
+ const view = mountFileInput([makeFile('doc.pdf', 'application/pdf')]);
83
+ (0, _vitest.expect)(createObjectURL).not.toHaveBeenCalled();
84
+ (0, _vitest.expect)(view.container.querySelector('.ncua-file-input__file-image')).toBeNull();
85
+ view.unmount();
86
+ });
87
+ (0, _vitest.it)('이미지가 여러 개면 각각 하나씩만 만들고 언마운트 시 모두 해제한다', () => {
88
+ const view = mountFileInput([makeFile('a.jpg', 'image/jpeg'), makeFile('b.png', 'image/png')]);
89
+ (0, _vitest.expect)(createObjectURL).toHaveBeenCalledTimes(2);
90
+ view.unmount();
91
+ (0, _vitest.expect)(revokeObjectURL).toHaveBeenCalledTimes(2);
92
+ });
93
+ });
94
+ /** 숨겨진 file input 에 파일을 넣고 change 를 발생시킨다 */
95
+ function selectFiles(container, files) {
96
+ const input = container.querySelector('input[type="file"]');
97
+ if (!input) throw new Error('file input을 찾을 수 없다');
98
+ Object.defineProperty(input, 'files', {
99
+ value: files,
100
+ configurable: true
101
+ });
102
+ (0, _testUtils.act)(() => {
103
+ input.dispatchEvent(new Event('change', {
104
+ bubbles: true
105
+ }));
106
+ });
107
+ }
108
+ (0, _vitest.describe)('FileInput — showFileTags', () => {
109
+ (0, _vitest.it)('기본값에서는 파일 태그 목록을 렌더한다', () => {
110
+ const view = mountFileInput([makeFile('doc.pdf', 'application/pdf')]);
111
+ (0, _vitest.expect)(view.container.querySelector('.ncua-file-input__file-tags')).not.toBeNull();
112
+ (0, _vitest.expect)(view.container.querySelector('.ncua-tag')).not.toBeNull();
113
+ view.unmount();
114
+ });
115
+ (0, _vitest.it)('false면 파일 태그 목록을 렌더하지 않는다', () => {
116
+ const view = mountFileInput([makeFile('doc.pdf', 'application/pdf')], {
117
+ showFileTags: false
118
+ });
119
+ (0, _vitest.expect)(view.container.querySelector('.ncua-file-input__file-tags')).toBeNull();
120
+ (0, _vitest.expect)(view.container.querySelector('.ncua-tag')).toBeNull();
121
+ view.unmount();
122
+ });
123
+ (0, _vitest.it)('false여도 파일 찾기 버튼과 안내 문구는 그대로 렌더한다', () => {
124
+ const view = mountFileInput([makeFile('doc.pdf', 'application/pdf')], {
125
+ showFileTags: false,
126
+ hintItems: ['파일은 5MB 이내입니다.']
127
+ });
128
+ (0, _vitest.expect)(view.container.querySelector('.ncua-btn')).not.toBeNull();
129
+ (0, _vitest.expect)(view.container.querySelector('.ncua-file-input__hint-list')?.textContent).toContain('5MB');
130
+ view.unmount();
131
+ });
132
+ (0, _vitest.it)('false면 이미지 썸네일 object URL을 만들지 않는다', () => {
133
+ const view = mountFileInput([makeFile('a.jpg', 'image/jpeg')], {
134
+ showFileTags: false
135
+ });
136
+ (0, _vitest.expect)(createObjectURL).not.toHaveBeenCalled();
137
+ (0, _vitest.expect)(view.container.querySelector('.ncua-file-input__file-image')).toBeNull();
138
+ view.unmount();
139
+ });
140
+ (0, _vitest.it)('false여도 선택한 파일을 onChange로 전달한다', () => {
141
+ const onChange = _vitest.vi.fn();
142
+ const view = mountFileInput([], {
143
+ showFileTags: false,
144
+ onChange
145
+ });
146
+ const picked = makeFile('a.pdf', 'application/pdf');
147
+ selectFiles(view.container, [picked]);
148
+ (0, _vitest.expect)(onChange).toHaveBeenCalledTimes(1);
149
+ (0, _vitest.expect)(onChange).toHaveBeenCalledWith([picked]);
150
+ view.unmount();
151
+ });
152
+ (0, _vitest.it)('false여도 이미 등록된 파일은 중복으로 걸러 onFail로 알린다', () => {
153
+ const already = makeFile('a.pdf', 'application/pdf');
154
+ const onChange = _vitest.vi.fn();
155
+ const onFail = _vitest.vi.fn();
156
+ const view = mountFileInput([already], {
157
+ showFileTags: false,
158
+ onChange,
159
+ onFail
160
+ });
161
+ selectFiles(view.container, [makeFile('a.pdf', 'application/pdf')]);
162
+ (0, _vitest.expect)(onChange).toHaveBeenCalledWith([already]);
163
+ (0, _vitest.expect)(onFail).toHaveBeenCalledTimes(1);
164
+ (0, _vitest.expect)(onFail.mock.calls[0][0]).toHaveLength(1);
165
+ (0, _vitest.expect)(onFail.mock.calls[0][0][0].errorType).toBe('ALREADY_UPLOADED');
166
+ view.unmount();
167
+ });
168
+ });
@@ -4,6 +4,12 @@ Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
6
  exports.ImageFileInput = void 0;
7
+ Object.defineProperty(exports, "ImageFileInputErrorType", {
8
+ enumerable: true,
9
+ get: function () {
10
+ return _FileInput.FileInputErrorType;
11
+ }
12
+ });
7
13
  var _jsxRuntime = require("react/jsx-runtime");
8
14
  var _uiAdminIcon = require("@ncds/ui-admin-icon");
9
15
  var _classnames = _interopRequireDefault(require("classnames"));
@@ -37,6 +43,8 @@ const ImageFileInput = exports.ImageFileInput = /*#__PURE__*/(0, _react.forwardR
37
43
  maxFileCount,
38
44
  value,
39
45
  onChange,
46
+ uploadedFiles = [],
47
+ onUploadedFilesChange,
40
48
  onFileSelect,
41
49
  onFail,
42
50
  buttonLabel = '파일 찾기',
@@ -61,6 +69,9 @@ const ImageFileInput = exports.ImageFileInput = /*#__PURE__*/(0, _react.forwardR
61
69
  // Determine if component is controlled or uncontrolled
62
70
  const isControlled = value !== undefined;
63
71
  const files = isControlled ? value : internalFiles;
72
+ // An entry without a URL has nothing to render, so it must not occupy a slot either.
73
+ // TypeScript requires fileImageUrl, but CDN/vanilla consumers are untyped.
74
+ const visibleUploadedFiles = uploadedFiles.filter(uploadedFile => Boolean(uploadedFile.fileImageUrl));
64
75
  // Sync internal state with controlled value
65
76
  (0, _react.useEffect)(() => {
66
77
  if (isControlled && value) {
@@ -82,32 +93,57 @@ const ImageFileInput = exports.ImageFileInput = /*#__PURE__*/(0, _react.forwardR
82
93
  validFiles,
83
94
  invalidFiles
84
95
  } = validateFiles(Array.from(selectedFiles));
85
- // Replace existing file if maxFileCount is 1
86
- const nextFiles = maxFileCount === 1 ? validFiles : [...files, ...validFiles];
87
- updateFiles(nextFiles);
96
+ // Nothing passed validation keep what the caller already had.
97
+ // Without this, a single slot would be overwritten with an empty list.
98
+ if (validFiles.length > 0) {
99
+ // Replace existing file if maxFileCount is 1
100
+ const nextFiles = maxFileCount === 1 ? validFiles : [...files, ...validFiles];
101
+ updateFiles(nextFiles);
102
+ // A single slot holds one image, so a new selection also clears the stored one.
103
+ // Entries we never rendered occupy no slot, so they are left in the caller's list.
104
+ if (maxFileCount === 1 && visibleUploadedFiles.length > 0) {
105
+ onUploadedFilesChange?.(uploadedFiles.filter(uploadedFile => !uploadedFile.fileImageUrl));
106
+ }
107
+ }
88
108
  if (onFail && invalidFiles.length > 0) {
89
109
  onFail(invalidFiles);
90
110
  }
91
111
  event.target.value = '';
92
112
  };
113
+ /**
114
+ * Returns why a file must be rejected, or null when it is acceptable.
115
+ * `acceptedCount` is how many files of the same selection already passed.
116
+ */
117
+ const findRejectionReason = (file, acceptedCount) => {
118
+ if (files.some(f => f.name === file.name && f.size === file.size)) {
119
+ return _FileInput.FileInputErrorType.ALREADY_UPLOADED;
120
+ }
121
+ // Stored images carry no size, so they can only be matched by name.
122
+ // A single slot is meant to be replaced, so re-picking the same name is allowed there.
123
+ if (maxFileCount !== 1 && visibleUploadedFiles.some(uploadedFile => uploadedFile.fileName === file.name)) {
124
+ return _FileInput.FileInputErrorType.ALREADY_UPLOADED;
125
+ }
126
+ if (!!maxFileSize && file.size > maxFileSize) {
127
+ return _FileInput.FileInputErrorType.EXCEED_MAX_FILE_SIZE;
128
+ }
129
+ // Skip max count check if maxFileCount is 1 (allow replacement).
130
+ // Stored images occupy slots too, so they count toward the limit.
131
+ const usedSlots = visibleUploadedFiles.length + files.length + acceptedCount;
132
+ if (!!maxFileCount && maxFileCount !== 1 && usedSlots >= maxFileCount) {
133
+ return _FileInput.FileInputErrorType.EXCEED_MAX_FILE_COUNT;
134
+ }
135
+ return null;
136
+ };
93
137
  const validateFiles = fileList => {
94
138
  const validFiles = [];
95
139
  const invalidFiles = [];
96
140
  for (const file of fileList) {
97
- if (files.some(f => f.name === file.name && f.size === file.size)) {
98
- invalidFiles.push(toInvalidFile(file, _FileInput.FileInputErrorType.ALREADY_UPLOADED));
99
- continue;
100
- }
101
- if (!!maxFileSize && file.size > maxFileSize) {
102
- invalidFiles.push(toInvalidFile(file, _FileInput.FileInputErrorType.EXCEED_MAX_FILE_SIZE));
103
- continue;
141
+ const rejectionReason = findRejectionReason(file, validFiles.length);
142
+ if (rejectionReason) {
143
+ invalidFiles.push(toInvalidFile(file, rejectionReason));
144
+ } else {
145
+ validFiles.push(file);
104
146
  }
105
- // Skip max count check if maxFileCount is 1 (allow replacement)
106
- if (!!maxFileCount && maxFileCount !== 1 && files.length + validFiles.length >= maxFileCount) {
107
- invalidFiles.push(toInvalidFile(file, _FileInput.FileInputErrorType.EXCEED_MAX_FILE_COUNT));
108
- continue;
109
- }
110
- validFiles.push(file);
111
147
  }
112
148
  return {
113
149
  validFiles,
@@ -124,13 +160,28 @@ const ImageFileInput = exports.ImageFileInput = /*#__PURE__*/(0, _react.forwardR
124
160
  newFiles.splice(index, 1);
125
161
  updateFiles(newFiles);
126
162
  };
163
+ // Removed by identity, not index, so keys stay stable and the caller's
164
+ // untouched entries (including ones we skip rendering) are preserved
165
+ const handleRemoveUploadedFile = target => {
166
+ onUploadedFilesChange?.(uploadedFiles.filter(uploadedFile => uploadedFile !== target));
167
+ };
127
168
  const renderImagePreview = function () {
128
169
  let files = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
129
- const showEmptySlot = maxFileCount ? files.length < maxFileCount : files.length === 0;
170
+ const totalCount = visibleUploadedFiles.length + files.length;
171
+ const showEmptySlot = maxFileCount ? totalCount < maxFileCount : totalCount === 0;
130
172
  return (0, _jsxRuntime.jsxs)("div", {
131
173
  className: "ncua-image-file-input__previews",
132
- children: [files.map((file, index) => (0, _jsxRuntime.jsx)(_ImagePreview.ImagePreview, {
174
+ children: [visibleUploadedFiles.map((uploadedFile, index) => (0, _jsxRuntime.jsx)(_ImagePreview.ImagePreview
175
+ // Removal is by identity, so the index here only breaks ties between
176
+ // entries that share both URL and file name.
177
+ , {
178
+ imageUrl: uploadedFile.fileImageUrl,
179
+ alt: uploadedFile.fileName,
180
+ disabled: disabled,
181
+ onRemove: () => handleRemoveUploadedFile(uploadedFile)
182
+ }, `uploaded-${index}-${uploadedFile.fileImageUrl}-${uploadedFile.fileName}`)), files.map((file, index) => (0, _jsxRuntime.jsx)(_ImagePreview.ImagePreview, {
133
183
  file: file,
184
+ disabled: disabled,
134
185
  onRemove: () => handleRemoveFile(index)
135
186
  }, `${file.name}-${index}`)), showEmptySlot && (0, _jsxRuntime.jsxs)("div", {
136
187
  className: "ncua-image-file-input__empty-slot-wrapper",
@@ -206,4 +257,10 @@ const ImageFileInput = exports.ImageFileInput = /*#__PURE__*/(0, _react.forwardR
206
257
  }), showHintText && renderHintList()]
207
258
  })]
208
259
  });
209
- });
260
+ });
261
+ /**
262
+ * Rejection reasons reported through `onFail`.
263
+ *
264
+ * Re-exported under the ImageFileInput name so callers do not have to reach
265
+ * into FileInput for it. Same enum, same values.
266
+ */