@synerise/ds-file-uploader 1.3.28 → 1.3.30

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 (3) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/CLAUDE.md +192 -0
  3. package/package.json +11 -10
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.3.30](https://github.com/Synerise/synerise-design/compare/@synerise/ds-file-uploader@1.3.29...@synerise/ds-file-uploader@1.3.30) (2026-08-11)
7
+
8
+ **Note:** Version bump only for package @synerise/ds-file-uploader
9
+
10
+ ## [1.3.29](https://github.com/Synerise/synerise-design/compare/@synerise/ds-file-uploader@1.3.28...@synerise/ds-file-uploader@1.3.29) (2026-07-23)
11
+
12
+ **Note:** Version bump only for package @synerise/ds-file-uploader
13
+
6
14
  ## [1.3.28](https://github.com/Synerise/synerise-design/compare/@synerise/ds-file-uploader@1.3.27...@synerise/ds-file-uploader@1.3.28) (2026-07-16)
7
15
 
8
16
  **Note:** Version bump only for package @synerise/ds-file-uploader
package/CLAUDE.md ADDED
@@ -0,0 +1,192 @@
1
+ # FileUploader (`@synerise/ds-file-uploader`)
2
+
3
+ > Drag-and-drop / click-to-upload file input with three visual variants: full drop-zone (`FileUploader`), avatar-preview (`AvatarUploader`), and compact button-row (`ItemUploader`). All variants share the same props shape and expose the same imperative ref.
4
+
5
+ ## Package structure
6
+
7
+ ```
8
+ src/
9
+ FileUploader.tsx — main drop-zone uploader (forwardRef)
10
+ FileUploader.types.ts — all shared types: props, ExtendedFile, FileUploaderRef, texts
11
+ FileUploader.styles.ts — styled-components for FileUploader
12
+ index.ts — public exports + FileUploaderStyles aggregate
13
+
14
+ FileView/
15
+ FileView.tsx — file row with progress/error/remove (used by FileUploader)
16
+ FileView.types.ts — FileViewProps
17
+ FileView.styles.ts
18
+ FileView.const.tsx — ICON_MAP, isPreviewableMimeType
19
+
20
+ AvatarUploader/
21
+ AvatarUploader.tsx — avatar-preview uploader (forwardRef)
22
+ AvatarUploader.styles.ts
23
+ FileViewAvatar/
24
+ FileViewAvatar.tsx — avatar + file row used by AvatarUploader
25
+ FileViewAvatar.types.ts — FileViewAvatarProps, FileViewAvatarTexts, PreviewableMimeType
26
+ FileViewAvatar.styles.ts
27
+ FileViewAvatar.const.tsx — ICON_MAP
28
+ FileViewAvatar.util.ts — isPreviewableMimeType
29
+
30
+ ItemUploader/
31
+ ItemUploader.tsx — compact button-row uploader (forwardRef)
32
+ ItemUploader.types.ts — re-exports ItemUploaderProps from FileUploader.types
33
+ ItemUploader.styles.ts
34
+ UploaderButton/
35
+ FileViewItem.tsx — compact file row used by ItemUploader
36
+ FileViewItem.styles.ts
37
+ FileViewItem.const.tsx — ICON_MAP, isPreviewableMimeType
38
+
39
+ __specs__/
40
+ FileUploader.spec.tsx — Vitest tests
41
+ ```
42
+
43
+ ## Public exports
44
+
45
+ ### `FileUploader` (default export)
46
+
47
+ Full-width drop-zone uploader. `forwardRef<FileUploaderRef, FileUploaderProps>`.
48
+
49
+ | Prop | Type | Default | Description |
50
+ |------|------|---------|-------------|
51
+ | `mode` | `'single' \| 'multi-medium' \| 'multi-large'` | `'single'` | Layout/behaviour mode. `multi-large` shows a large 160px drop area |
52
+ | `files` | `ExtendedFile[]` | `[]` | Controlled list of uploaded files to display |
53
+ | `onUpload` | `(files: FileWithContent[]) => void` | `undefined` | Called after drop/select with files enriched with `.content` |
54
+ | `onRemove` | `(file: FileWithContent, index: number) => void` | `undefined` | Called when user removes a file row |
55
+ | `accept` | `string[]` | `undefined` | Accepted MIME types (e.g. `['image/png', 'application/pdf']`) |
56
+ | `filesAmount` | `number` | `undefined` | Maximum number of files. Throws if set to `< 1`. Drop zone hides once limit is reached |
57
+ | `removable` | `boolean` | `true` | Show remove button on file rows |
58
+ | `disabled` | `boolean` | `undefined` | Disables the drop zone and file rows |
59
+ | `error` | `string` | `undefined` | Displays a global error message below the drop zone |
60
+ | `retry` | `boolean` | `undefined` | When `true` and a file has an error, shows a Retry button that re-opens the file dialog |
61
+ | `label` | `string` | `undefined` | Label above the drop zone |
62
+ | `tooltip` | `string` | `undefined` | Tooltip (ℹ icon) shown next to the label (requires `label`) |
63
+ | `description` | `string` | `undefined` | Hint text below the error area |
64
+ | `texts` | `FileUploaderTexts` | `undefined` | i18n overrides (see below). Falls back to `react-intl` message IDs |
65
+ | `className` | `string` | `undefined` | Extra CSS class; merged with `'ds-file-uploader'` |
66
+ | + HTML div attrs | — | — | Spread onto the outer `<div>` container |
67
+
68
+ **Imperative ref** (`FileUploaderRef`):
69
+ ```ts
70
+ { open: () => void; inputRef: RefObject<HTMLInputElement>; rootRef: RefObject<HTMLElement> }
71
+ ```
72
+ Call `ref.current.open()` to programmatically open the file picker.
73
+
74
+ ### `AvatarUploader`
75
+
76
+ `forwardRef<FileUploaderRef, FileUploaderProps>`. Same props as `FileUploader`. Shows uploaded files as avatar previews alongside a compact file row. Drop zone uses a picture-placeholder icon. "Add file" button label is **hardcoded** — not driven by `texts`. Does not accept `retry` in practice (it's in `FileUploaderProps` but not used in `AvatarUploader`).
77
+
78
+ ### `ItemUploader`
79
+
80
+ `forwardRef<FileUploaderRef, ItemUploaderProps>`. Same as `FileUploader` except:
81
+ - `mode` type: `'single' | 'multi'` (not `'multi-medium' | 'multi-large'`)
82
+ - Shows a ghost-primary "Add file" button instead of a drop-zone area. Button label is **hardcoded** — not driven by `texts`.
83
+ - Does not render a `description` below error messages (it's passed to `FileViewItem` but `FileViewItem` doesn't render it).
84
+
85
+ ### `FileUploaderStyles`
86
+
87
+ Aggregated style object exported for consumers who need to extend or override styled-components:
88
+ ```ts
89
+ {
90
+ FileUploader: FileUploaderStyles,
91
+ FileView: FileViewStyles,
92
+ ItemUploader: ItemUploaderStyles,
93
+ AvatarUploader: AvatarUploaderStyles,
94
+ }
95
+ ```
96
+
97
+ ### Types exported
98
+
99
+ | Type | Description |
100
+ |------|-------------|
101
+ | `FileUploaderProps` | Main props interface (extends `WithHTMLAttributes<HTMLDivElement, ..>`) |
102
+ | `ItemUploaderProps` | Same as `FileUploaderProps` with narrower `mode` |
103
+ | `ExtendedFile` | `{ file: FileWithContent; error?: string; disabled?: boolean; progress?: number; success?: boolean }` |
104
+ | `FileWithContent` | `File & { content?: FileContent }` |
105
+ | `FileContent` | `string \| ArrayBuffer \| null` |
106
+ | `FileUploaderRef` | `{ open; inputRef; rootRef }` |
107
+
108
+ ### `FileUploaderTexts` shape
109
+
110
+ | Key | Default (react-intl) | Description |
111
+ |-----|----------------------|-------------|
112
+ | `buttonLabel` | `"Upload file"` | Drop-zone button label (compact mode) |
113
+ | `buttonLabelLarge` | `"Upload file"` | Drop-zone label (multi-large, no files) |
114
+ | `buttonDescription` | `"Description"` | Drop-zone sub-label (multi-large) |
115
+ | `size` | `"Size"` | Prefix before file size in file row |
116
+ | `removeTooltip` | `"Remove"` | Tooltip on remove icon |
117
+ | `cancelText` | `"Cancel"` | Popconfirm cancel button |
118
+ | `okText` | `"OK"` | Popconfirm confirm button |
119
+ | `removeConfirmTitle` | `"Remove"` | Popconfirm title |
120
+ | `fileWeight` | `"File weight"` | Label for file weight during upload |
121
+ | `retryLabel` | `"Retry"` | Label on retry button |
122
+ | `percent` | — | Current upload percentage (number) — passed to `ProgressBar` |
123
+
124
+ ## Usage patterns
125
+
126
+ ```tsx
127
+ import FileUploader, { AvatarUploader, ItemUploader } from '@synerise/ds-file-uploader';
128
+ import type { ExtendedFile, FileWithContent } from '@synerise/ds-file-uploader';
129
+ import { useRef, useState } from 'react';
130
+
131
+ const [files, setFiles] = useState<ExtendedFile[]>([]);
132
+ const uploaderRef = useRef(null);
133
+
134
+ // Standard drop-zone
135
+ <FileUploader
136
+ mode="multi-medium"
137
+ files={files}
138
+ accept={['image/png', 'image/jpeg']}
139
+ filesAmount={5}
140
+ onUpload={(newFiles: FileWithContent[]) =>
141
+ setFiles(prev => [..prev, ..newFiles.map(f => ({ file: f }))])
142
+ }
143
+ onRemove={(_, index) => setFiles(prev => prev.filter((_, i) => i !== index))}
144
+ label="Attachments"
145
+ tooltip="Max 5 files"
146
+ />
147
+
148
+ // Avatar variant
149
+ <AvatarUploader
150
+ mode="single"
151
+ files={files}
152
+ onUpload={..}
153
+ onRemove={..}
154
+ />
155
+
156
+ // Compact button variant
157
+ <ItemUploader
158
+ mode="multi"
159
+ files={files}
160
+ onUpload={..}
161
+ onRemove={..}
162
+ />
163
+
164
+ // Programmatic open
165
+ <FileUploader ref={uploaderRef} .. />
166
+ <button onClick={() => uploaderRef.current?.open()}>Browse</button>
167
+ ```
168
+
169
+ ## Styling
170
+
171
+ Styles in `FileUploader.styles.ts` (and per-variant siblings). Uses `props.theme.palette` tokens from `@synerise/ds-core` throughout — no hardcoded hex values. Drop area transitions colours on `isDragActive`, hover, focus, error, and disabled states.
172
+
173
+ ## Key dependencies
174
+
175
+ - `react-dropzone` — core drag-and-drop, file input wiring, `isDragActive`, `open()`, `inputRef`/`rootRef`
176
+ - `filesize.js` — human-readable file size strings in `FileView`
177
+ - `react-intl` — default text strings (peer dependency)
178
+ - `@synerise/ds-popconfirm` — remove confirmation dialog in `FileView`
179
+ - `@synerise/ds-progress-bar` — upload progress bar in `FileView`
180
+ - `@synerise/ds-loader` — spinner in `FileViewAvatar` / `FileViewItem` during upload
181
+ - `@synerise/ds-tooltip` — remove/retry tooltips
182
+
183
+ ## Implementation notes
184
+
185
+ - **File content reading:** `onUpload` is called with `FileWithContent[]`. For `.txt` files only, `content` is populated with the file text via `FileReader.readAsText`. For all other MIME types, `content` is `null`. This happens automatically before `onUpload` fires.
186
+ - **`filesAmount` enforcement:** When more files are dropped than `filesAmount - files.length`, the upload is silently rejected and a local error "To many files uploaded" [sic] is appended. The prop mutation (`filesAmount = 1`) + `throw` when `< 1` is a runtime guard but does not prevent re-render.
187
+ - **`FileViewAvatar` memory leak:** `URL.createObjectURL(data.file)` is called on every render without a matching `URL.revokeObjectURL` call — leaks memory for long-lived file lists.
188
+ - **"Add file" hardcoded:** Both `AvatarUploader` and `ItemUploader` render a hardcoded "Add file" button label that cannot be overridden via `texts`.
189
+ - **`retry` in `FileView`:** When `retry=true` and a file row has `error`, the retry button spreads `getRootProps()` (from `useDropzone`) to open the file picker — it first calls `onRemove` to clear the errored entry, then re-opens the dialog.
190
+ - **`texts.percent`** is typed `number` (not `ReactNode`) because it's passed directly to `ProgressBar`'s `percent` prop.
191
+ - **`FileViewAvatarTexts`** is not exported from `index.ts` — accessible only as a deep import.
192
+ - **Uses Vitest** for testing.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synerise/ds-file-uploader",
3
- "version": "1.3.28",
3
+ "version": "1.3.30",
4
4
  "description": "FileUploader UI Component for the Synerise Design System",
5
5
  "license": "ISC",
6
6
  "repository": "Synerise/synerise-design",
@@ -17,6 +17,7 @@
17
17
  "files": [
18
18
  "/dist",
19
19
  "CHANGELOG.md",
20
+ "CLAUDE.md",
20
21
  "README.md",
21
22
  "package.json",
22
23
  "LICENSE.md"
@@ -41,14 +42,14 @@
41
42
  ],
42
43
  "types": "dist/index.d.ts",
43
44
  "dependencies": {
44
- "@synerise/ds-button": "^1.5.34",
45
- "@synerise/ds-icon": "^1.18.4",
46
- "@synerise/ds-loader": "^1.0.17",
47
- "@synerise/ds-popconfirm": "^1.4.1",
48
- "@synerise/ds-progress-bar": "^1.3.12",
49
- "@synerise/ds-tooltip": "^1.5.3",
50
- "@synerise/ds-typography": "^1.1.26",
51
- "@synerise/ds-utils": "^1.10.1",
45
+ "@synerise/ds-button": "^1.5.36",
46
+ "@synerise/ds-icon": "^1.18.5",
47
+ "@synerise/ds-loader": "^1.0.18",
48
+ "@synerise/ds-popconfirm": "^1.4.3",
49
+ "@synerise/ds-progress-bar": "^1.3.14",
50
+ "@synerise/ds-tooltip": "^1.5.5",
51
+ "@synerise/ds-typography": "^1.1.28",
52
+ "@synerise/ds-utils": "^1.10.2",
52
53
  "filesize.js": "^2.0.0",
53
54
  "react-dropzone": "^10.2.1"
54
55
  },
@@ -59,5 +60,5 @@
59
60
  "styled-components": "^5.3.3",
60
61
  "vitest": "4"
61
62
  },
62
- "gitHead": "a81ab6519d49a3dea9c0cfebcdc9104cbb4f4226"
63
+ "gitHead": "6df24ed12cd5f276d8eccde730591807982e3385"
63
64
  }