@oslokommune/punkt-react 15.3.0 → 15.4.0
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 +26 -0
- package/dist/index.d.ts +17 -13
- package/dist/punkt-react.es.js +3477 -3461
- package/dist/punkt-react.umd.js +228 -228
- package/package.json +4 -4
- package/src/components/fileupload/DropZone.tsx +15 -4
- package/src/components/fileupload/FileUpload.tsx +18 -2
- package/src/components/fileupload/QueueDisplay.tsx +17 -5
- package/src/components/fileupload/QueueItemContent.tsx +6 -2
- package/src/components/index.ts +2 -0
- package/src/components/types.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-react",
|
|
3
|
-
"version": "15.
|
|
3
|
+
"version": "15.4.0",
|
|
4
4
|
"description": "React komponentbibliotek til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@lit-labs/ssr-dom-shim": "^1.2.1",
|
|
41
41
|
"@lit/react": "^1.0.7",
|
|
42
|
-
"@oslokommune/punkt-elements": "^15.
|
|
42
|
+
"@oslokommune/punkt-elements": "^15.4.0",
|
|
43
43
|
"classnames": "^2.5.1",
|
|
44
44
|
"prettier": "^3.3.3",
|
|
45
45
|
"react-hook-form": "^7.53.0"
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@eslint/eslintrc": "^3.3.3",
|
|
51
51
|
"@eslint/js": "^9.37.0",
|
|
52
52
|
"@oslokommune/punkt-assets": "^15.0.0",
|
|
53
|
-
"@oslokommune/punkt-css": "^15.
|
|
53
|
+
"@oslokommune/punkt-css": "^15.4.0",
|
|
54
54
|
"@testing-library/jest-dom": "^6.5.0",
|
|
55
55
|
"@testing-library/react": "^16.0.1",
|
|
56
56
|
"@testing-library/user-event": "^14.5.2",
|
|
@@ -109,5 +109,5 @@
|
|
|
109
109
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
110
110
|
},
|
|
111
111
|
"license": "MIT",
|
|
112
|
-
"gitHead": "
|
|
112
|
+
"gitHead": "0323bc56dc8636595ea48dd88dd966d363aac371"
|
|
113
113
|
}
|
|
@@ -16,6 +16,8 @@ import { PktIcon } from '..'
|
|
|
16
16
|
import { uiMultipleTexts, uiSingleTexts, uiThumbnailMultipleTexts, uiThumbnailSingleTexts } from './texts'
|
|
17
17
|
import { FileItem, TFileItemList, TUploadStrategy } from './types'
|
|
18
18
|
|
|
19
|
+
const DEFAULT_FORMATS_HELP_TEXT = '.PDF, .JPEG, .JPG, .PNG, .HEIC, .DOC, .DOCX, .ODT'
|
|
20
|
+
|
|
19
21
|
/**
|
|
20
22
|
* Props for the internal `DropZone` building block.
|
|
21
23
|
*
|
|
@@ -61,7 +63,7 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
61
63
|
onFilesAdded = () => {},
|
|
62
64
|
name,
|
|
63
65
|
uploadStrategy,
|
|
64
|
-
accept
|
|
66
|
+
accept,
|
|
65
67
|
isThumbnailView = false,
|
|
66
68
|
disabled = false,
|
|
67
69
|
srAnnouncementIds,
|
|
@@ -71,9 +73,18 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
71
73
|
) => {
|
|
72
74
|
const fileInputRef = useRef<HTMLInputElement>(null)
|
|
73
75
|
|
|
76
|
+
const resolvedAccept = typeof accept === 'string' && accept.trim().length > 0 ? accept : undefined
|
|
74
77
|
const acceptedFormatsReadableString = useMemo(
|
|
75
|
-
() =>
|
|
76
|
-
|
|
78
|
+
() =>
|
|
79
|
+
resolvedAccept
|
|
80
|
+
? resolvedAccept
|
|
81
|
+
.split(/\s*,\s*/)
|
|
82
|
+
.map((format) => format.trim())
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join(', ')
|
|
85
|
+
.toUpperCase()
|
|
86
|
+
: DEFAULT_FORMATS_HELP_TEXT,
|
|
87
|
+
[resolvedAccept],
|
|
77
88
|
)
|
|
78
89
|
|
|
79
90
|
useImperativeHandle(forwardedRef, () => fileInputRef.current! as HTMLInputElement)
|
|
@@ -181,7 +192,7 @@ export const DropZone = forwardRef<HTMLInputElement, IDropZoneProps>(
|
|
|
181
192
|
ref={fileInputRef}
|
|
182
193
|
multiple={multiple}
|
|
183
194
|
onChange={filesSelectedInDialog}
|
|
184
|
-
accept={
|
|
195
|
+
accept={resolvedAccept}
|
|
185
196
|
disabled={disabled}
|
|
186
197
|
name={(uploadStrategy === 'form' && name) || undefined} // Ikke sett name hvis uploadStrategy er 'custom' - ignorerer ved POST
|
|
187
198
|
aria-label={multiple ? 'Velg filer' : 'Velg fil'}
|
|
@@ -240,6 +240,21 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
240
240
|
|
|
241
241
|
const itemRenderer = typeof itemRendererProp === 'string' ? ItemRenderers[itemRendererProp] : itemRendererProp
|
|
242
242
|
const isThumbnailView = itemRendererProp === 'thumbnail'
|
|
243
|
+
const explicitAccept = typeof props.accept === 'string' ? props.accept.trim() : undefined
|
|
244
|
+
const acceptFromAllowedFormats = useMemo(() => {
|
|
245
|
+
if (!allowedFormats || allowedFormats.length === 0) return undefined
|
|
246
|
+
return allowedFormats
|
|
247
|
+
.map((format) => format.trim())
|
|
248
|
+
.filter(Boolean)
|
|
249
|
+
.map((format) => {
|
|
250
|
+
const normalized = format.toLowerCase()
|
|
251
|
+
if (normalized.includes('/')) return normalized
|
|
252
|
+
if (normalized.startsWith('.')) return normalized
|
|
253
|
+
return `.${normalized}`
|
|
254
|
+
})
|
|
255
|
+
.join(', ')
|
|
256
|
+
}, [allowedFormats])
|
|
257
|
+
const resolvedAcceptForDropZone = explicitAccept || acceptFromAllowedFormats
|
|
243
258
|
|
|
244
259
|
const effectiveRenameEnabled = renameFilesEnabled && !isThumbnailView
|
|
245
260
|
const effectiveCommentsEnabled = addCommentsEnabled && !isThumbnailView
|
|
@@ -375,7 +390,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
375
390
|
multiple={multiple}
|
|
376
391
|
uploadStrategy={uploadStrategy}
|
|
377
392
|
ref={forwardedRef}
|
|
378
|
-
accept={isThumbnailView ? '.jpeg, .jpg, .png, .gif, .webp, .heic' :
|
|
393
|
+
accept={isThumbnailView ? '.jpeg, .jpg, .png, .gif, .webp, .heic' : resolvedAcceptForDropZone}
|
|
379
394
|
isThumbnailView={isThumbnailView}
|
|
380
395
|
disabled={disabled}
|
|
381
396
|
srAnnouncementIds={srAnnouncementIds}
|
|
@@ -390,6 +405,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
390
405
|
cancelTransfer={onFileRemoved}
|
|
391
406
|
truncateTail={truncateTail}
|
|
392
407
|
transfers={transfers}
|
|
408
|
+
uploadStrategy={uploadStrategy}
|
|
393
409
|
ItemRenderer={itemRenderer}
|
|
394
410
|
enableImagePreview={isThumbnailView && enableImagePreview}
|
|
395
411
|
queueItemOperations={queueItemExtensions
|
|
@@ -411,7 +427,7 @@ export const PktFileUpload: FC<IPktFileUpload> = forwardRef<HTMLInputElement, IP
|
|
|
411
427
|
hasError={hasError}
|
|
412
428
|
optionalTag={optionalTag}
|
|
413
429
|
requiredTag={requiredTag}
|
|
414
|
-
className=
|
|
430
|
+
className={classNames('pkt-fileupload-wrapper', { 'pkt-fileupload-wrapper--full-width': fullWidth })}
|
|
415
431
|
>
|
|
416
432
|
{fileUploadContent}
|
|
417
433
|
</PktInputWrapper>
|
|
@@ -7,6 +7,11 @@ import { FilenameRenderer, ImagePreviewModal, ThumbnailRenderer } from './Subcom
|
|
|
7
7
|
import { TruncateContext } from './Truncate'
|
|
8
8
|
import { TFileAndTransfer, FileItem, TFileItemList, TFileTransfer, TItemRenderer, TQueueItemOperation } from './types'
|
|
9
9
|
|
|
10
|
+
const isImageFile = (item: TFileAndTransfer): boolean => {
|
|
11
|
+
if (item.file.type?.startsWith('image/')) return true
|
|
12
|
+
return /\.(jpe?g|png|gif|webp|heic|heif|bmp|svg)$/i.test(item.file.name || '')
|
|
13
|
+
}
|
|
14
|
+
|
|
10
15
|
// ============================================
|
|
11
16
|
// Helper: Transform files with transfer status
|
|
12
17
|
// ============================================
|
|
@@ -19,13 +24,17 @@ import { TFileAndTransfer, FileItem, TFileItemList, TFileTransfer, TItemRenderer
|
|
|
19
24
|
* - then errors
|
|
20
25
|
* - then everything else
|
|
21
26
|
*/
|
|
22
|
-
const useFilesAndTransfers = (
|
|
27
|
+
const useFilesAndTransfers = (
|
|
28
|
+
files: TFileItemList,
|
|
29
|
+
transfers: TFileTransfer[],
|
|
30
|
+
uploadStrategy: 'form' | 'custom',
|
|
31
|
+
): TFileAndTransfer[] => {
|
|
23
32
|
return useMemo(() => {
|
|
24
33
|
const mapped = files.map((fileItem: FileItem) => {
|
|
25
34
|
const transfer = transfers.find((t) => t.fileId === fileItem.fileId)
|
|
26
35
|
return {
|
|
27
36
|
...fileItem,
|
|
28
|
-
progress: transfer?.progress ?? 'queued',
|
|
37
|
+
progress: transfer?.progress ?? (uploadStrategy === 'form' ? 'done' : 'queued'),
|
|
29
38
|
errorMessage: transfer?.errorMessage,
|
|
30
39
|
showProgress: transfer?.showProgress,
|
|
31
40
|
lastProgress: transfer?.lastProgress,
|
|
@@ -35,7 +44,7 @@ const useFilesAndTransfers = (files: TFileItemList, transfers: TFileTransfer[]):
|
|
|
35
44
|
// Sort order: in-progress first, then errors, then the rest
|
|
36
45
|
const priority = { 'in-progress': 0, error: 1, idle: 2 } as const
|
|
37
46
|
return mapped.sort((a, b) => priority[getProgressState(a.progress)] - priority[getProgressState(b.progress)])
|
|
38
|
-
}, [files, transfers])
|
|
47
|
+
}, [files, transfers, uploadStrategy])
|
|
39
48
|
}
|
|
40
49
|
|
|
41
50
|
// ============================================
|
|
@@ -46,7 +55,7 @@ const useFilesAndTransfers = (files: TFileItemList, transfers: TFileTransfer[]):
|
|
|
46
55
|
const usePreviewableImages = (filesAndTransfers: TFileAndTransfer[], enabled: boolean): TFileAndTransfer[] => {
|
|
47
56
|
return useMemo(() => {
|
|
48
57
|
if (!enabled) return []
|
|
49
|
-
return filesAndTransfers.filter((item) => item.progress === 'done' && item
|
|
58
|
+
return filesAndTransfers.filter((item) => item.progress === 'done' && isImageFile(item))
|
|
50
59
|
}, [filesAndTransfers, enabled])
|
|
51
60
|
}
|
|
52
61
|
|
|
@@ -74,6 +83,8 @@ interface IQueueDisplay {
|
|
|
74
83
|
truncateTail?: number
|
|
75
84
|
/** Enable image preview modal (only affects thumbnail view). */
|
|
76
85
|
enableImagePreview?: boolean
|
|
86
|
+
/** Upload mode decides default queue state when no transfer exists. */
|
|
87
|
+
uploadStrategy?: 'form' | 'custom'
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
// ============================================
|
|
@@ -88,9 +99,10 @@ export const QueueDisplay: FC<IQueueDisplay> = ({
|
|
|
88
99
|
ItemRenderer = FilenameRenderer,
|
|
89
100
|
truncateTail,
|
|
90
101
|
enableImagePreview = false,
|
|
102
|
+
uploadStrategy = 'form',
|
|
91
103
|
}) => {
|
|
92
104
|
// Transform data
|
|
93
|
-
const filesAndTransfers = useFilesAndTransfers(files, transfers)
|
|
105
|
+
const filesAndTransfers = useFilesAndTransfers(files, transfers, uploadStrategy)
|
|
94
106
|
const previewableImages = usePreviewableImages(filesAndTransfers, enableImagePreview)
|
|
95
107
|
|
|
96
108
|
// State management
|
|
@@ -4,6 +4,11 @@ import { PktIcon } from '..'
|
|
|
4
4
|
import { OperationButton, TransferError, TransferInProgress } from './Subcomponents'
|
|
5
5
|
import { TFileAndTransfer, TFileId, TItemRenderer, TQueueItemOperation, TTransferItemInProgress } from './types'
|
|
6
6
|
|
|
7
|
+
const isImageFile = (item: TFileAndTransfer): boolean => {
|
|
8
|
+
if (item.file.type?.startsWith('image/')) return true
|
|
9
|
+
return /\.(jpe?g|png|gif|webp|heic|heif|bmp|svg)$/i.test(item.file.name || '')
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
type TProgressState = 'in-progress' | 'error' | 'idle'
|
|
8
13
|
|
|
9
14
|
export const getProgressState = (progress: TFileAndTransfer['progress']): TProgressState => {
|
|
@@ -170,8 +175,7 @@ export const QueueItemContent: FC<IQueueItemContent> = ({
|
|
|
170
175
|
onOpenPreview,
|
|
171
176
|
}) => {
|
|
172
177
|
const state = getProgressState(transferItem.progress)
|
|
173
|
-
const isPreviewable =
|
|
174
|
-
enableImagePreview && transferItem.progress === 'done' && transferItem.file.type.startsWith('image/')
|
|
178
|
+
const isPreviewable = enableImagePreview && transferItem.progress === 'done' && isImageFile(transferItem)
|
|
175
179
|
|
|
176
180
|
switch (state) {
|
|
177
181
|
case 'in-progress':
|
package/src/components/index.ts
CHANGED
|
@@ -11,6 +11,7 @@ export { PktCombobox } from './combobox/Combobox'
|
|
|
11
11
|
export { PktConsent } from './consent/Consent'
|
|
12
12
|
export { PktDatepicker } from './datepicker/Datepicker'
|
|
13
13
|
export { PktFileUpload } from './fileupload/FileUpload'
|
|
14
|
+
export { ItemRenderers } from './fileupload/QueueDisplay'
|
|
14
15
|
export { PktFooter } from './footer/Footer'
|
|
15
16
|
export { PktFooterSimple } from './footerSimple/FooterSimple'
|
|
16
17
|
export { PktHeader } from './header/Header'
|
|
@@ -43,3 +44,4 @@ export { PktTag } from './tag/Tag'
|
|
|
43
44
|
export { PktTextarea } from './textarea/Textarea'
|
|
44
45
|
export { PktTextinput } from './textinput/Textinput'
|
|
45
46
|
export * from './interfaces'
|
|
47
|
+
export * from './types'
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { FileItem } from './fileupload/types'
|
|
2
|
+
export type {
|
|
3
|
+
TFileAndTransfer,
|
|
4
|
+
TFileAttribute,
|
|
5
|
+
TFileAttributes,
|
|
6
|
+
TFileId,
|
|
7
|
+
TFileItemList,
|
|
8
|
+
TFileTransfer,
|
|
9
|
+
TItemRenderer,
|
|
10
|
+
TQueueItemExtension,
|
|
11
|
+
TQueueItemOperation,
|
|
12
|
+
TTransferItemInProgress,
|
|
13
|
+
TUploadStrategy,
|
|
14
|
+
} from './fileupload/types'
|