@drivenets/design-system 0.0.1
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 +9 -0
- package/LICENSE +201 -0
- package/dist/index.cjs +5394 -0
- package/dist/index.css +1 -0
- package/dist/index.d.cts +9157 -0
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +9157 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5335 -0
- package/dist/index.js.map +1 -0
- package/package.json +104 -0
- package/src/components/ds-alert-banner/ds-alert-banner.module.scss +116 -0
- package/src/components/ds-alert-banner/ds-alert-banner.tsx +115 -0
- package/src/components/ds-alert-banner/ds-alert-banner.types.tsx +47 -0
- package/src/components/ds-alert-banner/index.tsx +2 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.module.scss +99 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.tsx +94 -0
- package/src/components/ds-breadcrumb/ds-breadcrumb.types.tsx +32 -0
- package/src/components/ds-breadcrumb/index.tsx +2 -0
- package/src/components/ds-button/ds-button.tsx +12 -0
- package/src/components/ds-button/ds-button.types.ts +6 -0
- package/src/components/ds-button/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.module.scss +257 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.tsx +34 -0
- package/src/components/ds-button/versions/ds-button-legacy/ds-button-legacy.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-legacy/index.ts +2 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.module.scss +580 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.tsx +49 -0
- package/src/components/ds-button/versions/ds-button-new/ds-button-new.types.ts +41 -0
- package/src/components/ds-button/versions/ds-button-new/index.ts +2 -0
- package/src/components/ds-checkbox/ds-checkbox.module.scss +130 -0
- package/src/components/ds-checkbox/ds-checkbox.tsx +55 -0
- package/src/components/ds-checkbox/ds-checkbox.types.tsx +27 -0
- package/src/components/ds-checkbox/index.tsx +2 -0
- package/src/components/ds-chip/ds-chip.module.scss +71 -0
- package/src/components/ds-chip/ds-chip.tsx +73 -0
- package/src/components/ds-chip/ds-chip.types.tsx +46 -0
- package/src/components/ds-chip/index.tsx +2 -0
- package/src/components/ds-chip-group/ds-chip-group.module.scss +59 -0
- package/src/components/ds-chip-group/ds-chip-group.tsx +122 -0
- package/src/components/ds-chip-group/ds-chip-group.types.ts +52 -0
- package/src/components/ds-chip-group/hooks/use-chip-row-calculation.ts +76 -0
- package/src/components/ds-chip-group/index.ts +2 -0
- package/src/components/ds-confirmation/ds-confirmation.module.scss +58 -0
- package/src/components/ds-confirmation/ds-confirmation.tsx +121 -0
- package/src/components/ds-confirmation/ds-confirmation.types.tsx +28 -0
- package/src/components/ds-confirmation/index.ts +2 -0
- package/src/components/ds-dialog/ds-dialog.module.scss +50 -0
- package/src/components/ds-dialog/ds-dialog.tsx +70 -0
- package/src/components/ds-dialog/ds-dialog.types.tsx +48 -0
- package/src/components/ds-dialog/index.tsx +2 -0
- package/src/components/ds-drawer/ds-drawer.module.scss +212 -0
- package/src/components/ds-drawer/ds-drawer.tsx +160 -0
- package/src/components/ds-drawer/ds-drawer.types.tsx +54 -0
- package/src/components/ds-drawer/index.ts +2 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.module.scss +123 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.tsx +396 -0
- package/src/components/ds-dropdown-menu/ds-dropdown-menu.types.tsx +262 -0
- package/src/components/ds-dropdown-menu/index.tsx +2 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.module.scss +70 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.tsx +106 -0
- package/src/components/ds-expandable-text-input/ds-expandable-text-input.types.tsx +19 -0
- package/src/components/ds-expandable-text-input/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.module.scss +90 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.tsx +106 -0
- package/src/components/ds-file-upload/components/file-upload/file-upload.types.tsx +94 -0
- package/src/components/ds-file-upload/components/file-upload/index.ts +2 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.module.scss +125 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.tsx +143 -0
- package/src/components/ds-file-upload/components/file-upload-item/file-upload-item.types.tsx +26 -0
- package/src/components/ds-file-upload/components/file-upload-item/index.ts +2 -0
- package/src/components/ds-file-upload/ds-file-upload-api.types.ts +38 -0
- package/src/components/ds-file-upload/ds-file-upload.tsx +46 -0
- package/src/components/ds-file-upload/ds-file-upload.types.tsx +54 -0
- package/src/components/ds-file-upload/errors/file-upload-errors.ts +27 -0
- package/src/components/ds-file-upload/hooks/index.ts +2 -0
- package/src/components/ds-file-upload/hooks/use-file-upload.ts +350 -0
- package/src/components/ds-file-upload/index.ts +7 -0
- package/src/components/ds-file-upload/types/accept-types.ts +106 -0
- package/src/components/ds-file-upload/utils/accept-utils.ts +25 -0
- package/src/components/ds-file-upload/utils/constants.ts +21 -0
- package/src/components/ds-file-upload/utils/file-factory.ts +18 -0
- package/src/components/ds-file-upload/utils/format-file-size.ts +15 -0
- package/src/components/ds-file-upload/utils/generate-helper-text.ts +39 -0
- package/src/components/ds-file-upload/utils/get-error-message.ts +17 -0
- package/src/components/ds-file-upload/utils/is-file-equal.ts +8 -0
- package/src/components/ds-form-control/ds-form-control.module.scss +135 -0
- package/src/components/ds-form-control/ds-form-control.tsx +94 -0
- package/src/components/ds-form-control/ds-form-control.types.tsx +58 -0
- package/src/components/ds-form-control/index.tsx +2 -0
- package/src/components/ds-grid/ds-grid.module.scss +0 -0
- package/src/components/ds-grid/ds-grid.tsx +43 -0
- package/src/components/ds-grid/ds-grid.types.ts +55 -0
- package/src/components/ds-grid/index.ts +2 -0
- package/src/components/ds-icon/ds-icon.module.scss +25 -0
- package/src/components/ds-icon/ds-icon.tsx +33 -0
- package/src/components/ds-icon/ds-icon.types.ts +53 -0
- package/src/components/ds-icon/index.ts +2 -0
- package/src/components/ds-icon/material-icons.ts +6203 -0
- package/src/components/ds-modal/ds-modal.module.scss +100 -0
- package/src/components/ds-modal/ds-modal.tsx +136 -0
- package/src/components/ds-modal/ds-modal.types.tsx +44 -0
- package/src/components/ds-modal/index.tsx +2 -0
- package/src/components/ds-number-input/ds-number-input.module.scss +15 -0
- package/src/components/ds-number-input/ds-number-input.tsx +74 -0
- package/src/components/ds-number-input/ds-number-input.types.tsx +76 -0
- package/src/components/ds-number-input/index.tsx +2 -0
- package/src/components/ds-panel/ds-panel.module.scss +95 -0
- package/src/components/ds-panel/ds-panel.tsx +46 -0
- package/src/components/ds-panel/ds-panel.types.ts +17 -0
- package/src/components/ds-panel/index.tsx +2 -0
- package/src/components/ds-password-input/ds-password-input.module.scss +10 -0
- package/src/components/ds-password-input/ds-password-input.tsx +67 -0
- package/src/components/ds-password-input/ds-password-input.types.tsx +62 -0
- package/src/components/ds-password-input/index.tsx +2 -0
- package/src/components/ds-popover/ds-popover.module.scss +23 -0
- package/src/components/ds-popover/ds-popover.tsx +33 -0
- package/src/components/ds-popover/ds-popover.types.tsx +24 -0
- package/src/components/ds-popover/index.tsx +2 -0
- package/src/components/ds-radio-group/ds-radio-group.module.scss +138 -0
- package/src/components/ds-radio-group/ds-radio-group.tsx +115 -0
- package/src/components/ds-radio-group/ds-radio-group.types.tsx +93 -0
- package/src/components/ds-radio-group/index.tsx +2 -0
- package/src/components/ds-select/ds-select.module.scss +66 -0
- package/src/components/ds-select/ds-select.tsx +239 -0
- package/src/components/ds-select/ds-select.types.tsx +80 -0
- package/src/components/ds-select/index.tsx +2 -0
- package/src/components/ds-select/select-items-chips.tsx +56 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.module.scss +69 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.tsx +113 -0
- package/src/components/ds-smart-tabs/ds-smart-tabs.types.tsx +74 -0
- package/src/components/ds-smart-tabs/index.tsx +2 -0
- package/src/components/ds-spinner/ds-spinner.module.scss +39 -0
- package/src/components/ds-spinner/ds-spinner.tsx +51 -0
- package/src/components/ds-spinner/ds-spinner.types.tsx +20 -0
- package/src/components/ds-spinner/index.tsx +2 -0
- package/src/components/ds-status-badge/ds-status-badge.module.scss +79 -0
- package/src/components/ds-status-badge/ds-status-badge.tsx +42 -0
- package/src/components/ds-status-badge/ds-status-badge.types.tsx +46 -0
- package/src/components/ds-status-badge/index.tsx +2 -0
- package/src/components/ds-stepper/components/ds-step-content.tsx +32 -0
- package/src/components/ds-stepper/components/ds-step-description.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-label.tsx +6 -0
- package/src/components/ds-stepper/components/ds-step-next-button.tsx +13 -0
- package/src/components/ds-stepper/components/ds-step.tsx +59 -0
- package/src/components/ds-stepper/ds-stepper.module.scss +134 -0
- package/src/components/ds-stepper/ds-stepper.tsx +34 -0
- package/src/components/ds-stepper/ds-stepper.types.ts +12 -0
- package/src/components/ds-stepper/hooks/use-stepper.ts +20 -0
- package/src/components/ds-stepper/hooks/use-steps.ts +18 -0
- package/src/components/ds-stepper/index.tsx +6 -0
- package/src/components/ds-system-status/ds-system-status.module.scss +87 -0
- package/src/components/ds-system-status/ds-system-status.tsx +33 -0
- package/src/components/ds-system-status/ds-system-status.types.tsx +37 -0
- package/src/components/ds-system-status/index.tsx +10 -0
- package/src/components/ds-table/components/core-table/core-table.module.scss +32 -0
- package/src/components/ds-table/components/core-table/core-table.tsx +83 -0
- package/src/components/ds-table/components/core-table/index.ts +1 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.module.scss +115 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.tsx +70 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/ds-table-bulk-actions.types.ts +41 -0
- package/src/components/ds-table/components/ds-table-bulk-actions/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.module.scss +53 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.tsx +95 -0
- package/src/components/ds-table/components/ds-table-cell/ds-table-cell.types.ts +67 -0
- package/src/components/ds-table/components/ds-table-cell/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.module.scss +81 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.tsx +99 -0
- package/src/components/ds-table/components/ds-table-header/ds-table-header.types.ts +11 -0
- package/src/components/ds-table/components/ds-table-header/index.ts +2 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.module.scss +137 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.tsx +205 -0
- package/src/components/ds-table/components/ds-table-row/ds-table-row.types.ts +22 -0
- package/src/components/ds-table/components/ds-table-row/index.ts +2 -0
- package/src/components/ds-table/context/ds-table-context.tsx +39 -0
- package/src/components/ds-table/ds-table.module.scss +60 -0
- package/src/components/ds-table/ds-table.tsx +312 -0
- package/src/components/ds-table/ds-table.types.ts +377 -0
- package/src/components/ds-table/filters/adapters/create-checkbox-filter-adapter.tsx +116 -0
- package/src/components/ds-table/filters/adapters/create-dual-range-filter-adapter.tsx +171 -0
- package/src/components/ds-table/filters/adapters/create-filter-adapter.ts +131 -0
- package/src/components/ds-table/filters/adapters/index.ts +12 -0
- package/src/components/ds-table/filters/components/checkbox-filter/checkbox-filter.tsx +43 -0
- package/src/components/ds-table/filters/components/checkbox-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/index.ts +2 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.module.scss +25 -0
- package/src/components/ds-table/filters/components/range-filter/range-filter.tsx +79 -0
- package/src/components/ds-table/filters/hooks/index.ts +1 -0
- package/src/components/ds-table/filters/hooks/use-table-filters.ts +234 -0
- package/src/components/ds-table/filters/index.ts +50 -0
- package/src/components/ds-table/filters/types/filter-adapter.types.ts +111 -0
- package/src/components/ds-table/hooks/use-drag-and-drop.tsx +107 -0
- package/src/components/ds-table/index.ts +4 -0
- package/src/components/ds-table/styles/_variables.scss +17 -0
- package/src/components/ds-table/styles/shared/ds-table-shared.module.scss +16 -0
- package/src/components/ds-table/utils/column-styling.ts +30 -0
- package/src/components/ds-table/utils/create-columns-grid-template.ts +23 -0
- package/src/components/ds-table/utils/merge-refs.ts +15 -0
- package/src/components/ds-table/utils/story-data-generator.ts +91 -0
- package/src/components/ds-text-input/ds-text-input.module.scss +27 -0
- package/src/components/ds-text-input/ds-text-input.tsx +75 -0
- package/src/components/ds-text-input/ds-text-input.types.tsx +110 -0
- package/src/components/ds-text-input/index.ts +2 -0
- package/src/components/ds-textarea/ds-textarea.module.scss +5 -0
- package/src/components/ds-textarea/ds-textarea.tsx +49 -0
- package/src/components/ds-textarea/ds-textarea.types.tsx +67 -0
- package/src/components/ds-textarea/index.tsx +2 -0
- package/src/components/ds-toast/ds-toast-context.tsx +79 -0
- package/src/components/ds-toast/ds-toast.module.scss +134 -0
- package/src/components/ds-toast/ds-toast.tsx +54 -0
- package/src/components/ds-toast/ds-toast.types.tsx +53 -0
- package/src/components/ds-toast/index.ts +3 -0
- package/src/components/ds-tooltip/ds-tooltip.module.scss +19 -0
- package/src/components/ds-tooltip/ds-tooltip.tsx +33 -0
- package/src/components/ds-tooltip/ds-tooltip.types.tsx +12 -0
- package/src/components/ds-tooltip/index.tsx +2 -0
- package/src/components/ds-typography/ds-typography.config.ts +32 -0
- package/src/components/ds-typography/ds-typography.module.scss +93 -0
- package/src/components/ds-typography/ds-typography.tsx +27 -0
- package/src/components/ds-typography/ds-typography.types.tsx +27 -0
- package/src/components/ds-typography/index.tsx +2 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.module.scss +60 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.tsx +80 -0
- package/src/components/ds-vertical-tabs/ds-vertical-tabs.types.ts +69 -0
- package/src/components/ds-vertical-tabs/index.ts +7 -0
- package/src/index.ts +34 -0
- package/src/styles/_colors.scss +65 -0
- package/src/styles/_grid.scss +111 -0
- package/src/styles/_mixin.scss +1 -0
- package/src/styles/_reset.scss +38 -0
- package/src/styles/_root.scss +390 -0
- package/src/styles/_scrollbars.scss +41 -0
- package/src/styles/_spacing.scss +47 -0
- package/src/styles/_typography.scss +301 -0
- package/src/styles/_utilities.scss +33 -0
- package/src/styles/_variables.scss +44 -0
- package/src/styles/mixins/_bounding-box.scss +53 -0
- package/src/styles/mixins/_list.scss +74 -0
- package/src/styles/shared/_dropdown.scss +89 -0
- package/src/styles/shared/_input.scss +365 -0
- package/src/styles/shared/_modal.scss +123 -0
- package/src/styles/styles.scss +12 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Progress } from '@ark-ui/react';
|
|
4
|
+
import { DsTooltip } from '../../../ds-tooltip';
|
|
5
|
+
import { DsButton } from '../../../ds-button';
|
|
6
|
+
import { DsIcon } from '../../../ds-icon';
|
|
7
|
+
import { getErrorMessage } from '../../utils/get-error-message';
|
|
8
|
+
import type { FileUploadItemProps } from './file-upload-item.types';
|
|
9
|
+
import styles from './file-upload-item.module.scss';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Individual file upload item component
|
|
13
|
+
* Displays a single file with its upload status, progress, and action buttons
|
|
14
|
+
*/
|
|
15
|
+
export const FileUploadItem: React.FC<FileUploadItemProps> = ({
|
|
16
|
+
id,
|
|
17
|
+
name,
|
|
18
|
+
progress,
|
|
19
|
+
hideProgress = false,
|
|
20
|
+
status,
|
|
21
|
+
errors,
|
|
22
|
+
onCancel,
|
|
23
|
+
onRetry,
|
|
24
|
+
onRemove,
|
|
25
|
+
onDelete,
|
|
26
|
+
}) => {
|
|
27
|
+
const fileItemClass = classNames(styles.fileItemContent, {
|
|
28
|
+
[styles.fileItemError]: status === 'error',
|
|
29
|
+
[styles.fileItemCompleted]: status === 'completed',
|
|
30
|
+
[styles.fileItemUploading]: status === 'uploading',
|
|
31
|
+
[styles.fileItemInterrupted]: status === 'interrupted' || status === 'cancelled',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className={styles.fileItem}>
|
|
36
|
+
<div className={fileItemClass}>
|
|
37
|
+
<DsIcon className={styles.filePreview} icon="upload_file" size="tiny" />
|
|
38
|
+
|
|
39
|
+
<div className={styles.fileInfo}>
|
|
40
|
+
<div className={styles.fileName} title={name}>
|
|
41
|
+
{name}
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<Progress.Root value={status === 'error' ? 1 : progress} className={styles.progressBar}>
|
|
45
|
+
<Progress.Track className={styles.progressTrack}>
|
|
46
|
+
<Progress.Range className={styles.progressRange} />
|
|
47
|
+
</Progress.Track>
|
|
48
|
+
</Progress.Root>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
{(status === 'interrupted' || status === 'cancelled') && onRetry && (
|
|
52
|
+
<DsTooltip content="Retry">
|
|
53
|
+
<DsButton
|
|
54
|
+
type="button"
|
|
55
|
+
design="v1.2"
|
|
56
|
+
buttonType="tertiary"
|
|
57
|
+
aria-label={`Retry ${name} upload`}
|
|
58
|
+
onClick={() => onRetry(id)}
|
|
59
|
+
>
|
|
60
|
+
<DsIcon icon="refresh" size="small" />
|
|
61
|
+
</DsButton>
|
|
62
|
+
</DsTooltip>
|
|
63
|
+
)}
|
|
64
|
+
|
|
65
|
+
{(status === 'pending' || status === 'error' || status === 'interrupted') && (
|
|
66
|
+
<DsTooltip content="Remove">
|
|
67
|
+
<DsButton
|
|
68
|
+
type="button"
|
|
69
|
+
design="v1.2"
|
|
70
|
+
buttonType="tertiary"
|
|
71
|
+
aria-label={`Remove ${name} upload`}
|
|
72
|
+
onClick={() => onRemove?.(id)}
|
|
73
|
+
>
|
|
74
|
+
<DsIcon icon="do_not_disturb_on" size="small" />
|
|
75
|
+
</DsButton>
|
|
76
|
+
</DsTooltip>
|
|
77
|
+
)}
|
|
78
|
+
|
|
79
|
+
{status === 'uploading' && onCancel && (
|
|
80
|
+
<DsTooltip content="Cancel">
|
|
81
|
+
<DsButton
|
|
82
|
+
type="button"
|
|
83
|
+
design="v1.2"
|
|
84
|
+
buttonType="tertiary"
|
|
85
|
+
aria-label={`Cancel ${name} upload`}
|
|
86
|
+
onClick={() => onCancel(id)}
|
|
87
|
+
>
|
|
88
|
+
<DsIcon icon="close" size="small" />
|
|
89
|
+
</DsButton>
|
|
90
|
+
</DsTooltip>
|
|
91
|
+
)}
|
|
92
|
+
|
|
93
|
+
{status === 'completed' && (
|
|
94
|
+
<DsTooltip content="Delete">
|
|
95
|
+
<DsButton
|
|
96
|
+
type="button"
|
|
97
|
+
design="v1.2"
|
|
98
|
+
buttonType="tertiary"
|
|
99
|
+
aria-label={`Delete ${name}`}
|
|
100
|
+
onClick={() => onDelete?.(id)}
|
|
101
|
+
>
|
|
102
|
+
<DsIcon icon="delete" size="small" />
|
|
103
|
+
</DsButton>
|
|
104
|
+
</DsTooltip>
|
|
105
|
+
)}
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
{status === 'completed' && (
|
|
109
|
+
<div className={styles.completedText}>
|
|
110
|
+
<DsIcon icon="check_circle" size="tiny" filled />
|
|
111
|
+
Upload complete
|
|
112
|
+
</div>
|
|
113
|
+
)}
|
|
114
|
+
|
|
115
|
+
{status === 'interrupted' && (
|
|
116
|
+
<div className={styles.interruptedText}>
|
|
117
|
+
<DsIcon icon="info" size="tiny" filled />
|
|
118
|
+
Upload interrupted
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
|
|
122
|
+
{status === 'cancelled' && (
|
|
123
|
+
<div className={styles.interruptedText}>
|
|
124
|
+
<DsIcon icon="info" size="tiny" filled />
|
|
125
|
+
Upload cancelled
|
|
126
|
+
</div>
|
|
127
|
+
)}
|
|
128
|
+
|
|
129
|
+
{status === 'error' &&
|
|
130
|
+
errors?.length &&
|
|
131
|
+
errors.map((error) => (
|
|
132
|
+
<div key={error} className={styles.errorMessage}>
|
|
133
|
+
<DsIcon icon="cancel" size="tiny" filled />
|
|
134
|
+
{getErrorMessage(error)}
|
|
135
|
+
</div>
|
|
136
|
+
))}
|
|
137
|
+
|
|
138
|
+
{!hideProgress && status === 'uploading' && (
|
|
139
|
+
<span className={styles.progressText}>Uploading...({Math.round(progress)}%)</span>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { UploadFileStatus } from '../../ds-file-upload-api.types';
|
|
2
|
+
|
|
3
|
+
export interface FileUploadItemProps {
|
|
4
|
+
/** Unique identifier for the file */
|
|
5
|
+
id: string;
|
|
6
|
+
/** Name of the file */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Upload progress percentage (0-100) */
|
|
9
|
+
progress: number;
|
|
10
|
+
/** Current status of the file upload */
|
|
11
|
+
status: UploadFileStatus;
|
|
12
|
+
/** Array of error messages if any */
|
|
13
|
+
errors?: string[];
|
|
14
|
+
|
|
15
|
+
/** Whether to hide progress bar */
|
|
16
|
+
hideProgress?: boolean;
|
|
17
|
+
|
|
18
|
+
/** Callback when cancel button is clicked */
|
|
19
|
+
onCancel?: (fileId: string) => void;
|
|
20
|
+
/** Callback when retry button is clicked */
|
|
21
|
+
onRetry?: (fileId: string) => void;
|
|
22
|
+
/** Callback when remove button is clicked (for pending/error states) */
|
|
23
|
+
onRemove?: (fileId: string) => void;
|
|
24
|
+
/** Callback when delete button is clicked (for completed state) */
|
|
25
|
+
onDelete?: (fileId: string) => void;
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { FileUploadFileError } from '@ark-ui/react';
|
|
2
|
+
|
|
3
|
+
export type FileError = FileUploadFileError;
|
|
4
|
+
export type UploadFileStatus = 'pending' | 'uploading' | 'interrupted' | 'completed' | 'error' | 'cancelled';
|
|
5
|
+
export type FileMetadata = Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
export interface UploadedFile extends File {
|
|
8
|
+
id: string;
|
|
9
|
+
progress: number;
|
|
10
|
+
status: UploadFileStatus;
|
|
11
|
+
errors?: FileError[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface FileUploadOptions {
|
|
15
|
+
file: File;
|
|
16
|
+
fileId: string;
|
|
17
|
+
metadata?: FileMetadata;
|
|
18
|
+
onProgress?: (progress: number) => void;
|
|
19
|
+
signal?: AbortSignal;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface FileUploadResult {
|
|
23
|
+
url: string;
|
|
24
|
+
metadata?: FileMetadata;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* File upload adapter interface
|
|
29
|
+
* The upload method should:
|
|
30
|
+
* - resolve with FileUploadResult on success
|
|
31
|
+
* - reject with RetryableFileUploadError for transient failures
|
|
32
|
+
* - reject with FatalFileUploadError for permanent failures
|
|
33
|
+
*/
|
|
34
|
+
export interface FileUploadAdapter {
|
|
35
|
+
upload: (options: FileUploadOptions) => FileUploadResult | Promise<FileUploadResult>;
|
|
36
|
+
cancel?: (fileId: string) => void | Promise<void>;
|
|
37
|
+
delete?: (fileId: string) => void | Promise<void>;
|
|
38
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type React from 'react';
|
|
2
|
+
import type { DsFileUploadProps } from './ds-file-upload.types';
|
|
3
|
+
import { FileUpload } from './components/file-upload';
|
|
4
|
+
import { useFileUpload } from './hooks';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Design system File Upload component (with state management)
|
|
8
|
+
*/
|
|
9
|
+
const DsFileUpload: React.FC<DsFileUploadProps> = ({
|
|
10
|
+
adapter,
|
|
11
|
+
autoUpload = true,
|
|
12
|
+
maxConcurrent = 3,
|
|
13
|
+
metadata,
|
|
14
|
+
onFileUploadComplete,
|
|
15
|
+
onFileUploadError,
|
|
16
|
+
onFilesAdded,
|
|
17
|
+
onFileRemoved,
|
|
18
|
+
onFileDeleted,
|
|
19
|
+
onFileUploadCanceled,
|
|
20
|
+
onFileUploadRetried,
|
|
21
|
+
onAllFileUploadsComplete,
|
|
22
|
+
...props
|
|
23
|
+
}) => {
|
|
24
|
+
const { getProps } = useFileUpload({
|
|
25
|
+
adapter,
|
|
26
|
+
autoUpload,
|
|
27
|
+
maxConcurrent,
|
|
28
|
+
maxFiles: props.maxFiles,
|
|
29
|
+
metadata,
|
|
30
|
+
onFileUploadComplete,
|
|
31
|
+
onFileUploadError,
|
|
32
|
+
onAllFileUploadsComplete,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const fileUploadProps = getProps({
|
|
36
|
+
onFilesAdded,
|
|
37
|
+
onFileRemoved,
|
|
38
|
+
onFileDeleted,
|
|
39
|
+
onFileUploadCanceled,
|
|
40
|
+
onFileUploadRetried,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return <FileUpload {...fileUploadProps} {...props} />;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export default DsFileUpload;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { FileUploadProps } from './components/file-upload';
|
|
2
|
+
import type { FileMetadata, FileUploadAdapter, FileUploadResult } from './ds-file-upload-api.types';
|
|
3
|
+
|
|
4
|
+
export interface DsFileUploadProps extends Omit<
|
|
5
|
+
FileUploadProps,
|
|
6
|
+
| 'files'
|
|
7
|
+
| 'acceptedFiles'
|
|
8
|
+
| 'onFileAccept'
|
|
9
|
+
| 'onFileReject'
|
|
10
|
+
| 'onFileRemove'
|
|
11
|
+
| 'onFileDelete'
|
|
12
|
+
| 'onFileCancel'
|
|
13
|
+
| 'onFileRetry'
|
|
14
|
+
| 'uploadProgress'
|
|
15
|
+
> {
|
|
16
|
+
/** Upload adapter implementation */
|
|
17
|
+
adapter: FileUploadAdapter;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Whether to start upload automatically when files are added
|
|
21
|
+
* @default true
|
|
22
|
+
*/
|
|
23
|
+
autoUpload?: boolean;
|
|
24
|
+
|
|
25
|
+
/** Maximum number of concurrent uploads */
|
|
26
|
+
maxConcurrent?: number;
|
|
27
|
+
|
|
28
|
+
/** Additional metadata to attach to uploads */
|
|
29
|
+
metadata?: FileMetadata;
|
|
30
|
+
|
|
31
|
+
/** Called when a file upload completes successfully */
|
|
32
|
+
onFileUploadComplete?: (fileId: string, result: FileUploadResult) => void;
|
|
33
|
+
|
|
34
|
+
/** Called when a file upload fails */
|
|
35
|
+
onFileUploadError?: (fileId: string, error: string) => void;
|
|
36
|
+
|
|
37
|
+
/** Called when files are added (before upload) */
|
|
38
|
+
onFilesAdded?: (files: File[]) => void;
|
|
39
|
+
|
|
40
|
+
/** Called when a file is removed */
|
|
41
|
+
onFileRemoved?: (fileId: string) => void;
|
|
42
|
+
|
|
43
|
+
/** Called when a file is deleted */
|
|
44
|
+
onFileDeleted?: (fileId: string) => void;
|
|
45
|
+
|
|
46
|
+
/** Called when a file upload is canceled */
|
|
47
|
+
onFileUploadCanceled?: (fileId: string) => void;
|
|
48
|
+
|
|
49
|
+
/** Called when a file upload is retried */
|
|
50
|
+
onFileUploadRetried?: (fileId: string) => void;
|
|
51
|
+
|
|
52
|
+
/** Called when all file uploads are complete */
|
|
53
|
+
onAllFileUploadsComplete?: () => void;
|
|
54
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for file upload errors
|
|
3
|
+
*/
|
|
4
|
+
export abstract class FileUploadError extends Error {
|
|
5
|
+
abstract readonly isRetryable: boolean;
|
|
6
|
+
|
|
7
|
+
constructor(message: string) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = this.constructor.name;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Retryable file upload error - indicates the upload can be retried
|
|
15
|
+
* Examples: network errors, timeouts, temporary server errors (503), user cancellation
|
|
16
|
+
*/
|
|
17
|
+
export class RetryableFileUploadError extends FileUploadError {
|
|
18
|
+
readonly isRetryable = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fatal file upload error - indicates the upload should not be retried
|
|
23
|
+
* Examples: file type not allowed, file too large, invalid credentials, malformed response
|
|
24
|
+
*/
|
|
25
|
+
export class FatalFileUploadError extends FileUploadError {
|
|
26
|
+
readonly isRetryable = false;
|
|
27
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
FileUploadFileAcceptDetails,
|
|
4
|
+
FileUploadFileRejectDetails,
|
|
5
|
+
FileUploadFileRejection,
|
|
6
|
+
} from '@ark-ui/react';
|
|
7
|
+
import { createUploadedFile } from '../utils/file-factory';
|
|
8
|
+
import { isFileEqual } from '../utils/is-file-equal';
|
|
9
|
+
import type {
|
|
10
|
+
FileError,
|
|
11
|
+
FileMetadata,
|
|
12
|
+
FileUploadAdapter,
|
|
13
|
+
FileUploadResult,
|
|
14
|
+
UploadedFile,
|
|
15
|
+
UploadFileStatus,
|
|
16
|
+
} from '../ds-file-upload-api.types';
|
|
17
|
+
import { FileUploadError } from '../errors/file-upload-errors';
|
|
18
|
+
import type { FileUploadProps } from '../components/file-upload';
|
|
19
|
+
|
|
20
|
+
export interface UseFileUploadConfig {
|
|
21
|
+
adapter: FileUploadAdapter;
|
|
22
|
+
autoUpload?: boolean;
|
|
23
|
+
maxConcurrent?: number;
|
|
24
|
+
maxFiles?: number;
|
|
25
|
+
metadata?: FileMetadata;
|
|
26
|
+
onFileUploadComplete?: (fileId: string, result: FileUploadResult) => void;
|
|
27
|
+
onFileUploadError?: (fileId: string, error: string) => void;
|
|
28
|
+
onAllFileUploadsComplete?: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface UseFileUploadUserCallbacks {
|
|
32
|
+
onFilesAdded?: (files: File[]) => void;
|
|
33
|
+
onFileRemoved?: (fileId: string) => void;
|
|
34
|
+
onFileDeleted?: (fileId: string) => void;
|
|
35
|
+
onFileUploadCanceled?: (fileId: string) => void;
|
|
36
|
+
onFileUploadRetried?: (fileId: string) => void;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface UseFileUploadReturn {
|
|
40
|
+
files: UploadedFile[];
|
|
41
|
+
acceptedFiles: UploadedFile[];
|
|
42
|
+
addFiles: (newFiles: File[]) => UploadedFile[];
|
|
43
|
+
addRejectedFiles: (filesWithErrors: { file: File; errors: FileError[] }[]) => void;
|
|
44
|
+
removeFile: (fileId: string) => void;
|
|
45
|
+
deleteFile: (fileId: string) => Promise<void>;
|
|
46
|
+
uploadFile: (fileId: string) => Promise<void>;
|
|
47
|
+
uploadAll: () => Promise<void>;
|
|
48
|
+
cancelUpload: (fileId: string) => Promise<void>;
|
|
49
|
+
retryUpload: (fileId: string) => Promise<void>;
|
|
50
|
+
clearFiles: () => void;
|
|
51
|
+
/**
|
|
52
|
+
* Get props for FileUpload component with callback composition
|
|
53
|
+
* @param userCallbacks Optional user callbacks to compose with internal logic
|
|
54
|
+
* @returns Props ready to spread onto FileUpload component
|
|
55
|
+
*/
|
|
56
|
+
getProps: (
|
|
57
|
+
userCallbacks?: UseFileUploadUserCallbacks,
|
|
58
|
+
) => Pick<
|
|
59
|
+
FileUploadProps,
|
|
60
|
+
| 'files'
|
|
61
|
+
| 'acceptedFiles'
|
|
62
|
+
| 'onFileAccept'
|
|
63
|
+
| 'onFileReject'
|
|
64
|
+
| 'onFileRemove'
|
|
65
|
+
| 'onFileDelete'
|
|
66
|
+
| 'onFileCancel'
|
|
67
|
+
| 'onFileRetry'
|
|
68
|
+
>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* File upload state management hook
|
|
73
|
+
*/
|
|
74
|
+
export function useFileUpload({
|
|
75
|
+
adapter,
|
|
76
|
+
autoUpload = true,
|
|
77
|
+
maxConcurrent = 3,
|
|
78
|
+
maxFiles,
|
|
79
|
+
metadata,
|
|
80
|
+
onFileUploadComplete,
|
|
81
|
+
onFileUploadError,
|
|
82
|
+
onAllFileUploadsComplete,
|
|
83
|
+
}: UseFileUploadConfig): UseFileUploadReturn {
|
|
84
|
+
const [abortControllers] = useState(() => new Map<string, AbortController>());
|
|
85
|
+
const [files, setFiles] = useState<UploadedFile[]>([]);
|
|
86
|
+
const acceptedFiles = files.filter((file) => file.status !== 'error');
|
|
87
|
+
|
|
88
|
+
if (files.length && !files.some((f) => f.status === 'uploading')) {
|
|
89
|
+
onAllFileUploadsComplete?.();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const getAddedFiles = (newFiles: File[]) =>
|
|
93
|
+
newFiles.filter((file) => !acceptedFiles.some((existing) => isFileEqual(existing, file)));
|
|
94
|
+
|
|
95
|
+
const getDuplicatedFiles = (newFiles: File[]) =>
|
|
96
|
+
newFiles.filter((file) => {
|
|
97
|
+
const uploadedFile = file as UploadedFile;
|
|
98
|
+
if (uploadedFile.id) {
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
const found = files.filter((existing) => isFileEqual(existing, file));
|
|
102
|
+
if (found.length) {
|
|
103
|
+
const alreadyAdded = found.some((existing) => existing.errors?.includes('FILE_EXISTS'));
|
|
104
|
+
return !alreadyAdded;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const markDuplicates = (duplicates: File[]) => {
|
|
110
|
+
const duplicateFilesWithErrors = duplicates.map((file) => ({
|
|
111
|
+
file,
|
|
112
|
+
errors: ['FILE_EXISTS'],
|
|
113
|
+
}));
|
|
114
|
+
if (duplicateFilesWithErrors.length) {
|
|
115
|
+
addRejectedFiles(duplicateFilesWithErrors);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const markMaxFiles = (files: File[]): File[] => {
|
|
120
|
+
if (maxFiles === undefined) {
|
|
121
|
+
return files;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const currentAcceptedCount = acceptedFiles.length;
|
|
125
|
+
const availableSlots = maxFiles - currentAcceptedCount;
|
|
126
|
+
|
|
127
|
+
let filesToAdd: File[];
|
|
128
|
+
let rejectedFiles: File[];
|
|
129
|
+
|
|
130
|
+
if (availableSlots <= 0) {
|
|
131
|
+
filesToAdd = [];
|
|
132
|
+
rejectedFiles = files;
|
|
133
|
+
} else if (files.length > availableSlots) {
|
|
134
|
+
filesToAdd = files.slice(0, availableSlots);
|
|
135
|
+
rejectedFiles = files.slice(availableSlots);
|
|
136
|
+
} else {
|
|
137
|
+
return files;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const rejectedFilesWithErrors = rejectedFiles.map((file) => ({
|
|
141
|
+
file,
|
|
142
|
+
errors: ['TOO_MANY_FILES'],
|
|
143
|
+
}));
|
|
144
|
+
addRejectedFiles(rejectedFilesWithErrors);
|
|
145
|
+
|
|
146
|
+
return filesToAdd;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const addNewFiles = (filesToUpload: File[]): UploadedFile[] => {
|
|
150
|
+
const uploadedFiles = filesToUpload.map((file) => createUploadedFile(file, 'pending'));
|
|
151
|
+
setFiles((prev) => [...prev, ...uploadedFiles]);
|
|
152
|
+
return uploadedFiles;
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
const uploadNewFiles = (uploadedFiles: UploadedFile[]) => {
|
|
156
|
+
if (autoUpload) {
|
|
157
|
+
uploadedFiles.forEach((file) => {
|
|
158
|
+
void uploadSingleFile(file);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const addFiles = (newFiles: File[]): UploadedFile[] => {
|
|
164
|
+
const addedFiles = getAddedFiles(newFiles);
|
|
165
|
+
const duplicateFiles = getDuplicatedFiles(newFiles);
|
|
166
|
+
markDuplicates(duplicateFiles);
|
|
167
|
+
const filesToAdd = markMaxFiles(addedFiles);
|
|
168
|
+
const uploadedFiles = addNewFiles(filesToAdd);
|
|
169
|
+
uploadNewFiles(uploadedFiles);
|
|
170
|
+
return uploadedFiles;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const addRejectedFiles = (filesWithErrors: { file: File; errors: FileError[] }[]) => {
|
|
174
|
+
const newFileStates = filesWithErrors.map(({ file, errors }) =>
|
|
175
|
+
createUploadedFile(file, 'error', errors),
|
|
176
|
+
);
|
|
177
|
+
setFiles((prev) => [...prev, ...newFileStates]);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const updateFileProgress = (fileId: string, progress: number) => {
|
|
181
|
+
setFiles((prev) => prev.map((file) => (file.id === fileId ? { ...file, progress } : file)));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const updateFileStatus = (fileId: string, status: UploadFileStatus, error?: string) => {
|
|
185
|
+
setFiles((prev) =>
|
|
186
|
+
prev.map((file) =>
|
|
187
|
+
file.id === fileId
|
|
188
|
+
? {
|
|
189
|
+
...file,
|
|
190
|
+
status,
|
|
191
|
+
errors: error ? [...(file.errors || []), error] : file.errors,
|
|
192
|
+
}
|
|
193
|
+
: file,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const uploadFile = async (fileId: string) => {
|
|
199
|
+
const file = acceptedFiles.find((f) => f.id === fileId);
|
|
200
|
+
if (file) {
|
|
201
|
+
await uploadSingleFile(file);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const uploadSingleFile = async (file: UploadedFile) => {
|
|
206
|
+
const fileId = file.id;
|
|
207
|
+
const abortController = new AbortController();
|
|
208
|
+
abortControllers.set(fileId, abortController);
|
|
209
|
+
|
|
210
|
+
updateFileStatus(file.id, 'uploading');
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
const result = await adapter.upload({
|
|
214
|
+
file,
|
|
215
|
+
fileId,
|
|
216
|
+
metadata,
|
|
217
|
+
signal: abortController.signal,
|
|
218
|
+
onProgress: (progress) => {
|
|
219
|
+
updateFileProgress(fileId, progress);
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
updateFileStatus(fileId, 'completed');
|
|
224
|
+
updateFileProgress(fileId, 100);
|
|
225
|
+
onFileUploadComplete?.(fileId, result);
|
|
226
|
+
} catch (error) {
|
|
227
|
+
const errorMessage = error instanceof Error ? error.message : 'Upload failed';
|
|
228
|
+
// Determine status based on error type
|
|
229
|
+
// Unknown errors are treated as retryable (network errors, etc.)
|
|
230
|
+
let status: UploadFileStatus = 'interrupted';
|
|
231
|
+
if (error instanceof FileUploadError) {
|
|
232
|
+
status = error.isRetryable ? 'interrupted' : 'error';
|
|
233
|
+
}
|
|
234
|
+
updateFileStatus(fileId, status, errorMessage);
|
|
235
|
+
onFileUploadError?.(fileId, errorMessage);
|
|
236
|
+
} finally {
|
|
237
|
+
abortControllers.delete(fileId);
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const uploadAll = async () => {
|
|
242
|
+
const pendingFiles = files.filter((f) => f.status === 'pending');
|
|
243
|
+
|
|
244
|
+
for (let i = 0; i < pendingFiles.length; i += maxConcurrent) {
|
|
245
|
+
const batch = pendingFiles.slice(i, i + maxConcurrent);
|
|
246
|
+
await Promise.allSettled(batch.map((f) => uploadFile(f.id)));
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const cancelUpload = async (fileId: string) => {
|
|
251
|
+
abortControllers.get(fileId)?.abort();
|
|
252
|
+
abortControllers.delete(fileId);
|
|
253
|
+
|
|
254
|
+
if (adapter.cancel) {
|
|
255
|
+
await adapter.cancel(fileId);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
updateFileStatus(fileId, 'cancelled');
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const retryUpload = async (fileId: string) => {
|
|
262
|
+
updateFileStatus(fileId, 'pending');
|
|
263
|
+
updateFileProgress(fileId, 0);
|
|
264
|
+
await uploadFile(fileId);
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const removeFile = (fileId: string) => {
|
|
268
|
+
abortControllers.get(fileId)?.abort();
|
|
269
|
+
abortControllers.delete(fileId);
|
|
270
|
+
|
|
271
|
+
setFiles((prev) => prev.filter((file) => file.id !== fileId));
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const deleteFile = async (fileId: string) => {
|
|
275
|
+
if (adapter.delete) {
|
|
276
|
+
await adapter.delete(fileId);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
removeFile(fileId);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const clearFiles = () => {
|
|
283
|
+
abortControllers.forEach((controller) => controller.abort());
|
|
284
|
+
abortControllers.clear();
|
|
285
|
+
|
|
286
|
+
setFiles([]);
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const getProps = (userCallbacks?: UseFileUploadUserCallbacks) => {
|
|
290
|
+
const handleFileAccept = (details: FileUploadFileAcceptDetails) => {
|
|
291
|
+
const addedFiles = addFiles(details.files);
|
|
292
|
+
userCallbacks?.onFilesAdded?.(addedFiles);
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
const handleFileReject = (details: FileUploadFileRejectDetails) => {
|
|
296
|
+
addRejectedFiles(
|
|
297
|
+
details.files.map((f: FileUploadFileRejection) => ({
|
|
298
|
+
file: f.file,
|
|
299
|
+
errors: f.errors,
|
|
300
|
+
})),
|
|
301
|
+
);
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const handleFileRemove = (fileId: string) => {
|
|
305
|
+
removeFile(fileId);
|
|
306
|
+
userCallbacks?.onFileRemoved?.(fileId);
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
const handleFileDelete = async (fileId: string) => {
|
|
310
|
+
await deleteFile(fileId);
|
|
311
|
+
userCallbacks?.onFileDeleted?.(fileId);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const handleFileUploadCanceled = async (fileId: string) => {
|
|
315
|
+
await cancelUpload(fileId);
|
|
316
|
+
userCallbacks?.onFileUploadCanceled?.(fileId);
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const handleFileUploadRetried = async (fileId: string) => {
|
|
320
|
+
await retryUpload(fileId);
|
|
321
|
+
userCallbacks?.onFileUploadRetried?.(fileId);
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
files,
|
|
326
|
+
acceptedFiles,
|
|
327
|
+
onFileAccept: handleFileAccept,
|
|
328
|
+
onFileReject: handleFileReject,
|
|
329
|
+
onFileRemove: handleFileRemove,
|
|
330
|
+
onFileDelete: handleFileDelete,
|
|
331
|
+
onFileCancel: handleFileUploadCanceled,
|
|
332
|
+
onFileRetry: handleFileUploadRetried,
|
|
333
|
+
};
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
return {
|
|
337
|
+
files,
|
|
338
|
+
acceptedFiles,
|
|
339
|
+
addFiles,
|
|
340
|
+
addRejectedFiles,
|
|
341
|
+
removeFile,
|
|
342
|
+
deleteFile,
|
|
343
|
+
uploadFile,
|
|
344
|
+
uploadAll,
|
|
345
|
+
cancelUpload,
|
|
346
|
+
retryUpload,
|
|
347
|
+
clearFiles,
|
|
348
|
+
getProps,
|
|
349
|
+
};
|
|
350
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { default as DsFileUpload } from './ds-file-upload';
|
|
2
|
+
export { FileUpload } from './components/file-upload';
|
|
3
|
+
export * from './ds-file-upload.types';
|
|
4
|
+
export type { FileUploadAdapter, FileUploadOptions, FileUploadResult } from './ds-file-upload-api.types';
|
|
5
|
+
export * from './hooks';
|
|
6
|
+
export { FileUploadError, RetryableFileUploadError, FatalFileUploadError } from './errors/file-upload-errors';
|
|
7
|
+
export type { AcceptedFileType, FileExtension } from './types/accept-types';
|