From 4c67e7fa97bc15f1dff0f087fc043dd30730eca7 Mon Sep 17 00:00:00 2001 From: Savut Sang Date: Thu, 30 Jan 2025 12:39:17 -0500 Subject: [PATCH 1/4] feat(AttachedFile): new component --- packages/react/package.json | 1 + .../attached-file/attached-file-classes.ts | 20 ++ .../attached-file/attached-file.tsx | 258 ++++++++++++++++++ packages/react/src/components/icon/icon.tsx | 6 + packages/react/src/i18n/translations.ts | 16 ++ packages/react/src/index.ts | 1 + .../src/themes/tokens/component-tokens.ts | 3 + .../tokens/component/attached-file-tokens.ts | 24 ++ .../stories/attached-file.stories.tsx | 92 +++++++ yarn.lock | 1 + 10 files changed, 422 insertions(+) create mode 100644 packages/react/src/components/attached-file/attached-file-classes.ts create mode 100644 packages/react/src/components/attached-file/attached-file.tsx create mode 100644 packages/react/src/themes/tokens/component/attached-file-tokens.ts create mode 100644 packages/storybook/stories/attached-file.stories.tsx diff --git a/packages/react/package.json b/packages/react/package.json index 78f118107f..3ee570cce9 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -31,6 +31,7 @@ "dependencies": { "@mui/base": "5.0.0-beta.60", "@tanstack/react-table": "^8.10.7", + "clsx": "^2.1.1", "date-fns": "^4.0.0", "feather-icons": "^4.29.0", "prop-types": "^15.8.1", diff --git a/packages/react/src/components/attached-file/attached-file-classes.ts b/packages/react/src/components/attached-file/attached-file-classes.ts new file mode 100644 index 0000000000..ca4532d3de --- /dev/null +++ b/packages/react/src/components/attached-file/attached-file-classes.ts @@ -0,0 +1,20 @@ +import { generateComponentClasses } from '../../utils/generate-component-classes'; + +const COMPONENT_NAME = 'AttachedFile'; + +export interface AttachedFileClasses { + root: string; + icon: string; + status: string; +} + +export type AttachedFileClassKeys = keyof AttachedFileClasses; + +export const attachedFileClasses: AttachedFileClasses = generateComponentClasses( + COMPONENT_NAME, + [ + 'root', + 'icon', + 'status', + ], +); diff --git a/packages/react/src/components/attached-file/attached-file.tsx b/packages/react/src/components/attached-file/attached-file.tsx new file mode 100644 index 0000000000..3f057e0a5d --- /dev/null +++ b/packages/react/src/components/attached-file/attached-file.tsx @@ -0,0 +1,258 @@ +import { FunctionComponent, PropsWithChildren, useEffect, useMemo, useState } from 'react'; +import styled, { DefaultTheme } from 'styled-components'; +import { clsx } from 'clsx'; +import { useTranslation } from '../../i18n/use-translation'; +import { Spinner } from '../spinner/spinner'; +import { useTheme } from '../../hooks/use-theme'; +import { Lozenge } from '../lozenge/lozenge'; +import { useId } from '../../hooks/use-id'; +import { Button, IconButton } from '../buttons'; +import { Icon } from '../icon/icon'; +import { attachedFileClasses } from './attached-file-classes'; + +type AttachedFileStatus = 'default' | 'uploading' | 'cancelled' | 'error' | 'success'; + +function formatFilesize(size: number): string { + const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; + let i = 0; + let scaledSize = size; + while (scaledSize >= 1024) { + scaledSize /= 1024; + i += 1; + } + const roundedValue = Math.round(scaledSize * 10) / 10; + + return `${roundedValue} ${units[i]}`; +} + +const getStatustextColor = (status: AttachedFileStatus, theme: DefaultTheme): string => { + switch (status) { + case 'error': + return theme.component['attached-file-error-text-color']; + case 'success': + return theme.component['attached-file-success-text-color']; + default: + return theme.component['attached-file-auxiliary-text-color']; + } +}; + +const AttachedFileRoot = styled.div<{ $status: AttachedFileStatus }>` + align-items: flex-start; + background-color: ${({ theme }) => theme.component['attached-file-background-color']}; + border: 1px solid ${({ theme }) => theme.component['attached-file-border-color']}; + border-radius: var(--border-radius); + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); + + .${attachedFileClasses.icon} { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); + } + + .${attachedFileClasses.status} { + color: ${({ theme, $status }) => getStatustextColor($status, theme)}; + } +`; + +const AttachedFileContent = styled.div` + color: ${({ theme }) => theme.component['attached-file-text-color']}; + display: flex; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +`; + +const AttachedFileName = styled.span` + display: block; +`; + +const AttachedFileActions = styled.div` + align-items: center; + display: flex; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +`; + +export interface AttachedFileProps { + id?: string; + filename: string; + className?: string; + /** + * Size in bytes + */ + filesize: number; + percent?: number; + errorText?: string; + onCancel?(): void; + onDelete?(): void; + onRetry?(): void; + onClose?(): void; + status?: AttachedFileStatus; + lozengeText?: string; +} + +export const AttachedFile: FunctionComponent> = ({ + id: providedId, + className, + filename, + filesize, + percent, + errorText, + onCancel, + onDelete, + onRetry, + onClose, + status = 'default', + lozengeText, + children, +}) => { + const { t } = useTranslation('attached-file'); + const id = useId(providedId); + const theme = useTheme(); + const [currentStatus, setCurrentStatus] = useState(status); + + useEffect(() => { + if (status === 'success') { + const timer = setTimeout(() => setCurrentStatus('default'), 2000); + return () => clearTimeout(timer); + } + return undefined; + }, [status]); + + const renderIcon = (): JSX.Element => { + switch (currentStatus) { + case 'uploading': + return ( + + ); + case 'cancelled': + return ( + + ); + case 'error': + return ( + + ); + case 'success': + return ( + + ); + default: + return ( + + ); + } + }; + + const renderActions = (): JSX.Element => { + switch (currentStatus) { + case 'uploading': + return ; + case 'cancelled': + return ( + <> + + + + ); + case 'error': + return ( + <> + + + + ); + default: + return ( + + ); + } + }; + + const statusText = useMemo(() => { + switch (currentStatus) { + case 'uploading': + return t('uploading', { percent }); + case 'error': + return errorText; + case 'success': + return t('uploadCompleted'); + default: + return undefined; + } + }, [t, currentStatus, errorText, percent]); + + return ( + + {renderIcon()} + + + {`${filename} (${formatFilesize(filesize)})`} + + {statusText && ( + + {statusText} + + )} + + + {lozengeText ? ( + + {lozengeText} + + ) : children} + {renderActions()} + + + ); +}; diff --git a/packages/react/src/components/icon/icon.tsx b/packages/react/src/components/icon/icon.tsx index d94aa0d2e5..e3c43e4c77 100644 --- a/packages/react/src/components/icon/icon.tsx +++ b/packages/react/src/components/icon/icon.tsx @@ -20,6 +20,8 @@ import Edit from 'feather-icons/dist/icons/edit-2.svg'; import ExternalLink from 'feather-icons/dist/icons/external-link.svg'; import Eye from 'feather-icons/dist/icons/eye.svg'; import EyeOff from 'feather-icons/dist/icons/eye-off.svg'; +import File from 'feather-icons/dist/icons/file.svg'; +import FileText from 'feather-icons/dist/icons/file-text.svg'; import Graph from 'feather-icons/dist/icons/bar-chart-2.svg'; import HelpCircle from 'feather-icons/dist/icons/help-circle.svg'; import Home from 'feather-icons/dist/icons/home.svg'; @@ -39,6 +41,7 @@ import Send from 'feather-icons/dist/icons/send.svg'; import Settings from 'feather-icons/dist/icons/settings.svg'; import Star from 'feather-icons/dist/icons/star.svg'; import Trash from 'feather-icons/dist/icons/trash-2.svg'; +import Upload from 'feather-icons/dist/icons/upload.svg'; import User from 'feather-icons/dist/icons/user.svg'; import Users from 'feather-icons/dist/icons/users.svg'; import X from 'feather-icons/dist/icons/x.svg'; @@ -96,7 +99,9 @@ const iconMapping = { export: Export, eye: Eye, eyeOff: EyeOff, + file: File, files: Files, + fileText: FileText, graph: Graph, helpCircle: HelpCircle, history: History, @@ -125,6 +130,7 @@ const iconMapping = { transfer: Transfer, trash: Trash, unlink: Unlink, + upload: Upload, user: User, users: Users, warningFilled: WarningFilled, diff --git a/packages/react/src/i18n/translations.ts b/packages/react/src/i18n/translations.ts index b4c9ba6e1b..c4ec97dec5 100644 --- a/packages/react/src/i18n/translations.ts +++ b/packages/react/src/i18n/translations.ts @@ -3,6 +3,14 @@ export const Translations = { avatar: { ariaLabel: '{{username}} avatar', }, + 'attached-file': { + close: 'Close', + retry: 'Retry', + cancel: 'Cancel', + deleteFile: 'Delete file', + uploading: 'Uploading... {{percent}}%', + uploadCompleted: 'Upload completed', + }, bento: { productsLabel: 'Products', externalsLabel: 'Resources', @@ -144,6 +152,14 @@ export const Translations = { avatar: { ariaLabel: 'Avatar de {{username}}', }, + 'attached-file': { + close: 'Fermer', + retry: 'Réessayer', + cancel: 'Annuler', + deleteFile: 'Supprimer le fichier', + uploading: 'Téléversement... {{percent}}%', + uploadCompleted: 'Téléversement terminé', + }, bento: { productsLabel: 'Produits', externalsLabel: 'Ressources', diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index dde5511eae..935ebe12d7 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -51,6 +51,7 @@ export { Listbox, ListboxOption } from './components/listbox/listbox'; // Miscellaneous export { AccordionItem, Accordion, ItemsProps } from './components/accordion/index'; +export { AttachedFile, AttachedFileProps } from './components/attached-file/attached-file'; export { Badge } from './components/badge/badge'; export { Card } from './components/card/card'; export { CardLink } from './components/card-link/card-link'; diff --git a/packages/react/src/themes/tokens/component-tokens.ts b/packages/react/src/themes/tokens/component-tokens.ts index c18a00b6ac..f1e2dc75c7 100644 --- a/packages/react/src/themes/tokens/component-tokens.ts +++ b/packages/react/src/themes/tokens/component-tokens.ts @@ -1,5 +1,6 @@ import { AccordionToken, defaultAccordionTokens } from './component/accordion-tokens'; import { AvatarToken, defaultAvatarTokens } from './component/avatar-tokens'; +import { AttachedFileToken, defaultAttachedFileTokens } from './component/attached-file-tokens'; import { BadgeToken, defaultBadgeTokens } from './component/badge-tokens'; import { BentoMenuButtonToken, defaultBentoMenuButtonTokens } from './component/bento-menu-button-tokens'; import { BreadcrumbToken, defaultBreadcrumbTokens } from './component/breadcrumb-tokens'; @@ -56,6 +57,7 @@ import { defaultTooltipTokens, TooltipToken } from './component/tooltip-tokens'; export type ComponentToken = | AccordionToken | AvatarToken + | AttachedFileToken | BadgeToken | BentoMenuButtonToken | BreadcrumbToken @@ -112,6 +114,7 @@ export type ComponentToken = export const defaultComponentTokens = { ...defaultAccordionTokens, ...defaultAvatarTokens, + ...defaultAttachedFileTokens, ...defaultBadgeTokens, ...defaultBentoMenuButtonTokens, ...defaultBreadcrumbTokens, diff --git a/packages/react/src/themes/tokens/component/attached-file-tokens.ts b/packages/react/src/themes/tokens/component/attached-file-tokens.ts new file mode 100644 index 0000000000..dbfb3b3def --- /dev/null +++ b/packages/react/src/themes/tokens/component/attached-file-tokens.ts @@ -0,0 +1,24 @@ +import type { ComponentTokenMap } from '../tokens'; + +export type AttachedFileToken = + | 'attached-file-background-color' + | 'attached-file-border-color' + | 'attached-file-icon-color' + | 'attached-file-text-color' + | 'attached-file-auxiliary-text-color' + | 'attached-file-success-text-color' + | 'attached-file-success-icon-color' + | 'attached-file-error-text-color' + | 'attached-file-error-icon-color' + +export const defaultAttachedFileTokens: ComponentTokenMap = { + 'attached-file-background-color': 'color-background', + 'attached-file-border-color': 'color-border', + 'attached-file-icon-color': 'color-content-subtle', + 'attached-file-text-color': 'color-content', + 'attached-file-auxiliary-text-color': 'color-content-subtle', + 'attached-file-success-text-color': 'color-control-auxiliary-success', + 'attached-file-success-icon-color': 'color-control-auxiliary-success', + 'attached-file-error-text-color': 'color-control-auxiliary-error', + 'attached-file-error-icon-color': 'color-control-auxiliary-error', +}; diff --git a/packages/storybook/stories/attached-file.stories.tsx b/packages/storybook/stories/attached-file.stories.tsx new file mode 100644 index 0000000000..2ab0fc9e7e --- /dev/null +++ b/packages/storybook/stories/attached-file.stories.tsx @@ -0,0 +1,92 @@ +import { AttachedFile } from '@equisoft/design-elements-react'; +import { Meta, StoryObj } from '@storybook/react'; + +const AttachedFileMeta: Meta = { + title: 'Components/Attached File', + component: AttachedFile, + args: { + onCancel() { + // eslint-disable-next-line no-console + console.info('onCancel'); + }, + onDelete() { + // eslint-disable-next-line no-console + console.info('onDelete'); + }, + onRetry() { + // eslint-disable-next-line no-console + console.info('onRetry'); + }, + onClose() { + // eslint-disable-next-line no-console + console.info('onClose'); + }, + }, +}; + +export default AttachedFileMeta; + +type Story = StoryObj; + +export const Default: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + filename: 'File.txt', + status: 'default', + filesize: 100000, + }, +}; + +export const Uploading: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + status: 'uploading', + filename: 'File.txt', + filesize: 100000, + percent: 40, + }, +}; + +export const Error: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + status: 'error', + filename: 'File.txt', + filesize: 100000, + errorText: 'Error message', + }, +}; + +export const Cancelled: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + status: 'cancelled', + filename: 'File.txt', + filesize: 100000, + }, +}; + +export const Success: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + filename: 'File.txt', + filesize: 100000, + status: 'success', + }, +}; + +export const WithLozange: Story = { + ...AttachedFileMeta, + args: { + ...AttachedFileMeta.args, + filename: 'File.txt', + status: 'default', + filesize: 100000, + lozengeText: 'Label', + }, +}; diff --git a/yarn.lock b/yarn.lock index 273532478e..d7030379c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1703,6 +1703,7 @@ __metadata: "@typescript-eslint/eslint-plugin": "npm:7.18.0" "@typescript-eslint/parser": "npm:7.18.0" "@wojtekmaj/enzyme-adapter-react-17": "npm:^0.8.0" + clsx: "npm:^2.1.1" cross-env: "npm:^7.0.3" css-loader: "npm:^7.0.0" date-fns: "npm:^4.0.0" From 80ce6f4a7b0cb27e8896f16afd9f4547cf95d1f8 Mon Sep 17 00:00:00 2001 From: Savut Sang Date: Thu, 30 Jan 2025 13:08:52 -0500 Subject: [PATCH 2/4] fix: add children in storybook --- packages/storybook/stories/attached-file.stories.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/storybook/stories/attached-file.stories.tsx b/packages/storybook/stories/attached-file.stories.tsx index 2ab0fc9e7e..e4d90cf65b 100644 --- a/packages/storybook/stories/attached-file.stories.tsx +++ b/packages/storybook/stories/attached-file.stories.tsx @@ -4,6 +4,13 @@ import { Meta, StoryObj } from '@storybook/react'; const AttachedFileMeta: Meta = { title: 'Components/Attached File', component: AttachedFile, + argTypes: { + children: { + control: { + type: 'text', + }, + }, + }, args: { onCancel() { // eslint-disable-next-line no-console From 21e4cf751ce86bdb1c2586f230ad9a4402f9ef4a Mon Sep 17 00:00:00 2001 From: Savut Sang Date: Thu, 13 Feb 2025 15:22:40 -0500 Subject: [PATCH 3/4] fix: comments, cancelled, mdx --- .../attached-file/attached-file.test.tsx | 54 + .../attached-file/attached-file.test.tsx.snap | 1035 +++++++++++++++++ .../attached-file/attached-file.tsx | 19 +- .../src/components/attached-file/index.ts | 2 + packages/react/src/components/icon/icon.tsx | 2 + packages/react/src/i18n/translations.ts | 12 +- packages/react/src/index.ts | 2 +- packages/storybook/stories/attached-file.mdx | 59 + .../stories/attached-file.stories.tsx | 8 +- packages/storybook/stories/icon-library.mdx | 3 + 10 files changed, 1178 insertions(+), 18 deletions(-) create mode 100644 packages/react/src/components/attached-file/attached-file.test.tsx create mode 100644 packages/react/src/components/attached-file/attached-file.test.tsx.snap create mode 100644 packages/react/src/components/attached-file/index.ts create mode 100644 packages/storybook/stories/attached-file.mdx diff --git a/packages/react/src/components/attached-file/attached-file.test.tsx b/packages/react/src/components/attached-file/attached-file.test.tsx new file mode 100644 index 0000000000..3c77e63b7f --- /dev/null +++ b/packages/react/src/components/attached-file/attached-file.test.tsx @@ -0,0 +1,54 @@ +import { renderWithProviders } from '../../test-utils/renderer'; +import { AttachedFile } from './attached-file'; + +describe('AttachedFile', () => { + test('Matches Default Snapshot', () => { + const tree = renderWithProviders(); + + expect(tree).toMatchSnapshot(); + }); + + test('Matches Uploading Snapshot', () => { + const tree = renderWithProviders(); + + expect(tree).toMatchSnapshot(); + }); + + test('Matches Success Snapshot', () => { + const tree = renderWithProviders(); + + expect(tree).toMatchSnapshot(); + }); + + test('Matches Cancelled Snapshot', () => { + const tree = renderWithProviders(); + + expect(tree).toMatchSnapshot(); + }); + + test('Matches Error Snapshot', () => { + const tree = renderWithProviders(); + + expect(tree).toMatchSnapshot(); + }); +}); diff --git a/packages/react/src/components/attached-file/attached-file.test.tsx.snap b/packages/react/src/components/attached-file/attached-file.test.tsx.snap new file mode 100644 index 0000000000..ea70b4c9e5 --- /dev/null +++ b/packages/react/src/components/attached-file/attached-file.test.tsx.snap @@ -0,0 +1,1035 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`AttachedFile Matches Cancelled Snapshot 1`] = ` +.c4 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + font-family: inherit; + font-size: 0.75rem; + font-weight: var(--font-bold); + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-letter-spacing: 0.025rem; + -moz-letter-spacing: 0.025rem; + -ms-letter-spacing: 0.025rem; + letter-spacing: 0.025rem; + line-height: 1rem; + min-height: var(--size-1halfx); + min-width: 2rem; + outline: none; + padding: 0 var(--spacing-1halfx); + text-transform: uppercase; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c4 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c4:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c4 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c5 { + background-color: transparent; + border-color: transparent; + color: #60666E; +} + +.c5 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c5:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c5:hover, +.c5[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c5[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c6 { + background-color: transparent; + border-color: transparent; + color: #60666E; + min-width: var(--size-1halfx); + padding: 0; + width: var(--size-1halfx); +} + +.c6 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c6:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c6:hover, +.c6[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c6[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c6 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c0 { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + background-color: #FFFFFF; + border: 1px solid #DBDEE1; + border-radius: var(--border-radius); + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); +} + +.c0 .eds-AttachedFile-icon { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); +} + +.c0 .eds-AttachedFile-status { + color: #60666E; +} + +.c1 { + color: #1B1C1E; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +} + +.c2 { + display: block; +} + +.c3 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +} + +
+ +
+ + File.txt (97.7 KB) + + + Uploading cancelled + +
+
+ + +
+
+`; + +exports[`AttachedFile Matches Default Snapshot 1`] = ` +.c4 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + font-family: inherit; + font-size: 0.75rem; + font-weight: var(--font-bold); + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-letter-spacing: 0.025rem; + -moz-letter-spacing: 0.025rem; + -ms-letter-spacing: 0.025rem; + letter-spacing: 0.025rem; + line-height: 1rem; + min-height: var(--size-1halfx); + min-width: 2rem; + outline: none; + padding: 0 var(--spacing-1halfx); + text-transform: uppercase; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c4 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c4:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c4 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c5 { + background-color: transparent; + border-color: transparent; + color: #60666E; + min-width: var(--size-1halfx); + padding: 0; + width: var(--size-1halfx); +} + +.c5 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c5:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c5:hover, +.c5[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c5[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c5 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c0 { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + background-color: #FFFFFF; + border: 1px solid #DBDEE1; + border-radius: var(--border-radius); + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); +} + +.c0 .eds-AttachedFile-icon { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); +} + +.c0 .eds-AttachedFile-status { + color: #60666E; +} + +.c1 { + color: #1B1C1E; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +} + +.c2 { + display: block; +} + +.c3 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +} + +
+ +
+ + File.txt (97.7 KB) + +
+
+ +
+
+`; + +exports[`AttachedFile Matches Error Snapshot 1`] = ` +.c4 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + font-family: inherit; + font-size: 0.75rem; + font-weight: var(--font-bold); + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-letter-spacing: 0.025rem; + -moz-letter-spacing: 0.025rem; + -ms-letter-spacing: 0.025rem; + letter-spacing: 0.025rem; + line-height: 1rem; + min-height: var(--size-1halfx); + min-width: 2rem; + outline: none; + padding: 0 var(--spacing-1halfx); + text-transform: uppercase; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c4 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c4:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c4 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c5 { + background-color: transparent; + border-color: transparent; + color: #60666E; + min-width: var(--size-1halfx); + padding: 0; + width: var(--size-1halfx); +} + +.c5 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c5:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c5:hover, +.c5[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c5[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c5 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c0 { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + background-color: #FFFFFF; + border: 1px solid #DBDEE1; + border-radius: var(--border-radius); + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); +} + +.c0 .eds-AttachedFile-icon { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); +} + +.c0 .eds-AttachedFile-status { + color: #CD2C23; +} + +.c1 { + color: #1B1C1E; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +} + +.c2 { + display: block; +} + +.c3 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +} + +
+ +
+ + File.txt (97.7 KB) + +
+
+ +
+
+`; + +exports[`AttachedFile Matches Success Snapshot 1`] = ` +.c4 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + font-family: inherit; + font-size: 0.75rem; + font-weight: var(--font-bold); + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-letter-spacing: 0.025rem; + -moz-letter-spacing: 0.025rem; + -ms-letter-spacing: 0.025rem; + letter-spacing: 0.025rem; + line-height: 1rem; + min-height: var(--size-1halfx); + min-width: 2rem; + outline: none; + padding: 0 var(--spacing-1halfx); + text-transform: uppercase; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c4 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c4:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c4 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c5 { + background-color: transparent; + border-color: transparent; + color: #60666E; + min-width: var(--size-1halfx); + padding: 0; + width: var(--size-1halfx); +} + +.c5 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c5:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c5:hover, +.c5[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c5[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c5 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c0 { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + background-color: #FFFFFF; + border: 1px solid #DBDEE1; + border-radius: var(--border-radius); + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); +} + +.c0 .eds-AttachedFile-icon { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); +} + +.c0 .eds-AttachedFile-status { + color: #008533; +} + +.c1 { + color: #1B1C1E; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +} + +.c2 { + display: block; +} + +.c3 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +} + +
+ +
+ + File.txt (97.7 KB) + + + Uploading completed + +
+
+ +
+
+`; + +exports[`AttachedFile Matches Uploading Snapshot 1`] = ` +.c5 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + background: inherit; + border: 1px solid; + border-radius: 1.5rem; + box-sizing: border-box; + color: inherit; + display: -webkit-inline-box; + display: -webkit-inline-flex; + display: -ms-inline-flexbox; + display: inline-flex; + font-family: inherit; + font-size: 0.75rem; + font-weight: var(--font-bold); + -webkit-box-pack: center; + -webkit-justify-content: center; + -ms-flex-pack: center; + justify-content: center; + -webkit-letter-spacing: 0.025rem; + -moz-letter-spacing: 0.025rem; + -ms-letter-spacing: 0.025rem; + letter-spacing: 0.025rem; + line-height: 1rem; + min-height: var(--size-1halfx); + min-width: 2rem; + outline: none; + padding: 0 var(--spacing-1halfx); + text-transform: uppercase; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.c5 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c5:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c5 > svg { + height: var(--size-1x); + width: var(--size-1x); +} + +.c1 { + color: #006296; + height: 64px; + width: 64px; +} + +.c6 { + background-color: transparent; + border-color: transparent; + color: #60666E; +} + +.c6 { + outline: 2px solid transparent; + outline-offset: -2px; +} + +.c6:focus { + box-shadow: 0 0 0 2px #006296; + outline: 2px solid #84C6EA; + outline-offset: -2px; +} + +.c6:hover, +.c6[aria-expanded='true'] { + background-color: rgb(0 0 0 / 0.15); + border-color: transparent; + color: #000000; +} + +.c6[aria-disabled='true'] { + background-color: transparent; + border-color: transparent; + color: #B7BBC2; + cursor: not-allowed; +} + +.c0 { + -webkit-align-items: flex-start; + -webkit-box-align: flex-start; + -ms-flex-align: flex-start; + align-items: flex-start; + background-color: #FFFFFF; + border: 1px solid #DBDEE1; + border-radius: var(--border-radius); + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + gap: var(--spacing-1x); + padding: var(--spacing-1x) var(--spacing-2x) var(--spacing-1x) var(--spacing-1halfx); +} + +.c0 .eds-AttachedFile-icon { + height: var(--size-1x); + margin-top: var(--spacing-half); + width: var(--size-1x); +} + +.c0 .eds-AttachedFile-status { + color: #60666E; +} + +.c2 { + color: #1B1C1E; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + font-size: 0.75rem; + gap: var(--spacing-half); + line-height: 1rem; + margin-top: var(--spacing-half); +} + +.c3 { + display: block; +} + +.c4 { + -webkit-align-items: center; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + display: -webkit-box; + display: -webkit-flex; + display: -ms-flexbox; + display: flex; + -webkit-flex-shrink: 0; + -ms-flex-negative: 0; + flex-shrink: 0; + gap: var(--spacing-1x); + margin-left: auto; +} + +
+ +
+ + File.txt (97.7 KB) + + + Uploading...null + +
+
+ +
+
+`; diff --git a/packages/react/src/components/attached-file/attached-file.tsx b/packages/react/src/components/attached-file/attached-file.tsx index 3f057e0a5d..bb5dda065a 100644 --- a/packages/react/src/components/attached-file/attached-file.tsx +++ b/packages/react/src/components/attached-file/attached-file.tsx @@ -12,8 +12,7 @@ import { attachedFileClasses } from './attached-file-classes'; type AttachedFileStatus = 'default' | 'uploading' | 'cancelled' | 'error' | 'success'; -function formatFilesize(size: number): string { - const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']; +function formatFilesize(units: string[], size: number): string { let i = 0; let scaledSize = size; while (scaledSize >= 1024) { @@ -112,6 +111,7 @@ export const AttachedFile: FunctionComponent { const { t } = useTranslation('attached-file'); + const { t: tCommon } = useTranslation('common'); const id = useId(providedId); const theme = useTheme(); const [currentStatus, setCurrentStatus] = useState(status); @@ -139,7 +139,7 @@ export const AttachedFile: FunctionComponent ); case 'error': @@ -176,7 +176,7 @@ export const AttachedFile: FunctionComponent - + - + {onRetry && } { switch (currentStatus) { case 'uploading': - return t('uploading', { percent }); + return t('uploading') + (percent !== undefined ? ` ${percent}%` : null); + case 'cancelled': + return t('uploadCancelled'); case 'error': return errorText; case 'success': @@ -228,12 +230,15 @@ export const AttachedFile: FunctionComponent {renderIcon()} - {`${filename} (${formatFilesize(filesize)})`} + {filename} + {filesizeText} {statusText && ( diff --git a/packages/react/src/components/attached-file/index.ts b/packages/react/src/components/attached-file/index.ts new file mode 100644 index 0000000000..bc3ba94a23 --- /dev/null +++ b/packages/react/src/components/attached-file/index.ts @@ -0,0 +1,2 @@ +export { attachedFileClasses, AttachedFileClasses } from './attached-file-classes'; +export { AttachedFile, AttachedFileProps } from './attached-file'; diff --git a/packages/react/src/components/icon/icon.tsx b/packages/react/src/components/icon/icon.tsx index e3c43e4c77..84fe734cf0 100644 --- a/packages/react/src/components/icon/icon.tsx +++ b/packages/react/src/components/icon/icon.tsx @@ -45,6 +45,7 @@ import Upload from 'feather-icons/dist/icons/upload.svg'; import User from 'feather-icons/dist/icons/user.svg'; import Users from 'feather-icons/dist/icons/users.svg'; import X from 'feather-icons/dist/icons/x.svg'; +import XOctagon from 'feather-icons/dist/icons/x-octagon.svg'; import { VoidFunctionComponent } from 'react'; import AlertFilled from '../../icons/alert-filled.svg'; import ArrowDownCircle from '../../icons/arrow-down-circle.svg'; @@ -135,6 +136,7 @@ const iconMapping = { users: Users, warningFilled: WarningFilled, x: X, + xOctagon: XOctagon, } as const; export type IconName = keyof typeof iconMapping; diff --git a/packages/react/src/i18n/translations.ts b/packages/react/src/i18n/translations.ts index c4ec97dec5..cc822ff9bf 100644 --- a/packages/react/src/i18n/translations.ts +++ b/packages/react/src/i18n/translations.ts @@ -6,10 +6,12 @@ export const Translations = { 'attached-file': { close: 'Close', retry: 'Retry', + resume: 'Resume', cancel: 'Cancel', deleteFile: 'Delete file', - uploading: 'Uploading... {{percent}}%', - uploadCompleted: 'Upload completed', + uploading: 'Uploading...', + uploadCompleted: 'Uploading completed', + uploadCancelled: 'Uploading cancelled', }, bento: { productsLabel: 'Products', @@ -30,6 +32,7 @@ export const Translations = { opensInNewTab: 'opens in a new tab', opensInNewTabScreenReader: '(opens in a new tab)', error: 'Error', + unitSymbolBytes: ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], }, datepicker: { calendarButtonLabel: 'Choose date', @@ -155,10 +158,12 @@ export const Translations = { 'attached-file': { close: 'Fermer', retry: 'Réessayer', + resume: 'Reprendre', cancel: 'Annuler', deleteFile: 'Supprimer le fichier', - uploading: 'Téléversement... {{percent}}%', + uploading: 'Téléversement...', uploadCompleted: 'Téléversement terminé', + uploadCancelled: 'Téléversement annulé', }, bento: { productsLabel: 'Produits', @@ -179,6 +184,7 @@ export const Translations = { opensInNewTab: 'ouvre dans un nouvel onglet', opensInNewTabScreenReader: '(ouvre dans un nouvel onglet)', error: 'Erreur', + unitSymbolBytes: ['o', 'Ko', 'Mo', 'Go', 'To', 'Po'], }, datepicker: { calendarButtonLabel: 'Choisissez une date', diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 935ebe12d7..9a164cb02e 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -51,7 +51,7 @@ export { Listbox, ListboxOption } from './components/listbox/listbox'; // Miscellaneous export { AccordionItem, Accordion, ItemsProps } from './components/accordion/index'; -export { AttachedFile, AttachedFileProps } from './components/attached-file/attached-file'; +export * from './components/attached-file'; export { Badge } from './components/badge/badge'; export { Card } from './components/card/card'; export { CardLink } from './components/card-link/card-link'; diff --git a/packages/storybook/stories/attached-file.mdx b/packages/storybook/stories/attached-file.mdx new file mode 100644 index 0000000000..78471f5182 --- /dev/null +++ b/packages/storybook/stories/attached-file.mdx @@ -0,0 +1,59 @@ +import { AttachedFile } from "@equisoft/design-elements-react"; +import { ArgTypes, Canvas, Meta } from "@storybook/blocks"; +import * as AttachedFileStories from "./attached-file.stories"; + + + +# Attached File + +1. [Definition](#definition) +2. [Usage](#usage) +3. [States](#states) +4. [Properties](#properties) + +## Definition + +Drop zone allows users to upload files by dragging and dropping the files (or folders) into an area on a page, or activating a button [2]. + + + +## Usage + +### When to use + +- Use when you need to display files that users have uploaded, such as documents, images, or other attachments. +- Use alongside the Dropzone component to visually represent files that users have dragged and dropped into an upload area. +- Use when you want to provide users with real-time feedback on the status of their file uploads, including progress indicators and success/error states. +- Use when you want to allow users to perform actions on uploaded files, such as deleting or retrying a failed upload. +- Use in forms where users are required to upload and manage documents, ensuring they can easily add, view, and manage their files as part of the submission process. +- Use when you want to enforce file type, size restrictions, or other validation rules. + +### When not to use + +- Do not use if you need to display content directly on the page rather than as an attachment. +- Do not use for files requiring complex interactions such as in-line editing, annotation, or version control. + +## States + +### Default + +The Default state is the final state of the attached file, following the Success state. + +### Uploading + +The Uploading state is the initial state of the attached file, indicating the progress of the file as it is being imported into the application. + +### Error + +The Error state occurs when there is an issue with the upload. Possible issues include: + +- Unsupported file type. +- Upload failure. + +### Success + +The Success state confirms that the attached file has been successfully uploaded. This state is displayed briefly (2 seconds) before transitioning to the Default state. + +## Properties + + diff --git a/packages/storybook/stories/attached-file.stories.tsx b/packages/storybook/stories/attached-file.stories.tsx index e4d90cf65b..baa0f4d1fe 100644 --- a/packages/storybook/stories/attached-file.stories.tsx +++ b/packages/storybook/stories/attached-file.stories.tsx @@ -36,7 +36,6 @@ export default AttachedFileMeta; type Story = StoryObj; export const Default: Story = { - ...AttachedFileMeta, args: { ...AttachedFileMeta.args, filename: 'File.txt', @@ -46,7 +45,6 @@ export const Default: Story = { }; export const Uploading: Story = { - ...AttachedFileMeta, args: { ...AttachedFileMeta.args, status: 'uploading', @@ -57,7 +55,6 @@ export const Uploading: Story = { }; export const Error: Story = { - ...AttachedFileMeta, args: { ...AttachedFileMeta.args, status: 'error', @@ -68,7 +65,6 @@ export const Error: Story = { }; export const Cancelled: Story = { - ...AttachedFileMeta, args: { ...AttachedFileMeta.args, status: 'cancelled', @@ -78,7 +74,6 @@ export const Cancelled: Story = { }; export const Success: Story = { - ...AttachedFileMeta, args: { ...AttachedFileMeta.args, filename: 'File.txt', @@ -87,8 +82,7 @@ export const Success: Story = { }, }; -export const WithLozange: Story = { - ...AttachedFileMeta, +export const WithLozenge: Story = { args: { ...AttachedFileMeta.args, filename: 'File.txt', diff --git a/packages/storybook/stories/icon-library.mdx b/packages/storybook/stories/icon-library.mdx index bf6df52236..fc6b17dffb 100644 --- a/packages/storybook/stories/icon-library.mdx +++ b/packages/storybook/stories/icon-library.mdx @@ -186,6 +186,9 @@ import { ArgTypes, Canvas, IconGallery, IconItem, Meta, Title } from '@storybook + + + From 5e8c7bf993394a34b9c9917b90c82da029bcd95c Mon Sep 17 00:00:00 2001 From: Savut Sang Date: Thu, 13 Feb 2025 15:50:50 -0500 Subject: [PATCH 4/4] fix: filesize optional --- .../react/src/components/attached-file/attached-file.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/attached-file/attached-file.tsx b/packages/react/src/components/attached-file/attached-file.tsx index bb5dda065a..995485d184 100644 --- a/packages/react/src/components/attached-file/attached-file.tsx +++ b/packages/react/src/components/attached-file/attached-file.tsx @@ -84,7 +84,7 @@ export interface AttachedFileProps { /** * Size in bytes */ - filesize: number; + filesize?: number; percent?: number; errorText?: string; onCancel?(): void; @@ -131,7 +131,6 @@ export const AttachedFile: FunctionComponent ); case 'cancelled': @@ -230,7 +229,7 @@ export const AttachedFile: FunctionComponent