@mezzanine-ui/react 1.0.0-rc.6 → 1.0.0-rc.7

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 (61) hide show
  1. package/AutoComplete/AutoComplete.d.ts +25 -9
  2. package/AutoComplete/AutoComplete.js +84 -17
  3. package/AutoComplete/AutoCompleteInside.d.ts +54 -0
  4. package/AutoComplete/AutoCompleteInside.js +17 -0
  5. package/AutoComplete/useAutoCompleteKeyboard.d.ts +2 -1
  6. package/AutoComplete/useAutoCompleteKeyboard.js +4 -1
  7. package/Breadcrumb/BreadcrumbDropdown.d.ts +1 -1
  8. package/Breadcrumb/BreadcrumbOverflowMenuDropdown.d.ts +1 -1
  9. package/Breadcrumb/typings.d.ts +1 -1
  10. package/COMPONENTS.md +2 -2
  11. package/Checkbox/Checkbox.js +24 -3
  12. package/Cropper/Cropper.d.ts +1 -1
  13. package/Description/Description.d.ts +1 -1
  14. package/Description/Description.js +1 -1
  15. package/Description/DescriptionTitle.d.ts +6 -1
  16. package/Description/DescriptionTitle.js +2 -2
  17. package/Drawer/Drawer.d.ts +39 -34
  18. package/Drawer/Drawer.js +33 -35
  19. package/Dropdown/Dropdown.d.ts +16 -1
  20. package/Dropdown/Dropdown.js +156 -9
  21. package/Dropdown/DropdownItem.d.ts +26 -2
  22. package/Dropdown/DropdownItem.js +91 -43
  23. package/Dropdown/DropdownItemCard.d.ts +3 -2
  24. package/Dropdown/DropdownItemCard.js +8 -5
  25. package/Dropdown/dropdownKeydownHandler.d.ts +6 -0
  26. package/Dropdown/dropdownKeydownHandler.js +14 -7
  27. package/FilterArea/Filter.d.ts +25 -2
  28. package/FilterArea/Filter.js +23 -0
  29. package/FilterArea/FilterArea.d.ts +43 -4
  30. package/FilterArea/FilterArea.js +35 -2
  31. package/FilterArea/FilterLine.d.ts +19 -0
  32. package/FilterArea/FilterLine.js +19 -0
  33. package/Input/SpinnerButton/SpinnerButton.js +1 -1
  34. package/Modal/Modal.d.ts +22 -86
  35. package/Modal/Modal.js +4 -2
  36. package/Modal/ModalBodyForVerification.js +3 -1
  37. package/NotificationCenter/NotificationCenter.d.ts +21 -9
  38. package/NotificationCenter/NotificationCenter.js +22 -10
  39. package/NotificationCenter/NotificationCenterDrawer.d.ts +52 -1
  40. package/NotificationCenter/NotificationCenterDrawer.js +2 -2
  41. package/OverflowTooltip/OverflowTooltip.js +46 -5
  42. package/PageFooter/PageFooter.js +6 -14
  43. package/Pagination/PaginationPageSize.js +1 -1
  44. package/README.md +34 -4
  45. package/Radio/Radio.js +16 -2
  46. package/Table/Table.js +1 -1
  47. package/TimePicker/TimePicker.js +1 -1
  48. package/TimeRangePicker/TimeRangePicker.js +1 -1
  49. package/Toggle/Toggle.d.ts +1 -1
  50. package/Toggle/Toggle.js +1 -1
  51. package/Upload/Upload.d.ts +13 -7
  52. package/Upload/Upload.js +55 -20
  53. package/Upload/UploadItem.js +4 -1
  54. package/Upload/UploadPictureCard.d.ts +5 -0
  55. package/Upload/UploadPictureCard.js +8 -5
  56. package/Upload/Uploader.d.ts +32 -31
  57. package/Upload/Uploader.js +10 -9
  58. package/index.d.ts +3 -3
  59. package/index.js +1 -1
  60. package/llms.txt +128 -9
  61. package/package.json +5 -4
package/Radio/Radio.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsxs, jsx } from 'react/jsx-runtime';
3
- import { forwardRef, useContext } from 'react';
3
+ import { forwardRef, useContext, useRef, useCallback } from 'react';
4
4
  import { radioClasses } from '@mezzanine-ui/core/radio';
5
5
  import InputCheck from '../_internal/InputCheck/InputCheck.js';
6
6
  import { useRadioControlValue } from '../Form/useRadioControlValue.js';
@@ -56,13 +56,27 @@ const Radio = forwardRef(function Radio(props, ref) {
56
56
  value,
57
57
  });
58
58
  const size = (_a = sizeProp !== null && sizeProp !== void 0 ? sizeProp : sizeFromGroup) !== null && _a !== void 0 ? _a : 'main';
59
+ const radioInputRef = useRef(null);
60
+ const textInputRef = useRef(null);
61
+ const handleRadioChange = useCallback((event) => {
62
+ var _a;
63
+ onChange(event);
64
+ if (withInputConfig && !withInputConfig.disabled) {
65
+ (_a = textInputRef.current) === null || _a === void 0 ? void 0 : _a.focus();
66
+ }
67
+ }, [onChange, withInputConfig]);
59
68
  return (jsxs("div", { ref: ref, className: cx(radioClasses.wrapper, className), children: [jsx(InputCheck, { ...rest, control: jsxs("span", { className: cx(radioClasses.host, radioClasses.size(size), {
60
69
  [radioClasses.segmented]: type === 'segment',
61
70
  [radioClasses.checked]: checked,
62
71
  [radioClasses.error]: error,
63
72
  }), children: [type === 'segment' && (jsxs("span", { className: cx(radioClasses.segmentedContainer, {
64
73
  [radioClasses.segmentedContainerWithIconText]: !!children && !!icon,
65
- }), children: [icon && jsx(Icon, { icon: icon, size: 16 }), children] })), jsx("input", { ...restInputProps, "aria-checked": checked, "aria-disabled": disabled, checked: checked, disabled: disabled, id: inputId, onChange: onChange, name: name, type: "radio", value: value })] }), disabled: disabled, error: error, hint: hint, htmlFor: inputId, segmentedStyle: type === 'segment', size: size, children: type === 'radio' && children }), type === 'radio' && withInputConfig && (jsx("div", { style: { width: (_b = withInputConfig.width) !== null && _b !== void 0 ? _b : 120 }, children: jsx(Input, { ...withInputConfig, variant: "base", placeholder: (_c = withInputConfig.placeholder) !== null && _c !== void 0 ? _c : 'Placeholder' }) }))] }));
74
+ }), children: [icon && jsx(Icon, { icon: icon, size: 16 }), children] })), jsx("input", { ...restInputProps, "aria-checked": checked, "aria-disabled": disabled, checked: checked, disabled: disabled, id: inputId, onChange: handleRadioChange, name: name, ref: radioInputRef, type: "radio", value: value })] }), disabled: disabled, error: error, hint: hint, htmlFor: inputId, segmentedStyle: type === 'segment', size: size, children: type === 'radio' && children }), type === 'radio' && withInputConfig && (jsx("div", { style: { width: (_b = withInputConfig.width) !== null && _b !== void 0 ? _b : 120 }, children: jsx(Input, { ...withInputConfig, inputRef: textInputRef, inputProps: {
75
+ onClick: () => {
76
+ var _a;
77
+ (_a = radioInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
78
+ },
79
+ }, variant: "base", placeholder: (_c = withInputConfig.placeholder) !== null && _c !== void 0 ? _c : 'Placeholder' }) }))] }));
66
80
  });
67
81
 
68
82
  export { Radio as default };
package/Table/Table.js CHANGED
@@ -244,7 +244,7 @@ function TableInner(props, ref) {
244
244
  pagination: pagination || undefined,
245
245
  pinnable: pinnableState,
246
246
  resizable,
247
- rowState,
247
+ rowState: rowState,
248
248
  rowHeight,
249
249
  scroll,
250
250
  scrollContainerRef,
@@ -204,7 +204,7 @@ const TimePicker = forwardRef(function TimePicker(props, ref) {
204
204
  }
205
205
  };
206
206
  const suffixIcon = (jsx(Icon, { "aria-label": "Open time picker", icon: ClockIcon, onClick: readOnly ? undefined : onIconClick }));
207
- return (jsxs(Fragment, { children: [jsx(PickerTrigger, { ...restTriggerProps, ref: triggerComposedRef, className: className, clearable: clearable, disabled: disabled, error: error, format: resolvedFormat, fullWidth: fullWidth, inputProps: resolvedInputProps, inputRef: inputRef, hoverValue: open && !inputValue && pendingValue
207
+ return (jsxs(Fragment, { children: [jsx(PickerTrigger, { ...restTriggerProps, ref: triggerComposedRef, className: className, clearable: clearable, disabled: disabled, error: error, forceShowClearable: !!internalValue, format: resolvedFormat, fullWidth: fullWidth, hideSuffixWhenClearable: clearable, inputProps: resolvedInputProps, inputRef: inputRef, hoverValue: open && !inputValue && pendingValue
208
208
  ? ((_b = formatToString(locale, pendingValue, resolvedFormat)) !== null && _b !== void 0 ? _b : undefined)
209
209
  : undefined, onChange: (e) => {
210
210
  const val = e.target.value;
@@ -150,7 +150,7 @@ const TimeRangePicker = forwardRef(function TimeRangePicker(props, ref) {
150
150
  });
151
151
  /** Suffix icon */
152
152
  const suffixIcon = (jsx(Icon, { "aria-label": "Open time picker", icon: ClockIcon, onClick: readOnly ? undefined : onIconClick }));
153
- return (jsxs(Fragment, { children: [jsx(RangePickerTrigger, { className: className, clearable: clearable, disabled: disabled, error: error, errorMessagesFrom: errorMessagesFrom, errorMessagesTo: errorMessagesTo, format: resolvedFormat, fullWidth: fullWidth, inputFromPlaceholder: inputFromPlaceholder, inputFromProps: inputFromProps, inputFromRef: inputFromRef, inputFromValue: inputFromValue, inputToPlaceholder: inputToPlaceholder, inputToProps: inputToProps, inputToRef: inputToRef, inputToValue: inputToValue, onClear: onClearHandler, onFromFocus: onFromFocusHandler, onIconClick: onIconClick, onInputFromChange: onInputFromChange, onInputToChange: onInputToChange, onToFocus: onToFocusHandler, prefix: prefix, readOnly: readOnly, ref: triggerComposedRef, required: required, size: size, suffixActionIcon: suffixIcon, validateFrom: validateFrom, validateTo: validateTo }), focusedInput && (jsx(TimePickerPanel, { anchor: panelAnchor, fadeProps: fadeProps, hideHour: hideHour, hideMinute: hideMinute, hideSecond: hideSecond, hourStep: hourStep, minuteStep: minuteStep, onChange: onPanelChange, onCancel: onCancel, onConfirm: onConfirm, open: open, popperProps: popperProps, ref: panelRef, secondStep: secondStep, value: panelValue }))] }));
153
+ return (jsxs(Fragment, { children: [jsx(RangePickerTrigger, { className: className, clearable: clearable, disabled: disabled, error: error, errorMessagesFrom: errorMessagesFrom, errorMessagesTo: errorMessagesTo, forceShowClearable: internalValue.some(Boolean), format: resolvedFormat, fullWidth: fullWidth, hideSuffixWhenClearable: clearable, inputFromPlaceholder: inputFromPlaceholder, inputFromProps: inputFromProps, inputFromRef: inputFromRef, inputFromValue: inputFromValue, inputToPlaceholder: inputToPlaceholder, inputToProps: inputToProps, inputToRef: inputToRef, inputToValue: inputToValue, onClear: onClearHandler, onFromFocus: onFromFocusHandler, onIconClick: onIconClick, onInputFromChange: onInputFromChange, onInputToChange: onInputToChange, onToFocus: onToFocusHandler, prefix: prefix, readOnly: readOnly, ref: triggerComposedRef, required: required, size: size, suffixActionIcon: suffixIcon, validateFrom: validateFrom, validateTo: validateTo }), focusedInput && (jsx(TimePickerPanel, { anchor: panelAnchor, fadeProps: fadeProps, hideHour: hideHour, hideMinute: hideMinute, hideSecond: hideSecond, hourStep: hourStep, minuteStep: minuteStep, onChange: onPanelChange, onCancel: onCancel, onConfirm: onConfirm, open: open, popperProps: popperProps, ref: panelRef, secondStep: secondStep, value: panelValue }))] }));
154
154
  });
155
155
 
156
156
  export { TimeRangePicker as default };
@@ -40,7 +40,7 @@ export interface ToggleProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
40
40
  supportingText?: string;
41
41
  }
42
42
  /**
43
- * 切換開關元件(亦以 `Switch` 名稱匯出),用於表示開/關二元狀態。
43
+ * 切換開關元件,用於表示開/關二元狀態。
44
44
  *
45
45
  * 支援受控(`checked` + `onChange`)與非受控(`defaultChecked`)兩種用法;
46
46
  * `label` 顯示於開關右側,`supportingText` 顯示於 label 下方作為輔助說明。
package/Toggle/Toggle.js CHANGED
@@ -8,7 +8,7 @@ import { FormControlContext } from '../Form/FormControlContext.js';
8
8
  import cx from 'clsx';
9
9
 
10
10
  /**
11
- * 切換開關元件(亦以 `Switch` 名稱匯出),用於表示開/關二元狀態。
11
+ * 切換開關元件,用於表示開/關二元狀態。
12
12
  *
13
13
  * 支援受控(`checked` + `onChange`)與非受控(`defaultChecked`)兩種用法;
14
14
  * `label` 顯示於開關右側,`supportingText` 顯示於 label 下方作為輔助說明。
@@ -2,6 +2,7 @@ import { type UploadItemStatus, type UploadMode, type UploadSize } from '@mezzan
2
2
  import { type ReactNode } from 'react';
3
3
  import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
4
4
  import type { UploaderProps } from './Uploader';
5
+ import type { UploadPictureCardAriaLabels } from './UploadPictureCard';
5
6
  export interface UploadFile {
6
7
  /**
7
8
  * The file object.
@@ -51,13 +52,17 @@ export interface UploadProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
51
52
  * @default false
52
53
  */
53
54
  disabled?: boolean;
55
+ /**
56
+ * Hints passed into the Uploader dropzone area. Only visible in dropzone modes (`list`, `card-wall`).
57
+ */
58
+ dropzoneHints?: UploaderProps['hints'];
54
59
  /**
55
60
  * Controlled file list for the upload component.
56
61
  * Provide this along with `onChange` to fully control the file state.
57
62
  */
58
63
  files?: UploadFile[];
59
64
  /**
60
- * Array of hints to display with the upload component.
65
+ * Array of hints displayed outside the uploader area. Visible in all modes.
61
66
  */
62
67
  hints?: UploaderProps['hints'];
63
68
  /**
@@ -77,11 +82,6 @@ export interface UploadProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
77
82
  * The react ref passed to input element.
78
83
  */
79
84
  inputRef?: UploaderProps['inputRef'];
80
- /**
81
- * Whether to fill the width of the container.
82
- * @default false
83
- */
84
- isFillWidth?: boolean;
85
85
  /**
86
86
  * Maximum number of files allowed to upload.
87
87
  * If exceeded, the excess files will be ignored.
@@ -89,7 +89,8 @@ export interface UploadProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
89
89
  maxFiles?: number;
90
90
  /**
91
91
  * The display mode for the upload component.
92
- * - 'list': Display files as a list using UploadItem
92
+ * - 'list': Display files as a list using UploadItem (with dropzone)
93
+ * - 'basic-list': Display files as a list without drag-and-drop
93
94
  * - 'button-list': Display uploader as a button with files in list format
94
95
  * - 'cards': Display image files as picture cards using UploadPictureCard
95
96
  * - 'card-wall': Display uploader at top with image files as picture cards below
@@ -129,6 +130,11 @@ export interface UploadProps extends Omit<NativeElementPropsWithoutKeyAndRef<'di
129
130
  * This will be used when a file's status becomes 'error' and no errorIcon is provided.
130
131
  */
131
132
  errorIcon?: ReactNode;
133
+ /**
134
+ * Aria labels passed to picture cards in `cards` / `card-wall` mode.
135
+ * Useful for customizing text such as "Click to Replace".
136
+ */
137
+ ariaLabels?: UploadPictureCardAriaLabels;
132
138
  /**
133
139
  * Fired when files list changes.
134
140
  */
package/Upload/Upload.js CHANGED
@@ -1,21 +1,25 @@
1
1
  'use client';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { forwardRef, useRef, useEffect, useMemo, useCallback } from 'react';
3
+ import { forwardRef, useRef, useState, useEffect, useMemo, useCallback } from 'react';
4
4
  import { defaultUploadPictureCardErrorMessage, uploadClasses } from '@mezzanine-ui/core/upload';
5
5
  import { DangerousFilledIcon, InfoFilledIcon } from '@mezzanine-ui/icons';
6
+ import { isImageFile } from './upload-utils.js';
6
7
  import Uploader from './Uploader.js';
7
8
  import UploadItem from './UploadItem.js';
8
- import { isImageFile } from './upload-utils.js';
9
9
  import UploadPictureCard from './UploadPictureCard.js';
10
10
  import Icon from '../Icon/Icon.js';
11
+ import MediaPreviewModal from '../Modal/MediaPreviewModal.js';
11
12
  import cx from 'clsx';
12
13
 
13
14
  const Upload = forwardRef(function Upload(props, ref) {
14
- const { accept, className, disabled = false, mode = 'list', size = 'main', showFileSize = true, files: controlledFiles = [], onUpload, onDelete, onReload, onDownload, onZoomIn, onChange, id, name, multiple = false, maxFiles, hints, uploaderLabel, uploaderIcon, inputRef, inputProps, onMaxFilesExceeded, errorMessage, errorIcon, ...rest } = props;
15
+ var _a, _b;
16
+ const { accept, className, disabled = false, dropzoneHints, mode = 'list', size = 'main', showFileSize = true, files: controlledFiles = [], onUpload, onDelete, onReload, onDownload, onZoomIn, onChange, id, name, multiple = false, maxFiles, hints, uploaderLabel, uploaderIcon, inputRef, inputProps, onMaxFilesExceeded, errorMessage, errorIcon, ariaLabels, ...rest } = props;
15
17
  const files = controlledFiles;
16
18
  const filesRef = useRef(files);
17
19
  const replaceFileIdRef = useRef(null);
18
20
  const replaceInputRef = useRef(null);
21
+ const [previewFile, setPreviewFile] = useState(null);
22
+ const [isPreviewOpen, setIsPreviewOpen] = useState(false);
19
23
  useEffect(() => {
20
24
  filesRef.current = files;
21
25
  }, [files]);
@@ -224,10 +228,34 @@ const Upload = forwardRef(function Upload(props, ref) {
224
228
  }, [findFileById, onDownload]);
225
229
  const handleZoomIn = useCallback((fileId) => {
226
230
  const file = findFileById(fileId);
227
- if (file && file.file) {
231
+ if (!file)
232
+ return;
233
+ setPreviewFile(file);
234
+ setIsPreviewOpen(true);
235
+ if (file.file) {
228
236
  onZoomIn === null || onZoomIn === void 0 ? void 0 : onZoomIn(fileId, file.file);
229
237
  }
230
238
  }, [findFileById, onZoomIn]);
239
+ const [previewObjectUrl, setPreviewObjectUrl] = useState(null);
240
+ useEffect(() => {
241
+ if (!previewFile || previewFile.url || !previewFile.file) {
242
+ setPreviewObjectUrl(null);
243
+ return;
244
+ }
245
+ if (!isImageFile(previewFile.file, previewFile.url)) {
246
+ setPreviewObjectUrl(null);
247
+ return;
248
+ }
249
+ const url = URL.createObjectURL(previewFile.file);
250
+ setPreviewObjectUrl(url);
251
+ return () => {
252
+ URL.revokeObjectURL(url);
253
+ };
254
+ }, [previewFile]);
255
+ const previewIsImage = previewFile
256
+ ? isImageFile(previewFile.file, previewFile.url)
257
+ : false;
258
+ const previewImageSrc = (_b = (_a = previewFile === null || previewFile === void 0 ? void 0 : previewFile.url) !== null && _a !== void 0 ? _a : previewObjectUrl) !== null && _b !== void 0 ? _b : '';
231
259
  const { imageFiles, nonImageFiles } = useMemo(() => {
232
260
  const images = [];
233
261
  const nonImages = [];
@@ -246,19 +274,23 @@ const Upload = forwardRef(function Upload(props, ref) {
246
274
  const uploaderConfig = useMemo(() => {
247
275
  const config = {
248
276
  list: {
249
- isFillWidth: true,
277
+ mode: 'dropzone',
278
+ type: 'base',
279
+ },
280
+ 'basic-list': {
281
+ mode: 'basic',
250
282
  type: 'base',
251
283
  },
252
284
  'button-list': {
253
285
  type: 'button',
254
286
  },
255
287
  cards: {
288
+ mode: 'basic',
256
289
  type: 'base',
257
- isFillWidth: false,
258
290
  },
259
291
  'card-wall': {
292
+ mode: 'basic',
260
293
  type: 'base',
261
- isFillWidth: false,
262
294
  },
263
295
  };
264
296
  return config[mode];
@@ -266,7 +298,7 @@ const Upload = forwardRef(function Upload(props, ref) {
266
298
  const topUploaderConfig = useMemo(() => {
267
299
  if (mode === 'card-wall') {
268
300
  return {
269
- isFillWidth: true,
301
+ mode: 'dropzone',
270
302
  type: 'base',
271
303
  };
272
304
  }
@@ -296,18 +328,19 @@ const Upload = forwardRef(function Upload(props, ref) {
296
328
  handleDownload,
297
329
  handleReload,
298
330
  ]);
299
- const uploaderElement = (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...uploaderConfig }));
300
- const topUploaderElement = topUploaderConfig ? (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id ? `${id}-top` : undefined, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: hints, onUpload: handleUpload, ...topUploaderConfig })) : null;
331
+ const uploaderElement = (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: uploaderConfig.mode === 'dropzone' ? dropzoneHints : undefined, onUpload: handleUpload, ...uploaderConfig }));
332
+ const topUploaderElement = topUploaderConfig ? (jsx(Uploader, { accept: accept, disabled: effectiveDisabled, id: id ? `${id}-top` : undefined, name: name, multiple: multiple, label: uploaderLabel, icon: uploaderIcon, inputRef: inputRef, inputProps: inputProps, hints: dropzoneHints, onUpload: handleUpload, ...topUploaderConfig })) : null;
333
+ // When a top uploader exists (card-wall mode), the inline card uploader is not needed.
334
+ const inlineCardUploaderElement = topUploaderElement ? null : uploaderElement;
301
335
  const hintsElement = useMemo(() => {
302
- if (!hints ||
303
- hints.length === 0 ||
304
- mode === 'list' ||
305
- mode === 'card-wall')
336
+ if (!hints || hints.length === 0)
306
337
  return null;
307
- const hintsClassName = mode === 'cards' ? uploadClasses.fillWidthHints : uploadClasses.hints;
338
+ const hintsClassName = mode === 'list' || mode === 'card-wall' || mode === 'cards'
339
+ ? uploadClasses.fillWidthHints
340
+ : uploadClasses.hints;
308
341
  return (jsx("ul", { className: hintsClassName, children: hints.map((hint) => (jsxs("li", { className: uploadClasses.hint(hint.type || 'info'), children: [jsx(Icon, { icon: hint.type === 'info' ? InfoFilledIcon : DangerousFilledIcon, color: hint.type === 'info' ? 'info' : 'error', size: 14 }), hint.label] }, hint.label))) }));
309
342
  }, [hints, mode]);
310
- return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, mode === 'button-list' && hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id), ...(!isSingleFileCardMode && {
343
+ return (jsxs("div", { ref: ref, className: cx(uploadClasses.host, className, mode === 'cards' && uploadClasses.hostCards), ...rest, children: [topUploaderElement, mode === 'card-wall' && hintsElement, !shouldUsePictureCard && (jsxs("div", { className: uploadClasses.uploadButtonList, children: [uploaderElement, hintsElement] })), jsxs("div", { className: cx(uploadClasses.uploadList, shouldUsePictureCard && uploadClasses.uploadListCards), children: [shouldUsePictureCard && (jsxs(Fragment, { children: [imageFiles.map((uploadFile) => (jsx(UploadPictureCard, { ariaLabels: ariaLabels, file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id), ...(!isSingleFileCardMode && {
311
344
  onDownload: () => handleDownload(uploadFile.id),
312
345
  onZoomIn: () => handleZoomIn(uploadFile.id),
313
346
  }), ...(isSingleFileCardMode && {
@@ -317,17 +350,19 @@ const Upload = forwardRef(function Upload(props, ref) {
317
350
  replaceFileIdRef.current = uploadFile.id;
318
351
  (_a = replaceInputRef.current) === null || _a === void 0 ? void 0 : _a.click();
319
352
  },
320
- }) }, uploadFile.id))), !isSingleFileCardMode && uploaderElement, isSingleFileCardMode &&
353
+ }) }, uploadFile.id))), nonImageFiles.map((uploadFile) => (jsx(UploadPictureCard, { ariaLabels: ariaLabels, file: uploadFile.file, url: uploadFile.url, id: uploadFile.id, status: uploadFile.status, size: size, disabled: disabled, errorMessage: uploadFile.errorMessage, onDelete: () => handleDelete(uploadFile.id), onReload: () => handleReload(uploadFile.id), ...(!isSingleFileCardMode && {
354
+ onDownload: () => handleDownload(uploadFile.id),
355
+ }) }, uploadFile.id))), !isSingleFileCardMode && inlineCardUploaderElement, isSingleFileCardMode &&
321
356
  imageFiles.length === 0 &&
322
- uploaderElement, isSingleFileCardMode && (jsx("input", { ref: replaceInputRef, accept: accept, style: { display: 'none' }, type: "file", onChange: (e) => {
357
+ inlineCardUploaderElement, isSingleFileCardMode && (jsx("input", { ref: replaceInputRef, accept: accept, style: { display: 'none' }, type: "file", onChange: (e) => {
323
358
  var _a;
324
359
  const selectedFiles = Array.from((_a = e.target.files) !== null && _a !== void 0 ? _a : []);
325
360
  e.target.value = '';
326
361
  if (selectedFiles.length)
327
362
  handleUpload(selectedFiles);
328
- } }))] })), nonImageFiles.length > 0 && nonImageFiles.map(renderUploadItem), !shouldUsePictureCard &&
363
+ } }))] })), !shouldUsePictureCard && nonImageFiles.length > 0 && nonImageFiles.map(renderUploadItem), !shouldUsePictureCard &&
329
364
  imageFiles.length > 0 &&
330
- imageFiles.map(renderUploadItem)] }), mode === 'cards' && hintsElement] }));
365
+ imageFiles.map(renderUploadItem)] }), mode === 'cards' && hintsElement, previewFile && previewIsImage && previewImageSrc && (jsx(MediaPreviewModal, { open: isPreviewOpen, onClose: () => setIsPreviewOpen(false), mediaItems: [previewImageSrc] }))] }));
331
366
  });
332
367
 
333
368
  export { Upload as default };
@@ -91,6 +91,8 @@ const UploadItem = forwardRef(function UploadItem(props, ref) {
91
91
  const isFinished = useMemo(() => {
92
92
  return /done|error/.test(status);
93
93
  }, [status]);
94
+ const shouldShowFileSize = Boolean(showFileSize && isFinished && fileSize);
95
+ const shouldSingleLineCenter = Boolean(type === 'thumbnail' && fileName && !shouldShowFileSize);
94
96
  useEffect(() => {
95
97
  if (!props.file && !props.url) {
96
98
  console.warn('UploadItem: Both `file` and `url` props are missing. At least one should be provided to display the upload item.');
@@ -112,7 +114,8 @@ const UploadItem = forwardRef(function UploadItem(props, ref) {
112
114
  [uploadItemClasses.alignCenter]: status !== 'done',
113
115
  [uploadItemClasses.error]: status === 'error',
114
116
  [uploadItemClasses.disabled]: disabled,
115
- }, className), children: [jsxs("div", { ...rest, "aria-disabled": disabled, className: uploadItemClasses.container, role: "group", tabIndex: disabled ? -1 : 0, children: [jsxs("div", { className: uploadItemClasses.contentWrapper, children: [jsx("div", { className: uploadItemClasses.icon, children: itemIcon }), jsxs("div", { className: uploadItemClasses.content, children: [jsx(Tooltip, { title: isTextTruncated ? fileName : undefined, options: { placement: 'bottom' }, children: ({ onMouseEnter, onMouseLeave, ref: tooltipRef }) => (jsx(Typography, { ref: mergeRefs(tooltipRef), className: uploadItemClasses.name, ellipsis: true, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: fileName })) }), showFileSize && isFinished && fileSize && (jsx(Typography, { className: uploadItemClasses.fontSize, children: fileSize }))] })] }), jsxs("div", { className: uploadItemClasses.actions, children: [status === 'loading' && (jsxs(Fragment, { children: [loadingIcon, jsx(ClearActions, { onClick: onCancel, className: uploadItemClasses.closeIcon, role: "button" })] })), status === 'done' && !disabled && (jsx(Icon, { icon: DownloadIcon, size: 16, className: uploadItemClasses.downloadIcon, onClick: onDownload })), status === 'error' && !disabled && (jsx(Icon, { icon: ResetIcon, size: 16, className: uploadItemClasses.resetIcon, onClick: onReload }))] })] }), isFinished && !disabled && (jsx("div", { className: uploadItemClasses.deleteContent, children: jsx(Icon, { icon: TrashIcon, size: 16, className: uploadItemClasses.deleteIcon, onClick: onDelete }) }))] }), status === 'error' && errorMessage && (jsxs("div", { className: uploadItemClasses.errorMessage, children: [jsx(Icon, { icon: errorIcon !== null && errorIcon !== void 0 ? errorIcon : DangerousFilledIcon, size: 14, color: "error", className: uploadItemClasses.errorIcon }), jsx(Typography, { color: "text-error", variant: "caption", className: uploadItemClasses.errorMessageText, children: errorMessage })] }))] }));
117
+ [uploadItemClasses.singleLineContent]: shouldSingleLineCenter,
118
+ }, className), children: [jsxs("div", { ...rest, "aria-disabled": disabled, className: uploadItemClasses.container, role: "group", tabIndex: disabled ? -1 : 0, children: [jsxs("div", { className: uploadItemClasses.contentWrapper, children: [jsx("div", { className: uploadItemClasses.icon, children: itemIcon }), jsxs("div", { className: uploadItemClasses.content, children: [jsx(Tooltip, { title: isTextTruncated ? fileName : undefined, options: { placement: 'bottom' }, children: ({ onMouseEnter, onMouseLeave, ref: tooltipRef }) => (jsx(Typography, { ref: mergeRefs(tooltipRef), className: uploadItemClasses.name, ellipsis: true, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: fileName })) }), shouldShowFileSize && (jsx(Typography, { className: uploadItemClasses.fontSize, children: fileSize }))] })] }), jsxs("div", { className: uploadItemClasses.actions, children: [status === 'loading' && (jsxs(Fragment, { children: [loadingIcon, jsx(ClearActions, { onClick: onCancel, className: uploadItemClasses.closeIcon, role: "button" })] })), status === 'done' && !disabled && (jsx(Icon, { icon: DownloadIcon, size: 16, className: uploadItemClasses.downloadIcon, onClick: onDownload })), status === 'error' && !disabled && (jsx(Icon, { icon: ResetIcon, size: 16, className: uploadItemClasses.resetIcon, onClick: onReload }))] })] }), isFinished && !disabled && (jsx("div", { className: uploadItemClasses.deleteContent, children: jsx(Icon, { icon: TrashIcon, size: 16, className: uploadItemClasses.deleteIcon, color: "neutral-solid", onClick: onDelete }) }))] }), status === 'error' && errorMessage && (jsxs("div", { className: uploadItemClasses.errorMessage, children: [jsx(Icon, { icon: errorIcon !== null && errorIcon !== void 0 ? errorIcon : DangerousFilledIcon, size: 14, color: "error", className: uploadItemClasses.errorIcon }), jsx(Typography, { color: "text-error", variant: "caption", className: uploadItemClasses.errorMessageText, children: errorMessage })] }))] }));
116
119
  });
117
120
 
118
121
  export { UploadItem as default };
@@ -91,6 +91,11 @@ export interface UploadPictureCardProps extends NativeElementPropsWithoutKeyAndR
91
91
  * Error icon to display when status is 'error'.
92
92
  */
93
93
  errorIcon?: IconDefinition;
94
+ /**
95
+ * Whether the upload picture card is readable.
96
+ * @default false
97
+ */
98
+ readable?: boolean;
94
99
  /**
95
100
  * When delete icon is clicked, this callback will be fired.
96
101
  */
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
- import { forwardRef, useMemo, useState, useEffect } from 'react';
3
+ import { forwardRef, useCallback, useMemo, useState, useEffect } from 'react';
4
4
  import { uploadPictureCardClasses } from '@mezzanine-ui/core/upload';
5
5
  import { ImageIcon, FileIcon, ZoomInIcon, DownloadIcon, TrashIcon, ResetIcon } from '@mezzanine-ui/icons';
6
6
  import Button from '../Button/Button.js';
@@ -16,7 +16,7 @@ import cx from 'clsx';
16
16
  */
17
17
  const UploadPictureCard = forwardRef(function UploadPictureCard(props, ref) {
18
18
  var _a;
19
- const { ariaLabels, className, file, url, status = 'loading', imageFit = 'cover', size = 'main', disabled = false, errorMessage, errorIcon, onDelete, onDownload, onReload, onReplace, onZoomIn, ...rest } = props;
19
+ const { ariaLabels, className, file, url, status = 'loading', imageFit = 'cover', size = 'main', disabled = false, errorMessage, errorIcon, readable = false, onDelete, onDownload, onReload, onReplace, onZoomIn, ...rest } = props;
20
20
  const defaultAriaLabels = {
21
21
  cancelUpload: 'Cancel upload',
22
22
  clickToReplace: 'Click to Replace',
@@ -27,6 +27,9 @@ const UploadPictureCard = forwardRef(function UploadPictureCard(props, ref) {
27
27
  zoomIn: 'Zoom in image',
28
28
  };
29
29
  const labels = { ...defaultAriaLabels, ...ariaLabels };
30
+ const handleDelete = useCallback((e) => { e.stopPropagation(); onDelete === null || onDelete === void 0 ? void 0 : onDelete(e); }, [onDelete]);
31
+ const handleDownload = useCallback((e) => { e.stopPropagation(); onDownload === null || onDownload === void 0 ? void 0 : onDownload(e); }, [onDownload]);
32
+ const handleZoomIn = useCallback((e) => { e.stopPropagation(); onZoomIn === null || onZoomIn === void 0 ? void 0 : onZoomIn(e); }, [onZoomIn]);
30
33
  const isImage = useMemo(() => {
31
34
  return isImageFile(file, url);
32
35
  }, [file, url]);
@@ -94,17 +97,17 @@ const UploadPictureCard = forwardRef(function UploadPictureCard(props, ref) {
94
97
  console.warn('UploadPictureCard: minor size is not supported for non-image files');
95
98
  return null;
96
99
  }
97
- return (jsx("div", { "aria-disabled": disabled, className: cx(uploadPictureCardClasses.host, uploadPictureCardClasses.size(size), disabled && uploadPictureCardClasses.disabled, onReplace && status === 'done' && uploadPictureCardClasses.replaceMode, className), onClick: onReplace && status === 'done' ? onReplace : undefined, onKeyDown: onReplace && status === 'done'
100
+ return (jsx("div", { "aria-disabled": disabled, className: cx(uploadPictureCardClasses.host, uploadPictureCardClasses.size(size), disabled && uploadPictureCardClasses.disabled, readable && uploadPictureCardClasses.readable, !readable && onReplace && status === 'done' && uploadPictureCardClasses.replaceMode, className), onClick: !readable && onReplace && status === 'done' ? onReplace : undefined, onKeyDown: !readable && onReplace && status === 'done'
98
101
  ? (e) => {
99
102
  if (e.key === 'Enter' || e.key === ' ') {
100
103
  e.preventDefault();
101
104
  e.currentTarget.click();
102
105
  }
103
106
  }
104
- : undefined, ref: ref, role: "group", tabIndex: disabled ? -1 : 0, ...rest, children: jsxs("div", { className: uploadPictureCardClasses.container, children: [isImage && imageUrl && status !== 'error' && (jsx("img", { alt: fileName, src: imageUrl, style: {
107
+ : undefined, ref: ref, role: "group", tabIndex: disabled || readable ? -1 : 0, ...rest, children: jsxs("div", { className: uploadPictureCardClasses.container, children: [isImage && imageUrl && status !== 'error' && (jsx("img", { alt: fileName, src: imageUrl, style: {
105
108
  objectFit: imageFit,
106
109
  objectPosition: 'center',
107
- } })), status === 'done' && size !== 'minor' && !isImage && (jsxs("div", { className: uploadPictureCardClasses.content, children: [jsx(Icon, { icon: FileIcon, color: "brand", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.name, ellipsis: true, children: fileName })] })), status === 'error' && size !== 'minor' && (jsxs("div", { className: uploadPictureCardClasses.errorMessage, role: "alert", "aria-live": "polite", children: [jsx(Icon, { icon: errorIconContent, color: "error", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.errorMessageText, children: errorMessageContent })] })), jsxs("div", { className: cx(uploadPictureCardClasses.actions, uploadPictureCardClasses.actionsStatus(status)), children: [status === 'loading' && size !== 'minor' && (jsxs(Fragment, { children: [jsx(ClearActions, { type: "embedded", variant: "contrast", onClick: onDelete, className: uploadPictureCardClasses.clearActionsIcon, "aria-label": labels.cancelUpload }), jsx("div", { className: uploadPictureCardClasses.loadingIcon, "aria-label": labels.uploading, children: jsx(Spin, { loading: true, size: "sub" }) })] })), status === 'done' && size !== 'minor' && (jsxs(Fragment, { children: [jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [onZoomIn && (jsx(Button, { variant: "base-secondary", size: "minor", icon: ZoomInIcon, iconType: "icon-only", onClick: onZoomIn, "aria-label": labels.zoomIn })), onDownload && (jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: DownloadIcon, onClick: onDownload, "aria-label": labels.download })), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: onDelete, "aria-label": labels.delete })] }) }), onReplace && (jsx("span", { className: uploadPictureCardClasses.replaceLabel, children: labels.clickToReplace }))] })), status === 'error' && size !== 'minor' && (jsx(Fragment, { children: jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: ResetIcon, onClick: onReload, "aria-label": labels.reload }), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: onDelete, "aria-label": labels.delete })] }) }) })), size === 'minor' && (jsx(Icon, { icon: ZoomInIcon, color: "fixed-light", size: 24 }))] })] }) }));
110
+ } })), status === 'done' && size !== 'minor' && !isImage && (jsxs("div", { className: uploadPictureCardClasses.content, children: [jsx(Icon, { icon: FileIcon, color: "brand", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.name, ellipsis: true, children: fileName })] })), status === 'error' && size !== 'minor' && (jsxs("div", { className: uploadPictureCardClasses.errorMessage, role: "alert", "aria-live": "polite", children: [jsx(Icon, { icon: errorIconContent, color: "error", size: 16 }), jsx(Typography, { className: uploadPictureCardClasses.errorMessageText, children: errorMessageContent })] })), jsxs("div", { className: cx(uploadPictureCardClasses.actions, uploadPictureCardClasses.actionsStatus(status)), children: [status === 'loading' && size !== 'minor' && !readable && (jsxs(Fragment, { children: [jsx(ClearActions, { type: "embedded", variant: "contrast", onClick: onDelete, className: uploadPictureCardClasses.clearActionsIcon, "aria-label": labels.cancelUpload }), jsx("div", { className: uploadPictureCardClasses.loadingIcon, "aria-label": labels.uploading, children: jsx(Spin, { loading: true, size: "sub" }) })] })), status === 'done' && size !== 'minor' && !readable && (jsxs(Fragment, { children: [jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [onZoomIn && (jsx(Button, { variant: "base-secondary", size: "minor", icon: ZoomInIcon, iconType: "icon-only", onClick: handleZoomIn, "aria-label": labels.zoomIn })), onDownload && (jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: DownloadIcon, onClick: handleDownload, "aria-label": labels.download })), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: handleDelete, "aria-label": labels.delete })] }) }), onReplace && (jsx("span", { className: uploadPictureCardClasses.replaceLabel, children: labels.clickToReplace }))] })), status === 'error' && size !== 'minor' && !readable && (jsx(Fragment, { children: jsx("div", { className: uploadPictureCardClasses.tools, children: jsxs("div", { className: uploadPictureCardClasses.toolsContent, children: [jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: ResetIcon, onClick: onReload, "aria-label": labels.reload }), jsx(Button, { variant: "base-secondary", size: "minor", iconType: "icon-only", icon: TrashIcon, onClick: onDelete, "aria-label": labels.delete })] }) }) })), size === 'minor' && !readable && (jsx(Icon, { icon: ZoomInIcon, color: "fixed-light", size: 24 }))] })] }) }));
108
111
  });
109
112
 
110
113
  export { UploadPictureCard as default };
@@ -1,5 +1,5 @@
1
1
  import { ChangeEventHandler, ReactNode, Ref } from 'react';
2
- import { type UploaderHintType, type UploadPictureControl, type UploadType } from '@mezzanine-ui/core/upload';
2
+ import { type UploaderHintType, type UploaderMode, type UploadPictureControl, type UploadType } from '@mezzanine-ui/core/upload';
3
3
  import { type IconDefinition } from '@mezzanine-ui/icons';
4
4
  import { NativeElementPropsWithoutKeyAndRef } from '../utils/jsx-types';
5
5
  type UploaderInputElementProps = Omit<NativeElementPropsWithoutKeyAndRef<'input'>, 'accept' | 'disabled' | 'multiple' | 'onChange' | 'type' | `aria-${'disabled'}`> & {
@@ -42,7 +42,7 @@ export interface UploaderLabel {
42
42
  */
43
43
  error?: string;
44
44
  /**
45
- * Label text for "Click to upload" when isFillWidth is true.
45
+ * Label text for "Click to upload" in `mode="dropzone"`.
46
46
  * @default 'Click to upload'
47
47
  */
48
48
  clickToUpload?: string;
@@ -87,20 +87,31 @@ export interface UploaderProps extends Omit<NativeElementPropsWithoutKeyAndRef<'
87
87
  * @example 'image/*', '.pdf,.doc,.docx'
88
88
  */
89
89
  accept?: string;
90
+ /**
91
+ * Provide `controllerRef` if you need detail data of file.
92
+ */
93
+ controllerRef?: Ref<UploadPictureControl | null>;
90
94
  /**
91
95
  * Whether the input is disabled.
92
96
  * @default false
93
97
  */
94
98
  disabled?: boolean;
95
99
  /**
96
- * The id of input element.
100
+ * Array of hints to display outside the uploader (below the label element).
97
101
  */
98
- id?: string;
102
+ externalHints?: UploaderHint[];
99
103
  /**
100
- * Whether to fill the width of the container.
101
- * @default false
104
+ * Array of hints to display with the upload component.
105
+ */
106
+ hints?: UploaderHint[];
107
+ /**
108
+ * Icon configuration for different actions and states.
102
109
  */
103
- isFillWidth?: boolean;
110
+ icon?: UploaderIcon;
111
+ /**
112
+ * The id of input element.
113
+ */
114
+ id?: string;
104
115
  /**
105
116
  * Since at Mezzanine we use a host element to wrap our input, most derived props will be passed to the host element.
106
117
  * If you need direct control to the input element, use this prop to provide to it.
@@ -110,37 +121,25 @@ export interface UploaderProps extends Omit<NativeElementPropsWithoutKeyAndRef<'
110
121
  * The react ref passed to input element.
111
122
  */
112
123
  inputRef?: React.Ref<HTMLInputElement>;
124
+ /**
125
+ * Label configuration for different states.
126
+ */
127
+ label?: UploaderLabel;
113
128
  /**
114
129
  * The name attribute of the input element.
115
130
  */
116
131
  name?: string;
132
+ /**
133
+ * The mode for upload component.
134
+ * @default 'basic'
135
+ * @example 'basic' | 'dropzone'
136
+ */
137
+ mode?: UploaderMode;
117
138
  /**
118
139
  * Whether can select multiple files to upload.
119
140
  * @default false
120
141
  */
121
142
  multiple?: boolean;
122
- /**
123
- * The type for upload component.
124
- * @default 'base'
125
- * @example 'base' | 'button'
126
- */
127
- type?: UploadType;
128
- /**
129
- * Array of hints to display with the upload component.
130
- */
131
- hints?: UploaderHint[];
132
- /**
133
- * Label configuration for different states.
134
- */
135
- label?: UploaderLabel;
136
- /**
137
- * Icon configuration for different actions and states.
138
- */
139
- icon?: UploaderIcon;
140
- /**
141
- * Provide `controllerRef` if you need detail data of file.
142
- */
143
- controllerRef?: Ref<UploadPictureControl | null>;
144
143
  /**
145
144
  * Invoked by input change event.
146
145
  */
@@ -150,9 +149,11 @@ export interface UploaderProps extends Omit<NativeElementPropsWithoutKeyAndRef<'
150
149
  */
151
150
  onUpload?: (files: File[]) => void;
152
151
  /**
153
- * Fired after user deletes file.
152
+ * The type for upload component.
153
+ * @default 'base'
154
+ * @example 'base' | 'button'
154
155
  */
155
- onDelete?: () => void;
156
+ type?: UploadType;
156
157
  }
157
158
  /**
158
159
  * The react component for `mezzanine` uploader.
@@ -2,7 +2,7 @@
2
2
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
3
3
  import { forwardRef, useId, useRef, useState, useMemo } from 'react';
4
4
  import { uploaderClasses } from '@mezzanine-ui/core/upload';
5
- import { UploadIcon } from '@mezzanine-ui/icons';
5
+ import { UploadIcon, InfoFilledIcon, DangerousFilledIcon } from '@mezzanine-ui/icons';
6
6
  import Button from '../Button/Button.js';
7
7
  import Typography from '../Typography/Typography.js';
8
8
  import { composeRefs } from '../utils/composeRefs.js';
@@ -14,7 +14,7 @@ import cx from 'clsx';
14
14
  */
15
15
  const Uploader = forwardRef(function Uploader(props, ref) {
16
16
  var _a, _b, _c;
17
- const { accept, className, disabled = false, id, inputProps, inputRef: inputRefProp, isFillWidth = false, multiple = false, name, type, hints, label: labelConfig, icon: iconConfig, controllerRef: _controllerRef, onChange: onChangeProp, onUpload, onDelete: _onDelete, ...rest } = props;
17
+ const { accept, className, controllerRef: _controllerRef, disabled = false, externalHints, id, hints, icon: iconConfig, inputProps, inputRef: inputRefProp, label: labelConfig, mode = 'basic', multiple = false, name, onChange: onChangeProp, onUpload, type, ...rest } = props;
18
18
  const { name: nameFromInputProps, id: idFromInputProps, ...restInputProps } = inputProps || {};
19
19
  // Generate unique id if not provided
20
20
  const generatedId = useId();
@@ -23,6 +23,7 @@ const Uploader = forwardRef(function Uploader(props, ref) {
23
23
  const [isDragging, setIsDragging] = useState(false);
24
24
  const composedInputRef = useMemo(() => composeRefs([inputRefProp, inputElementRef]), [inputRefProp]);
25
25
  const resolvedName = (_b = name !== null && name !== void 0 ? name : nameFromInputProps) !== null && _b !== void 0 ? _b : finalInputId;
26
+ const isDropzone = mode === 'dropzone';
26
27
  const handleChange = (event) => {
27
28
  if (onChangeProp) {
28
29
  onChangeProp(event);
@@ -98,7 +99,7 @@ const Uploader = forwardRef(function Uploader(props, ref) {
98
99
  }, [iconConfig]);
99
100
  const uploadLabel = (labelConfig === null || labelConfig === void 0 ? void 0 : labelConfig.uploadLabel)
100
101
  ? labelConfig === null || labelConfig === void 0 ? void 0 : labelConfig.uploadLabel
101
- : isFillWidth
102
+ : isDropzone
102
103
  ? 'Drag the file here or'
103
104
  : 'Upload';
104
105
  const clickToUploadLabel = (_c = labelConfig === null || labelConfig === void 0 ? void 0 : labelConfig.clickToUpload) !== null && _c !== void 0 ? _c : 'Click to upload';
@@ -109,12 +110,12 @@ const Uploader = forwardRef(function Uploader(props, ref) {
109
110
  inputElementRef.current.click();
110
111
  }
111
112
  };
112
- return (jsx("label", { className: cx(uploaderClasses.host, type && uploaderClasses.type(type), type !== 'button' && isFillWidth && uploaderClasses.fillWidth, isDragging && uploaderClasses.dragging, type !== 'button' && disabled && uploaderClasses.disabled, className), onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, ref: ref, ...rest, children: jsxs(Fragment, { children: [type === 'base'
113
- && isFillWidth
114
- && jsxs("div", { className: uploaderClasses.uploadContent, children: [uploadIcon, jsxs(Typography, { className: uploaderClasses.uploadLabel, children: [uploadLabel && jsxs(Fragment, { children: [uploadLabel, ' '] }), jsx("span", { className: uploaderClasses.clickToUpload, children: clickToUploadLabel })] }), hints === null || hints === void 0 ? void 0 : hints.map((hint, index) => (jsx(Typography, { className: uploaderClasses.fillWidthHints, children: hint.label }, index)))] }), type === 'base'
115
- && !isFillWidth
116
- && jsxs("div", { className: uploaderClasses.uploadContent, children: [uploadIcon, jsx(Typography, { className: uploaderClasses.uploadLabel, children: uploadLabel })] }), type === 'button'
117
- && (jsxs(Button, { disabled: disabled, onClick: handleClickToUpload, children: [uploadIcon, jsx(Typography, { children: uploadLabel })] })), jsx("input", { ...restInputProps, accept: accept, "aria-disabled": disabled, className: uploaderClasses.input, disabled: disabled, id: finalInputId, multiple: multiple, name: resolvedName, onChange: handleChange, ref: composedInputRef, type: "file" })] }) }));
113
+ return (jsxs(Fragment, { children: [jsx("label", { className: cx(uploaderClasses.host, type && uploaderClasses.type(type), type !== 'button' && isDropzone && uploaderClasses.fillWidth, isDragging && uploaderClasses.dragging, type !== 'button' && disabled && uploaderClasses.disabled, className), onDragEnter: handleDragEnter, onDragLeave: handleDragLeave, onDragOver: handleDragOver, onDrop: handleDrop, ref: ref, ...rest, children: jsxs(Fragment, { children: [type === 'base'
114
+ && isDropzone
115
+ && jsxs("div", { className: uploaderClasses.uploadContent, children: [uploadIcon, jsxs(Typography, { className: uploaderClasses.uploadLabel, children: [uploadLabel && jsxs(Fragment, { children: [uploadLabel, ' '] }), jsx("span", { className: uploaderClasses.clickToUpload, children: clickToUploadLabel })] }), hints === null || hints === void 0 ? void 0 : hints.map((hint, index) => (jsx(Typography, { className: uploaderClasses.fillWidthHints, children: hint.label }, index)))] }), type === 'base'
116
+ && !isDropzone
117
+ && jsxs("div", { className: uploaderClasses.uploadContent, children: [uploadIcon, jsx(Typography, { className: uploaderClasses.uploadLabel, children: uploadLabel })] }), type === 'button'
118
+ && (jsx(Button, { disabled: disabled, iconType: "leading", icon: UploadIcon, onClick: handleClickToUpload, children: jsx(Typography, { align: "center", color: "text-fixed-light", variant: "button-highlight", className: uploaderClasses.uploadButtonText, children: uploadLabel }) })), jsx("input", { ...restInputProps, accept: accept, "aria-disabled": disabled, className: uploaderClasses.input, disabled: disabled, id: finalInputId, multiple: multiple, name: resolvedName, onChange: handleChange, ref: composedInputRef, type: "file" })] }) }), externalHints && externalHints.length > 0 && (jsx("ul", { className: uploaderClasses.externalHints, children: externalHints.map((hint) => (jsxs("li", { className: uploaderClasses.externalHint(hint.type || 'info'), children: [jsx(Icon, { icon: hint.type === 'info' ? InfoFilledIcon : DangerousFilledIcon, color: hint.type === 'info' ? 'info' : 'error', size: 14 }), hint.label] }, hint.label))) }))] }));
118
119
  });
119
120
 
120
121
  export { Uploader as default };
package/index.d.ts CHANGED
@@ -123,7 +123,7 @@ export type { CascaderOption, CascaderPanelProps, CascaderProps, CascaderSize, }
123
123
  export { default as Select, SelectControlContext, SelectTrigger, SelectTriggerTags, } from './Select';
124
124
  export type { SelectControl, SelectProps, SelectTriggerInputProps, SelectTriggerProps, SelectTriggerTagsProps, SelectValue, } from './Select';
125
125
  export { default as SelectionCard } from './SelectionCard';
126
- export type { SelectionCardProps, SelectionCardPropsBase } from './SelectionCard';
126
+ export type { SelectionCardProps, SelectionCardPropsBase, } from './SelectionCard';
127
127
  export { default as Slider, useSlider } from './Slider';
128
128
  export type { RangeSliderProps, RangeSliderValue, SingleSliderProps, SingleSliderValue, SliderBaseProps, SliderComponentProps, SliderProps, SliderRect, SliderValue, UseRangeSliderProps, UseSingleSliderProps, UseSliderCommonProps, UseSliderProps, UseSliderResult, } from './Slider';
129
129
  export { default as Textarea } from './Textarea';
@@ -134,8 +134,8 @@ export { default as TimePicker, TimePickerPanel } from './TimePicker';
134
134
  export type { TimePickerPanelProps, TimePickerProps } from './TimePicker';
135
135
  export { default as TimeRangePicker, useTimeRangePickerValue, } from './TimeRangePicker';
136
136
  export type { TimeRangePickerProps, TimeRangePickerValue, UseTimeRangePickerValueProps, } from './TimeRangePicker';
137
- export { default as Switch } from './Toggle';
138
- export type { ToggleProps as SwitchProps, ToggleSize as SwitchSize, } from './Toggle';
137
+ export { default as Toggle } from './Toggle';
138
+ export type { ToggleProps, ToggleSize } from './Toggle';
139
139
  export { Upload, UploadItem, UploadPictureCard, Uploader } from './Upload';
140
140
  export type { UploadFile, UploadItemProps, UploadPictureCardProps, UploadProps, UploaderProps, } from './Upload';
141
141
  /**
package/index.js CHANGED
@@ -116,7 +116,7 @@ export { default as TextField } from './TextField/TextField.js';
116
116
  export { default as TimePicker } from './TimePicker/TimePicker.js';
117
117
  export { default as TimePickerPanel } from './TimePicker/TimePickerPanel.js';
118
118
  export { default as TimeRangePicker } from './TimeRangePicker/TimeRangePicker.js';
119
- export { default as Switch } from './Toggle/Toggle.js';
119
+ export { default as Toggle } from './Toggle/Toggle.js';
120
120
  export { default as Upload } from './Upload/Upload.js';
121
121
  export { default as UploadItem } from './Upload/UploadItem.js';
122
122
  export { default as UploadPictureCard } from './Upload/UploadPictureCard.js';