Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: File token group [internal] #2950

Merged
merged 24 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pages/file-upload/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const permutations = createPermutations<Omit<FileUploadProps, 'dismissAriaLabel'
value: [
[
new File([new Blob(['demo content 1'])], 'demo file 1', { type: 'image/*' }),
new File([new Blob(['demo content 2'])], 'demo file 2', { type: 'image/*' }),
new File([new Blob(['demo content 2'])], 'demo file 2 with long file name', { type: 'image/*' }),
],
],
showFileSize: [true],
Expand All @@ -37,6 +37,7 @@ const permutations = createPermutations<Omit<FileUploadProps, 'dismissAriaLabel'
constraintText: ['File size must not exceed 1 MB'],
fileErrors: [undefined, ['File size is above 1 MB', 'File size is above 1 MB']],
fileWarnings: [undefined, ['File type is image', 'File type is image']],
fileTokenAlignment: ['vertical', 'horizontal'],
},
]);

Expand Down
6 changes: 6 additions & 0 deletions pages/file-upload/scenario-standalone.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { validateContractFiles } from './validations';
export default function FileUploadScenarioStandalone() {
const contractsRef = useRef<FileUploadProps.Ref>(null);
const [acceptMultiple, setAcceptMultiple] = useState(true);
const [verticalAlignment, setVerticalAlignment] = useState(true);
const formState = useContractFilesForm();

const contractsValidationErrors = validateContractFiles(formState.files);
Expand Down Expand Up @@ -40,11 +41,16 @@ export default function FileUploadScenarioStandalone() {
Accept multiple files
</Checkbox>

<Checkbox checked={verticalAlignment} onChange={event => setVerticalAlignment(event.detail.checked)}>
Vertical alignment
</Checkbox>

<FormField
label={acceptMultiple ? 'Contracts' : 'Contract'}
description={acceptMultiple ? 'Upload your contract with all amendments' : 'Upload your contract'}
>
<FileUpload
fileTokenAlignment={verticalAlignment ? 'vertical' : 'horizontal'}
ref={contractsRef}
multiple={acceptMultiple}
tokenLimit={3}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7453,6 +7453,20 @@ is provided by its parent form field component.
"optional": true,
"type": "ReadonlyArray<null | string>",
},
{
"description": "Alignment of the file tokens. Defaults to "vertical".",
"inlineType": {
"name": "FileUploadProps.FileTokenAlignment",
"type": "union",
"values": [
"vertical",
"horizontal",
],
},
"name": "fileTokenAlignment",
"optional": true,
"type": "string",
},
{
"description": "An array of file warnings corresponding to the files in the \`value\`.",
"name": "fileWarnings",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,6 @@ exports[`test-utils selectors 1`] = `
"awsui_root_gwq0h",
],
"file-upload": [
"awsui_file-option-last-modified_ezgb4",
"awsui_file-option-name_ezgb4",
"awsui_file-option-size_ezgb4",
"awsui_file-option-thumbnail-image_ezgb4",
"awsui_hints_1ubbm",
"awsui_root_1ubbm",
],
Expand Down Expand Up @@ -339,6 +335,10 @@ exports[`test-utils selectors 1`] = `
"awsui_dropdown_qwoo0",
"awsui_file-input-button_181f9",
"awsui_file-input_181f9",
"awsui_file-option-last-modified_ofwwb",
"awsui_file-option-name_ofwwb",
"awsui_file-option-size_ofwwb",
"awsui_file-option-thumbnail_ofwwb",
"awsui_filter-container_z5mul",
"awsui_filtering-match-highlight_1p2cx",
"awsui_handle_sdha6",
Expand All @@ -365,6 +365,7 @@ exports[`test-utils selectors 1`] = `
"awsui_root_1qprf",
"awsui_root_1t44z",
"awsui_root_1tk3k",
"awsui_root_9f1dn",
"awsui_root_qwoo0",
"awsui_root_vrgzu",
"awsui_selectable-item_15o6u",
Expand All @@ -374,6 +375,7 @@ exports[`test-utils selectors 1`] = `
"awsui_ticks--y_f0fot",
"awsui_title_1kjc7",
"awsui_toggle_gfwv3",
"awsui_token_ofwwb",
"awsui_value_10ipo",
"awsui_wrapper_1wepg",
],
Expand Down
118 changes: 0 additions & 118 deletions src/file-upload/__tests__/file-upload.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import FileUpload, { FileUploadProps } from '../../../lib/components/file-upload
import createWrapper from '../../../lib/components/test-utils/dom';
import FileDropzoneWrapper from '../../../lib/components/test-utils/dom/internal/file-dropzone';

import tokenListSelectors from '../../../lib/components/internal/components/token-list/styles.selectors.js';

jest.mock('@cloudscape-design/component-toolkit/internal', () => ({
...jest.requireActual('@cloudscape-design/component-toolkit/internal'),
warnOnce: jest.fn(),
Expand Down Expand Up @@ -214,11 +212,6 @@ describe('FileUpload input', () => {
});

describe('File upload tokens', () => {
test('token list is not rendered when `multiple=false`', () => {
const wrapper = render({ multiple: false, value: [file1] });
expect(wrapper.find(tokenListSelectors.root)).toBeNull();
});

test(`when multiple=true all file tokens are shown`, () => {
const wrapper = render({ multiple: true, value: [file1, file2] });

Expand All @@ -231,100 +224,6 @@ describe('File upload tokens', () => {
expect(wrapper.findFileToken(2)!.getElement()).toHaveTextContent('test-file-2.txt');
});

test('dev warning is issued when using multiple files with a singular file upload', () => {
render({ value: [file1, file2] });

expect(warnOnce).toHaveBeenCalledTimes(1);
expect(warnOnce).toHaveBeenCalledWith('FileUpload', 'Value must be an array of size 0 or 1 when `multiple=false`.');
});

test('file token remove button has ARIA label set', () => {
const wrapper = render({ value: [file1] });
expect(wrapper.findFileToken(1)!.findRemoveButton()!.getElement()).toHaveAccessibleName('Remove file 1');
});

test('selected file can be removed', () => {
const wrapperSingular = render({ value: [file1] });
wrapperSingular.findFileToken(1)!.findRemoveButton()!.click();

expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ detail: { value: [] } }));

const wrapperMultiple = render({ multiple: true, value: [file1, file2] });
wrapperMultiple.findFileToken(1)!.findRemoveButton()!.click();

expect(onChange).toHaveBeenCalledWith(expect.objectContaining({ detail: { value: [file2] } }));
});

test('file token only shows name by default', () => {
const wrapper = render({ value: [file1] });

expect(wrapper.findFileToken(1)!.findFileName().getElement()).toHaveTextContent('test-file-1.txt');
expect(wrapper.findFileToken(1)!.findFileSize()).toBe(null);
expect(wrapper.findFileToken(1)!.findFileLastModified()).toBe(null);
expect(wrapper.findFileToken(1)!.findFileThumbnail()).toBe(null);
});

test('file token metadata can be opt-in', () => {
const wrapper = render({
value: [file1],
showFileSize: true,
showFileLastModified: true,
});
expect(wrapper.findFileToken(1)!.findFileName().getElement()).toHaveTextContent('test-file-1.txt');
expect(wrapper.findFileToken(1)!.findFileSize()!.getElement()).toHaveTextContent('0.01 KB');
expect(wrapper.findFileToken(1)!.findFileLastModified()!.getElement()).toHaveTextContent('2020-06-01T00:00:00');
});

test('thumbnail is only shown when file type starts with "image"', () => {
expect(
render({ value: [file1], showFileThumbnail: true })
.findFileToken(1)!
.findFileThumbnail()
).toBe(null);

expect(
render({ value: [file2], showFileThumbnail: true })
.findFileToken(1)!
.findFileThumbnail()
).not.toBe(null);
});

test('selected file size can be customized', () => {
const wrapper = render({
value: [file1],
showFileSize: true,
i18nStrings: {
...defaultProps.i18nStrings,
formatFileSize: sizeInBytes => `${sizeInBytes} bytes`,
},
});
expect(wrapper.findFileToken(1)!.findFileSize()!.getElement()).toHaveTextContent('14 bytes');
});

test('selected file last update timestamp can be customized', () => {
const wrapper = render({
value: [file1],
showFileLastModified: true,
i18nStrings: {
...defaultProps.i18nStrings,
formatFileLastModified: date => `${date.getFullYear()} year`,
},
});
expect(wrapper.findFileToken(1)!.findFileLastModified()!.getElement()).toHaveTextContent('2020 year');
});

test('the `tokenLimit` property controls the number of tokens shown by default', () => {
const wrapper = render({ multiple: true, value: [file1, file2], tokenLimit: 1 });
expect(wrapper.findFileTokens()).toHaveLength(1);
expect(wrapper.getElement().textContent).toContain('Show more files');
});

test('file tokens have aria labels set to file names', () => {
const wrapper = render({ multiple: true, value: [file1, file2] });
expect(wrapper.findFileToken(1)!.getElement()).toHaveAttribute('aria-label', file1.name);
expect(wrapper.findFileToken(2)!.getElement()).toHaveAttribute('aria-label', file2.name);
});

test('file errors are associated to file tokens', () => {
const wrapper = render({ multiple: true, value: [file1, file2], fileErrors: ['Error 1', 'Error 2'] });
expect(wrapper.findFileToken(1)!.getElement()).toHaveAccessibleDescription('Error 1');
Expand Down Expand Up @@ -358,23 +257,6 @@ describe('File upload dropzone', () => {
});

describe('Focusing behavior', () => {
test.each([1, 2])(
`Focus is dispatched to the next token when the token before it is removed, tokenLimit=%s`,
tokenLimit => {
const wrapper = renderStateful({ multiple: true, value: [file1, file2], tokenLimit });
wrapper.findFileToken(1)!.findRemoveButton().click();

expect(wrapper.findFileToken(1)!.findRemoveButton().getElement()).toHaveFocus();
}
);

test('Focus is dispatched to the previous token when removing the token at the end', () => {
const wrapper = renderStateful({ multiple: true, value: [file1, file2] });
wrapper.findFileToken(2)!.findRemoveButton().click();

expect(wrapper.findFileToken(1)!.findRemoveButton().getElement()).toHaveFocus();
});

test.each([false, true])(
'Focus is dispatched to the file input when the last token is removed, multiple=%s',
multiple => {
Expand Down
47 changes: 0 additions & 47 deletions src/file-upload/file-option/index.tsx

This file was deleted.

34 changes: 0 additions & 34 deletions src/file-upload/file-option/styles.scss

This file was deleted.

6 changes: 6 additions & 0 deletions src/file-upload/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export interface FileUploadProps extends BaseComponentProps, FormFieldCommonVali
* An array of file warnings corresponding to the files in the `value`.
*/
fileWarnings?: ReadonlyArray<null | string>;
/**
* Alignment of the file tokens. Defaults to "vertical".
*/
fileTokenAlignment?: FileUploadProps.FileTokenAlignment;
/**
* An object containing all the localized strings required by the component:
* * `uploadButtonText` (function): A function to render the text of the file upload button. It takes `multiple` attribute to define plurality.
Expand All @@ -89,6 +93,8 @@ export namespace FileUploadProps {
file: File;
}

export type FileTokenAlignment = 'vertical' | 'horizontal';

export interface I18nStrings {
uploadButtonText: (multiple: boolean) => string;
dropzoneText: (multiple: boolean) => string;
Expand Down
Loading
Loading