diff --git a/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx b/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx
index fd62aa24129..a22e1fc40ab 100644
--- a/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx
+++ b/packages/dnb-design-system-portal/src/docs/uilib/components/upload/Examples.tsx
@@ -44,7 +44,7 @@ export const UploadPrefilledFileList = () => (
errorMessage: 'This is no real file!',
},
])
- }, [])
+ }, [setFiles])
return
}
@@ -128,7 +128,7 @@ export const UploadRemoveFile = () => (
reader.readAsDataURL(file)
})
- }, [files])
+ }, [files, images])
return (
@@ -368,8 +368,15 @@ export const UploadOnFileClick = () => (
{
file: createMockFile('1501870.jpg', 123, 'image/png'),
},
+ {
+ file: createMockFile(
+ 'file-name-that-is-very-long-and-has-letters.png',
+ 123,
+ 'image/png',
+ ),
+ },
])
- }, [])
+ }, [setFiles])
async function mockAsyncFileFetching({ fileItem }) {
const request = createRequest()
@@ -385,13 +392,11 @@ export const UploadOnFileClick = () => (
}
return (
- <>
-
- >
+
)
}
diff --git a/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx b/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx
index b612481f121..e6640c42534 100644
--- a/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx
+++ b/packages/dnb-eufemia/src/components/dropdown/stories/Dropdown.stories.tsx
@@ -1006,6 +1006,17 @@ export const GlobalStatusExample = () => {
)
}
+export const TypesExample = () => {
+ interface MyInterface {
+ content: string
+ selected_key: string
+ }
+
+ const myData: MyInterface[] = []
+
+ return
+}
+
export function InDialog() {
const list = Array(30).fill('Content')
return (
diff --git a/packages/dnb-eufemia/src/components/upload/UploadFileListCell.tsx b/packages/dnb-eufemia/src/components/upload/UploadFileListCell.tsx
index f01b542aac9..78cd45065d1 100644
--- a/packages/dnb-eufemia/src/components/upload/UploadFileListCell.tsx
+++ b/packages/dnb-eufemia/src/components/upload/UploadFileListCell.tsx
@@ -1,4 +1,4 @@
-import React, { useRef } from 'react'
+import React, { useCallback, useRef } from 'react'
import classnames from 'classnames'
// Components
@@ -25,7 +25,7 @@ import {
import { UploadFile, UploadFileNative } from './types'
// Shared
-import { getPreviousSibling, warn } from '../../shared/component-helper'
+import { getPreviousSibling } from '../../shared/component-helper'
import useUpload from './useUpload'
import { getFileTypeFromExtension } from './UploadVerify'
import UploadFileLink from './UploadFileListLink'
@@ -96,26 +96,20 @@ const UploadFileListCell = ({
const cellRef = useRef()
const exists = useExistsHighlight(id, file)
- const handleDisappearFocus = () => {
- try {
- const cellElement = cellRef.current
- const focusElement = getPreviousSibling(
- '.dnb-upload',
- cellElement
- ).querySelector(
- '.dnb-upload__file-input-button'
- ) as HTMLButtonElement
- focusElement.focus()
- } catch (e) {
- warn(e)
- }
- }
+ const handleDisappearFocus = useCallback(() => {
+ const cellElement = cellRef.current
+ const focusElement = getPreviousSibling(
+ '.dnb-upload',
+ cellElement
+ )?.querySelector('.dnb-upload__file-input-button') as HTMLButtonElement
+ focusElement?.focus({ preventScroll: true })
+ }, [cellRef])
- const onDeleteHandler = () => {
+ const onDeleteHandler = useCallback(() => {
handleDisappearFocus()
onDelete()
- }
+ }, [handleDisappearFocus, onDelete])
return (
{
it('renders the delete button', () => {
render()
- const element = screen.getByRole('button')
+ const element = document.querySelector('button')
expect(element).toBeInTheDocument()
})
@@ -316,7 +316,7 @@ describe('UploadFileListCell', () => {
/>
)
- const element = screen.getByRole('button')
+ const element = document.querySelector('button')
expect(element.textContent).toMatch(deleteButtonText)
})
@@ -324,7 +324,7 @@ describe('UploadFileListCell', () => {
it('renders button as tertiary', () => {
render()
- const element = screen.getByRole('button')
+ const element = document.querySelector('button')
expect(element.className).toMatch('dnb-button--tertiary')
})
@@ -357,7 +357,7 @@ describe('UploadFileListCell', () => {
onDelete={onDelete}
/>
)
- const element = screen.getByRole('button')
+ const element = document.querySelector('button')
fireEvent.click(element)
@@ -374,7 +374,7 @@ describe('UploadFileListCell', () => {
}}
/>
)
- const element = screen.getByRole('button')
+ const element = document.querySelector('button')
expect(element).toBeDisabled()
})
@@ -394,5 +394,45 @@ describe('UploadFileListCell', () => {
document.querySelector('.dnb-progress-indicator')
).not.toBeInTheDocument()
})
+
+ it('should set focus when clicking the delete button', () => {
+ const MockComponent = () => {
+ return (
+
+
+
+
+ )
+ }
+ const { rerender } = render()
+
+ const removeButton = document.querySelector('button')
+ const uploadButton = document.querySelector(
+ '.dnb-upload__file-input-button'
+ )
+
+ expect(document.body).toHaveFocus()
+
+ fireEvent.click(removeButton)
+ expect(uploadButton).toHaveFocus()
+
+ const focus = jest.fn()
+ jest
+ .spyOn(HTMLElement.prototype, 'focus')
+ .mockImplementationOnce(focus)
+
+ rerender()
+
+ fireEvent.click(removeButton)
+ expect(focus).toHaveBeenCalledTimes(1)
+ expect(focus).toHaveBeenCalledWith({ preventScroll: true })
+ })
})
})
diff --git a/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-sbanken-have-to-match-anchor-looks-when-displaying-a-button.snap.png b/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-sbanken-have-to-match-anchor-looks-when-displaying-a-button.snap.png
index 2439b622f99..a3e64fddc35 100644
Binary files a/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-sbanken-have-to-match-anchor-looks-when-displaying-a-button.snap.png and b/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-sbanken-have-to-match-anchor-looks-when-displaying-a-button.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-ui-have-to-match-anchor-looks-when-displaying-a-button.snap.png b/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-ui-have-to-match-anchor-looks-when-displaying-a-button.snap.png
index bb5f72a88ea..f6cd1dbda97 100644
Binary files a/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-ui-have-to-match-anchor-looks-when-displaying-a-button.snap.png and b/packages/dnb-eufemia/src/components/upload/__tests__/__image_snapshots__/upload-for-ui-have-to-match-anchor-looks-when-displaying-a-button.snap.png differ
diff --git a/packages/dnb-eufemia/src/components/upload/__tests__/__snapshots__/Upload.test.tsx.snap b/packages/dnb-eufemia/src/components/upload/__tests__/__snapshots__/Upload.test.tsx.snap
index 077c4f8f28c..fa91facb674 100644
--- a/packages/dnb-eufemia/src/components/upload/__tests__/__snapshots__/Upload.test.tsx.snap
+++ b/packages/dnb-eufemia/src/components/upload/__tests__/__snapshots__/Upload.test.tsx.snap
@@ -818,13 +818,13 @@ button .dnb-form-status__text {
}
.dnb-upload__file-cell__content {
display: flex;
- flex-direction: row;
+ column-gap: var(--spacing-small);
justify-content: space-between;
align-items: center;
}
.dnb-upload__file-cell__content__left {
display: flex;
- flex-direction: row;
+ column-gap: var(--spacing-small);
align-items: center;
}
.dnb-upload__file-cell__content__left .dnb-icon {
@@ -839,7 +839,6 @@ button .dnb-form-status__text {
.dnb-upload__file-cell__text-container {
display: flex;
flex-direction: column;
- margin-left: var(--spacing-small);
}
.dnb-upload__file-cell__text-container--loading {
font-size: var(--font-size-basis);
diff --git a/packages/dnb-eufemia/src/components/upload/style/dnb-upload.scss b/packages/dnb-eufemia/src/components/upload/style/dnb-upload.scss
index 8f979a744fa..eaa735a06df 100644
--- a/packages/dnb-eufemia/src/components/upload/style/dnb-upload.scss
+++ b/packages/dnb-eufemia/src/components/upload/style/dnb-upload.scss
@@ -106,14 +106,14 @@
&__content {
display: flex;
- flex-direction: row;
+ column-gap: var(--spacing-small);
justify-content: space-between;
align-items: center;
&__left {
display: flex;
- flex-direction: row;
+ column-gap: var(--spacing-small);
align-items: center;
.dnb-icon {
@@ -134,8 +134,6 @@
display: flex;
flex-direction: column;
- margin-left: var(--spacing-small);
-
&--loading {
font-size: var(--font-size-basis);
}
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx
index eceb14fa643..32796efcf1d 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/Upload.tsx
@@ -118,13 +118,13 @@ function UploadComponent(props: Props) {
onFileClick,
} = rest
- const { files: fileContext, setFiles } = useUpload(id)
+ const { files, setFiles } = useUpload(id)
const filesRef = useRef>()
useEffect(() => {
- filesRef.current = fileContext
- }, [fileContext])
+ filesRef.current = files
+ }, [files])
useEffect(() => {
// Files stored in session storage will not have a property (due to serialization).
@@ -137,7 +137,8 @@ function UploadComponent(props: Props) {
const handleChangeAsync = useCallback(
async (existingFiles: UploadValue) => {
// Filter out existing files
- const existingFileIds = fileContext?.map((file) => file.id) || []
+ const existingFileIds =
+ filesRef.current?.map((file) => file.id) || []
const newFiles = existingFiles.filter(
(file) => !existingFileIds.includes(file.id)
)
@@ -145,7 +146,7 @@ function UploadComponent(props: Props) {
if (newFiles.length > 0) {
// Set loading
setFiles([
- ...fileContext,
+ ...filesRef.current,
...updateFileLoadingState(newFiles, { isLoading: true }),
])
@@ -171,7 +172,7 @@ function UploadComponent(props: Props) {
handleChange(existingFiles)
}
},
- [fileContext, setFiles, fileHandler, handleChange]
+ [files, setFiles, fileHandler, handleChange]
)
const changeHandler = useCallback(
diff --git a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx
index 27400b7e807..1dc6d31013a 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Field/Upload/__tests__/Upload.test.tsx
@@ -8,6 +8,7 @@ import nbNOForms from '../../../constants/locales/nb-NO'
import nbNOShared from '../../../../../shared/locales/nb-NO'
import userEvent from '@testing-library/user-event'
import { UploadValue } from '../Upload'
+import { wait } from '../../../../../core/jest/jestSetup'
const nbForms = nbNOForms['nb-NO']
const nbShared = nbNOShared['nb-NO']
@@ -90,6 +91,32 @@ describe('Field.Upload', () => {
expect(onFileClick).toHaveBeenCalledTimes(1)
})
+ it('should display spinner for an async onFileClick event', async () => {
+ const onFileClick = jest.fn(async () => {
+ await wait(1)
+ })
+
+ render(
+
+ )
+
+ const fileButton = document.querySelector(
+ '.dnb-upload__file-cell button'
+ )
+
+ await waitFor(() => {
+ fireEvent.click(fileButton)
+ expect(
+ document.querySelector('.dnb-progress-indicator')
+ ).toBeInTheDocument()
+ })
+ })
+
it('should render files given in data context', () => {
render(
{
})
})
+ it('should add new files from fileHandler with async function while removing file', async () => {
+ const asyncOnFileDelete = jest.fn(async () => {
+ await wait(1)
+ })
+
+ const newFile = (fileId) => {
+ return createMockFile(`${fileId}.png`, 100, 'image/png')
+ }
+
+ const filesFirstUpload = [newFile(0)]
+
+ const filesSecondUpload = [newFile(1)]
+
+ const asyncValidatorResolvingWithSuccess = (files) =>
+ new Promise((resolve) =>
+ setTimeout(() => {
+ const filesToResolve = files.map((file, i) => {
+ return {
+ file: file,
+ id: 'server_generated_id_' + i,
+ exists: false,
+ }
+ })
+ resolve(filesToResolve)
+ }, 1)
+ )
+
+ const asyncFileHandlerFnSuccess = jest
+ .fn(asyncValidatorResolvingWithSuccess)
+ .mockReturnValueOnce(
+ asyncValidatorResolvingWithSuccess(filesFirstUpload)
+ )
+ .mockReturnValueOnce(
+ asyncValidatorResolvingWithSuccess(filesSecondUpload)
+ )
+
+ render(
+
+ )
+
+ const element = getRootElement()
+
+ await waitFor(() => {
+ // upload the first file
+ fireEvent.drop(element, {
+ dataTransfer: {
+ files: filesFirstUpload,
+ },
+ })
+ })
+
+ await waitFor(() => {
+ expect(
+ document.querySelectorAll('.dnb-upload__file-cell').length
+ ).toBe(1)
+ })
+
+ await waitFor(() => {
+ // upload the second file
+ fireEvent.drop(element, {
+ dataTransfer: {
+ files: filesSecondUpload,
+ },
+ })
+ })
+
+ await waitFor(() => {
+ expect(
+ document.querySelectorAll('.dnb-upload__file-cell').length
+ ).toBe(2)
+ })
+
+ await waitFor(() => {
+ // delete the first file
+ fireEvent.click(
+ document
+ .querySelectorAll('.dnb-upload__file-cell')[0]
+ .querySelector('button')
+ )
+ })
+
+ await waitFor(() => {
+ expect(
+ document.querySelectorAll('.dnb-upload__file-cell').length
+ ).toBe(1)
+ })
+ })
+
it('should not add existing file using fileHandler with async function', async () => {
const file = createMockFile('fileName.png', 100, 'image/png')
diff --git a/packages/dnb-eufemia/src/extensions/forms/Tools/Log.tsx b/packages/dnb-eufemia/src/extensions/forms/Tools/Log.tsx
index eb3646b4483..271b29ed75c 100644
--- a/packages/dnb-eufemia/src/extensions/forms/Tools/Log.tsx
+++ b/packages/dnb-eufemia/src/extensions/forms/Tools/Log.tsx
@@ -54,13 +54,15 @@ function replaceUndefinedValues(
): unknown {
if (typeof value === 'undefined') {
return replaceWith
+ } else if (Array.isArray(value)) {
+ return value.map((item) => replaceUndefinedValues(item, replaceWith))
} else if (value && typeof value === 'object' && value !== replaceWith) {
return {
...value,
...Object.fromEntries(
Object.entries(value).map(([k, v]) => [
k,
- replaceUndefinedValues(v),
+ replaceUndefinedValues(v, replaceWith),
])
),
}
diff --git a/packages/dnb-eufemia/src/extensions/forms/Tools/__tests__/Log.test.tsx b/packages/dnb-eufemia/src/extensions/forms/Tools/__tests__/Log.test.tsx
new file mode 100644
index 00000000000..a2cd83f9c5a
--- /dev/null
+++ b/packages/dnb-eufemia/src/extensions/forms/Tools/__tests__/Log.test.tsx
@@ -0,0 +1,43 @@
+import React from 'react'
+import { render } from '@testing-library/react'
+import { Form, Tools } from '../../'
+
+describe('Tools.Log', () => {
+ it('should render data context', () => {
+ const data = { foo: 'bar' }
+ render(
+
+
+
+ )
+
+ const element = document.querySelector('output')
+ expect(element.textContent).toBe(JSON.stringify(data, null, 2) + ' ')
+ })
+
+ it('should format array with square brackets', () => {
+ const data = { foo: ['bar', 'baz'] }
+ render(
+
+
+
+ )
+
+ const element = document.querySelector('output')
+ expect(element.textContent).toBe(JSON.stringify(data, null, 2) + ' ')
+ expect(element.textContent).toContain('[')
+ expect(element.textContent).toContain('}')
+ })
+
+ it('should format "undefined"', () => {
+ const data = { foo: { bar: undefined } }
+ render(
+
+
+
+ )
+
+ const element = document.querySelector('output')
+ expect(element.textContent).toContain('"bar": "undefined"')
+ })
+})
diff --git a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts
index cdad59a81cc..071df9ebd98 100644
--- a/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts
+++ b/packages/dnb-eufemia/src/fragments/drawer-list/DrawerList.d.ts
@@ -16,7 +16,7 @@ export type DrawerListValue = string | number;
/** @deprecated use `DrawerListDataArrayObject` */
export type DrawerListDataObject = DrawerListDataArrayObject;
export type DrawerListDataArrayObject = {
- [customProperty: string]: unknown;
+ [customProperty: string]: any;
selected_value?: string | React.ReactNode;
selectedKey?: string | number;
selected_key?: string | number;