Skip to content

Commit

Permalink
Merge pull request #6355 from Sage/FE-6216-file-input
Browse files Browse the repository at this point in the history
feat: add new file-input component
  • Loading branch information
robinzigmond authored Dec 13, 2023
2 parents f66fa0d + ecbc2fe commit 8e99496
Show file tree
Hide file tree
Showing 19 changed files with 2,761 additions and 4 deletions.
18 changes: 18 additions & 0 deletions playwright/components/file-input/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Page } from "playwright-core";
import { LABEL, FILE_INPUT } from "./locators";

export const hiddenInput = (page: Page, label: string) => {
return page.getByLabel(label);
};

export const selectFileButton = (page: Page, buttonText = "Select file") => {
return page.getByRole("button", { name: buttonText });
};

export const label = (page: Page) => {
return page.locator(LABEL);
};

export const fileInput = (page: Page) => {
return page.locator(FILE_INPUT);
};
2 changes: 2 additions & 0 deletions playwright/components/file-input/locators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const LABEL = "label";
export const FILE_INPUT = '[data-component="file-input"]';
9 changes: 5 additions & 4 deletions src/__internal__/utils/helpers/tags/tags-specs/tags-specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,15 @@ const rootTagTest = (
};

const rootTagTestRtl = (
element: HTMLElement,
rootNode: HTMLElement,
comp: string,
elem?: string,
role?: string
) => {
expect(element.getAttribute("data-component")).toBe(comp);
expect(element.getAttribute("data-element")).toBe(elem);
expect(element.getAttribute("data-role")).toBe(role);
expect(rootNode).toHaveAttribute("data-component", comp);
expect(rootNode).toHaveAttribute("data-element", elem);
expect(rootNode).toHaveAttribute("data-role", role);
};

// eslint-disable-next-line jest/no-export
export { elementsTagTest, rootTagTest, rootTagTestRtl };
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import React from "react";
import Link, { LinkProps } from "../../../link";
import ButtonMinor from "../../../button-minor";
import StyledTypography from "../../../typography/typography.style";
import ProgressTracker from "../../../progress-tracker";
import LoaderBar from "../../../loader-bar";
import Icon, { IconType } from "../../../icon";
import {
StyledFileUploadStatus,
StyledFileUploadStatusRow,
StyledFileLinkContainer,
} from "./file-upload-status.style";
import useLocale from "../../../../hooks/__internal__/useLocale";

interface StatusUploadingProps {
/** the status of the upload */
status: "uploading";
/** a number from 0-100 giving the current upload progress as a percentage. Only used for the `uploading` status.
* If the progress prop is not specified in the `uploading` status, a loading animation will be shown instead
* (or text equivalent for users with a reduced-motion operating system preference).
*/
progress?: number;
}

interface StatusDoneProps extends LinkProps {
/** the status of the upload */
status: "completed" | "previously";
/** the URL opened by the file link. Must be provided for only the `completed` and `previously` statuses. */
href: string;
}

interface StatusErrorProps {
/** the status of the upload */
status: "error";
}

interface MandatoryStatusProps {
/** the name of the file */
filename: string;
/** a function to be executed when the user clicks the appropriate action button (Clear/Delete File/Cancel Upload) */
onAction: () => void;
/** The status message. Used to display the current upload progress, including error messages where appropriate. Not used for the `previously` status. */
message?: string;
/** The icon to use for the file during or after upload */
iconType?: IconType;
}

export type FileUploadStatusProps = MandatoryStatusProps &
(StatusUploadingProps | StatusErrorProps | StatusDoneProps);

export const FileUploadStatus = ({
status,
filename,
message,
onAction,
iconType = "file_generic",
...statusProps
}: FileUploadStatusProps) => {
const locale = useLocale();
const statusMessage = message || locale.fileInput.fileUploadStatus();

let buttonText;
let linkProps;
let progressBar = null;
switch (status) {
case "uploading":
buttonText = locale.fileInput.actions.cancel();
progressBar =
(statusProps as StatusUploadingProps).progress === undefined ? (
<LoaderBar size="small" />
) : (
<ProgressTracker
size="small"
progress={(statusProps as StatusUploadingProps).progress}
length="100%"
/>
);
break;
case "previously":
case "completed":
buttonText = locale.fileInput.actions.delete();
linkProps = { ...statusProps, icon: iconType };
break;
case "error":
buttonText = locale.fileInput.actions.clear();
break;
// istanbul ignore next
default:
// no other cases if consumers are using TS, but ESLint still insists on it
break;
}
const actionButton = (
<ButtonMinor onClick={onAction} buttonType="tertiary">
{buttonText}
</ButtonMinor>
);
const fileLink = linkProps ? (
<Link {...linkProps}>{filename}</Link>
) : (
<>
<Icon type={iconType} />
<span>{filename}</span>
</>
);
const mainRow =
status !== "previously" ? (
<StyledFileUploadStatusRow>
<StyledTypography as="p" mb={0} aria-live="polite">
{statusMessage}
</StyledTypography>
{actionButton}
</StyledFileUploadStatusRow>
) : (
<StyledFileUploadStatusRow onlyRow>
<StyledFileLinkContainer>{fileLink}</StyledFileLinkContainer>
{actionButton}
</StyledFileUploadStatusRow>
);
const secondRow =
status !== "previously" ? (
<StyledFileUploadStatusRow upperPadding lowerPadding>
<StyledFileLinkContainer>{fileLink}</StyledFileLinkContainer>
</StyledFileUploadStatusRow>
) : null;
return (
<StyledFileUploadStatus hasError={status === "error"}>
{mainRow}
{secondRow}
{progressBar}
</StyledFileUploadStatus>
);
};

export default FileUploadStatus;
Loading

0 comments on commit 8e99496

Please sign in to comment.