Skip to content

Commit

Permalink
feat: Adds i18n provider to file upload and file token group (#3026)
Browse files Browse the repository at this point in the history
Co-authored-by: Katie George <[email protected]>
  • Loading branch information
katiegeorge and Katie George authored Nov 19, 2024
1 parent dc7000b commit 1b5b3e9
Show file tree
Hide file tree
Showing 25 changed files with 438 additions and 87 deletions.
18 changes: 6 additions & 12 deletions pages/file-token-group/permutations.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import React from 'react';

import FileTokenGroup, { FileTokenGroupProps } from '~components/file-token-group';
import { I18nProvider } from '~components/i18n';
import messages from '~components/i18n/messages/all.en';

import createPermutations from '../utils/permutations';
import PermutationsView from '../utils/permutations-view';
Expand All @@ -11,7 +13,7 @@ import ScreenshotArea from '../utils/screenshot-area';
const file1 = new File([new Blob(['demo content 1'])], 'demo file 1', { type: 'image/*' });
const file2 = new File([new Blob(['demo content 2'])], 'demo file 2 long name here test', { type: 'image/*' });

const permutations = createPermutations<Omit<FileTokenGroupProps, 'onDismiss' | 'i18nStrings'>>([
const permutations = createPermutations<Omit<FileTokenGroupProps, 'onDismiss'>>([
{
items: [[{ file: file1 }]],
showFileLastModified: [true, false],
Expand All @@ -34,27 +36,19 @@ const permutations = createPermutations<Omit<FileTokenGroupProps, 'onDismiss' |
showFileLastModified: [true, false],
showFileSize: [true, false],
alignment: ['horizontal', 'vertical'],
limit: [undefined, 0],
},
]);

export default function FileTokenGroupPermutations() {
return (
<>
<I18nProvider messages={[messages]} locale="en">
<h1>FileTokenGroup permutations</h1>
<ScreenshotArea disableAnimations={true}>
<PermutationsView
permutations={permutations}
render={permutation => (
<FileTokenGroup
i18nStrings={{
errorIconAriaLabel: 'Error',
warningIconAriaLabel: 'Warning',
removeFileAriaLabel: (fileIndex: number) => `Remove file ${fileIndex + 1}`,
formatFileSize: () => `1.01 MB`,
formatFileLastModified: () => '2020-01-01T00:00:00',
limitShowFewer: 'Show fewer',
limitShowMore: 'Show more',
}}
onDismiss={() => {
/*empty handler to suppress react controlled property warning*/
}}
Expand All @@ -63,6 +57,6 @@ export default function FileTokenGroupPermutations() {
)}
/>
</ScreenshotArea>
</>
</I18nProvider>
);
}
82 changes: 42 additions & 40 deletions pages/file-upload/scenario-standalone.page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import React, { useEffect, useRef, useState } from 'react';

import { Alert, Box, Checkbox, FileUpload, FileUploadProps, FormField, Header } from '~components';
import { I18nProvider } from '~components/i18n';
import messages from '~components/i18n/messages/all.en';
import SpaceBetween from '~components/space-between';

import { PageNotifications, useContractFilesForm } from './page-helpers';
import { i18nStrings } from './shared';
import { validateContractFiles } from './validations';

export default function FileUploadScenarioStandalone() {
Expand All @@ -26,49 +27,50 @@ export default function FileUploadScenarioStandalone() {
}, [hasError]);

return (
<Box margin="xl">
<SpaceBetween size="xl">
<Header variant="h1">File upload scenario: Standalone</Header>
<I18nProvider messages={[messages]} locale="en">
<Box margin="xl">
<SpaceBetween size="xl">
<Header variant="h1">File upload scenario: Standalone</Header>

<Alert statusIconAriaLabel="Info" header="Scenario description">
File upload is used as a standalone component. It supports synchronous client-side validation as per
constraints. Additionally, the component imitates server-side validation triggered for image files.
</Alert>
<Alert statusIconAriaLabel="Info" header="Scenario description">
File upload is used as a standalone component. It supports synchronous client-side validation as per
constraints. Additionally, the component imitates server-side validation triggered for image files.
</Alert>

<PageNotifications status={formState.status} />
<PageNotifications status={formState.status} />

<Checkbox checked={acceptMultiple} onChange={event => setAcceptMultiple(event.detail.checked)}>
Accept multiple files
</Checkbox>
<Checkbox checked={acceptMultiple} onChange={event => setAcceptMultiple(event.detail.checked)}>
Accept multiple files
</Checkbox>

<Checkbox checked={verticalAlignment} onChange={event => setVerticalAlignment(event.detail.checked)}>
Vertical alignment
</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}
value={formState.files}
onChange={event => {
formState.onFilesChange(event.detail.value);
formState.onUploadFiles(!validateContractFiles(event.detail.value) ? event.detail.value : []);
}}
accept="application/pdf, image/png, image/jpeg"
showFileSize={true}
showFileLastModified={true}
showFileThumbnail={true}
i18nStrings={i18nStrings}
{...contractsErrors}
constraintText="File size must not exceed 250 KB. Combined file size must not exceed 750 KB"
/>
</FormField>
</SpaceBetween>
</Box>
<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}
value={formState.files}
onChange={event => {
formState.onFilesChange(event.detail.value);
formState.onUploadFiles(!validateContractFiles(event.detail.value) ? event.detail.value : []);
}}
accept="application/pdf, image/png, image/jpeg"
showFileSize={true}
showFileLastModified={true}
showFileThumbnail={true}
{...contractsErrors}
constraintText="File size must not exceed 250 KB. Combined file size must not exceed 750 KB"
/>
</FormField>
</SpaceBetween>
</Box>
</I18nProvider>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7675,7 +7675,7 @@ exports[`Documenter definition for file-token-group matches the snapshot: file-t
},
{
"name": "removeFileAriaLabel",
"optional": false,
"optional": true,
"type": "(fileIndex: number) => string",
},
{
Expand All @@ -7687,7 +7687,7 @@ exports[`Documenter definition for file-token-group matches the snapshot: file-t
"type": "object",
},
"name": "i18nStrings",
"optional": false,
"optional": true,
"type": "FileTokenGroupProps.I18nStrings",
},
{
Expand Down Expand Up @@ -7889,7 +7889,7 @@ is provided by its parent form field component.
"properties": [
{
"name": "dropzoneText",
"optional": false,
"optional": true,
"type": "(multiple: boolean) => string",
},
{
Expand All @@ -7909,22 +7909,22 @@ is provided by its parent form field component.
},
{
"name": "limitShowFewer",
"optional": false,
"optional": true,
"type": "string",
},
{
"name": "limitShowMore",
"optional": false,
"optional": true,
"type": "string",
},
{
"name": "removeFileAriaLabel",
"optional": false,
"optional": true,
"type": "(fileIndex: number) => string",
},
{
"name": "uploadButtonText",
"optional": false,
"optional": true,
"type": "(multiple: boolean) => string",
},
{
Expand All @@ -7936,7 +7936,7 @@ is provided by its parent form field component.
"type": "object",
},
"name": "i18nStrings",
"optional": false,
"optional": true,
"type": "FileUploadProps.I18nStrings",
},
{
Expand Down
34 changes: 32 additions & 2 deletions src/file-token-group/__tests__/file-token-group.test.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React, { useState } from 'react';
import { act, fireEvent, render as testingLibraryRender } from '@testing-library/react';
import { act, fireEvent, render as testingLibraryRender, screen } from '@testing-library/react';

import '../../__a11y__/to-validate-a11y';
import FileTokenGroup, { FileTokenGroupProps } from '../../../lib/components/file-token-group';
import TestI18nProvider from '../../../lib/components/i18n/testing';
import createWrapper from '../../../lib/components/test-utils/dom';

import styles from '../../../lib/components/file-token-group/styles.css.js';
Expand Down Expand Up @@ -137,7 +138,6 @@ describe('File upload tokens', () => {
items: [{ file: file1 }],
showFileSize: true,
i18nStrings: {
...defaultProps.i18nStrings,
formatFileSize: sizeInBytes => `${sizeInBytes} bytes`,
},
});
Expand Down Expand Up @@ -260,6 +260,36 @@ describe('Focusing behavior', () => {
});
});

describe('i18n', () => {
test('supports providing custom i18n strings', () => {
const { container } = testingLibraryRender(
<TestI18nProvider
messages={{
'file-token-group': {
'i18nStrings.limitShowMore': 'Custom show more',
'i18nStrings.removeFileAriaLabel': `Custom remove file {fileIndex}`,
'i18nStrings.errorIconAriaLabel': 'Custom error',
'i18nStrings.warningIconAriaLabel': 'Custom warning',
},
}}
>
<FileTokenGroup
items={[{ file: file1, errorText: 'Error' }, { file: file2, warningText: 'Warning' }, { file: file2 }]}
limit={2}
onDismiss={onDismiss}
/>
</TestI18nProvider>
);

const wrapper = createWrapper(container).findFileTokenGroup()!;

expect(wrapper.getElement()).toHaveTextContent('Custom show more');
expect(wrapper.findFileToken(1)!.findRemoveButton()!.getElement()).toHaveAccessibleName('Custom remove file 1');
expect(screen.getByLabelText('Custom error')).not.toBeNull();
expect(screen.getByLabelText('Custom warning')).not.toBeNull();
});
});

describe('a11y', () => {
test('empty', async () => {
const wrapper = render({ items: [] });
Expand Down
20 changes: 11 additions & 9 deletions src/file-token-group/file-token.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import testUtilStyles from './test-classes/styles.css.js';

export namespace FileTokenProps {
export interface I18nStrings {
removeFileAriaLabel: (fileIndex: number) => string;
removeFileAriaLabel?: (fileIndex: number) => string;
errorIconAriaLabel?: string;
warningIconAriaLabel?: string;
formatFileSize?: (sizeInBytes: number) => string;
Expand All @@ -39,7 +39,7 @@ export interface FileTokenProps extends BaseComponentProps {
warningText?: React.ReactNode;
loading?: boolean;
readOnly?: boolean;
i18nStrings: FileTokenProps.I18nStrings;
i18nStrings?: FileTokenProps.I18nStrings;
dismissLabel?: string;
alignment?: TokenGroupProps.Alignment;
groupContainsImage?: boolean;
Expand All @@ -63,8 +63,8 @@ function InternalFileToken({
isImage,
index,
}: FileTokenProps) {
const formatFileSize = i18nStrings.formatFileSize ?? defaultFormatters.formatFileSize;
const formatFileLastModified = i18nStrings.formatFileLastModified ?? defaultFormatters.formatFileLastModified;
const formatFileSize = i18nStrings?.formatFileSize ?? defaultFormatters.formatFileSize;
const formatFileLastModified = i18nStrings?.formatFileLastModified ?? defaultFormatters.formatFileLastModified;

const errorId = useUniqueId('error');
const warningId = useUniqueId('warning');
Expand All @@ -75,6 +75,10 @@ function InternalFileToken({
const fileNameContainerRef = useRef<HTMLDivElement>(null);
const [showTooltip, setShowTooltip] = useState(false);

const getDismissLabel = (fileIndex: number) => {
return i18nStrings?.removeFileAriaLabel?.(fileIndex);
};

function isEllipsisActive() {
const span = fileNameRef.current;
const container = fileNameContainerRef.current;
Expand Down Expand Up @@ -166,17 +170,15 @@ function InternalFileToken({
</InternalSpaceBetween>
</div>
</InternalBox>
{onDismiss && !readOnly && (
<DismissButton dismissLabel={i18nStrings.removeFileAriaLabel(index)} onDismiss={onDismiss} />
)}
{onDismiss && !readOnly && <DismissButton dismissLabel={getDismissLabel(index)} onDismiss={onDismiss} />}
</div>
{errorText && (
<FormFieldError id={errorId} errorIconAriaLabel={i18nStrings.errorIconAriaLabel}>
<FormFieldError id={errorId} errorIconAriaLabel={i18nStrings?.errorIconAriaLabel}>
{errorText}
</FormFieldError>
)}
{showWarning && (
<FormFieldWarning id={warningId} warningIconAriaLabel={i18nStrings.warningIconAriaLabel}>
<FormFieldWarning id={warningId} warningIconAriaLabel={i18nStrings?.warningIconAriaLabel}>
{warningText}
</FormFieldWarning>
)}
Expand Down
4 changes: 2 additions & 2 deletions src/file-token-group/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export interface FileTokenGroupProps extends BaseComponentProps {
* * `formatFileSize` (function): (Optional) A function that takes file size in bytes, and produces a formatted string.
* * `formatFileLastModified` (function): (Optional) A function that takes the files last modified date, and produces a formatted string.
*/
i18nStrings: FileTokenGroupProps.I18nStrings;
i18nStrings?: FileTokenGroupProps.I18nStrings;
}

export namespace FileTokenGroupProps {
Expand All @@ -78,7 +78,7 @@ export namespace FileTokenGroupProps {
limitShowFewer?: string;
limitShowMore?: string;

removeFileAriaLabel: (fileIndex: number) => string;
removeFileAriaLabel?: (fileIndex: number) => string;
errorIconAriaLabel?: string;
warningIconAriaLabel?: string;
formatFileSize?: (sizeInBytes: number) => string;
Expand Down
Loading

0 comments on commit 1b5b3e9

Please sign in to comment.