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

[MDS-5801] Text/Language updates #2976

Merged
merged 11 commits into from
Mar 4, 2024
20 changes: 12 additions & 8 deletions services/common/src/components/forms/BaseInput.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Typography } from "antd";
import React, { FC } from "react";
import React, { FC, ReactNode } from "react";
import { WrappedFieldProps, WrappedFieldMetaProps, WrappedFieldInputProps } from "redux-form";

/**
Expand Down Expand Up @@ -62,8 +62,8 @@ import { WrappedFieldProps, WrappedFieldMetaProps, WrappedFieldInputProps } from
export interface BaseInputProps extends WrappedFieldProps {
meta: WrappedFieldMetaProps;
input: WrappedFieldInputProps;
label?: string;
labelSubtitle?: string;
label?: string | ReactNode;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty much just updating this file to accept elements as well as strings for labels.

labelSubtitle?: string | ReactNode;
id: string;
defaultValue?: any;
placeholder?: string;
Expand All @@ -75,13 +75,13 @@ export interface BaseInputProps extends WrappedFieldProps {
}

interface BaseViewInputProps {
label?: string;
label?: string | ReactNode;
value: string | number;
}
export const BaseViewInput: FC<BaseViewInputProps> = ({ label = "", value = "" }) => {
return (
<div className="view-item">
{label.length && (
{label && label !== "" && (
<Typography.Paragraph className="view-item-label">{label}</Typography.Paragraph>
)}
<Typography.Paragraph className="view-item-value">{value.toString()}</Typography.Paragraph>
Expand All @@ -90,7 +90,11 @@ export const BaseViewInput: FC<BaseViewInputProps> = ({ label = "", value = "" }
};

// for consistent formatting of optional field indicator
export const getFormItemLabel = (label: string, isRequired: boolean, labelSubtitle?: string) => {
export const getFormItemLabel = (
label: string | ReactNode,
isRequired: boolean,
labelSubtitle?: string | ReactNode
) => {
if (!label) {
return "";
}
Expand All @@ -101,7 +105,7 @@ export const getFormItemLabel = (label: string, isRequired: boolean, labelSubtit
{labelSubtitle && (
<>
<br />
<span className="light--sm">{labelSubtitle}</span>
<span className="label-subtitle">{labelSubtitle}</span>
</>
)}
</>
Expand All @@ -113,7 +117,7 @@ export const getFormItemLabel = (label: string, isRequired: boolean, labelSubtit
{labelSubtitle && (
<>
<br />
<span className="light--sm">{labelSubtitle}</span>
<span className="label-subtitle">{labelSubtitle}</span>
</>
)}
</>
Expand Down
43 changes: 15 additions & 28 deletions services/common/src/components/forms/RenderAutoSizeField.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState, useEffect, FC } from "react";
import React, { FC } from "react";
import { Input, Form, Row } from "antd";
import { BaseInputProps, BaseViewInput, getFormItemLabel } from "./BaseInput";
import { FormConsumer, IFormContext } from "./FormWrapper";
Expand All @@ -9,36 +9,19 @@ import { FormConsumer, IFormContext } from "./FormWrapper";

interface AutoSizeProps extends BaseInputProps {
minRows?: number;
maximumCharacters?: number;
maximumCharacters: number;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I decided that this should be mandatory. There's no instances where this version of the component (ie the one in common) is used and the value is not supplied.


const RenderAutoSizeField: FC<AutoSizeProps> = ({
label = "",
labelSubtitle,
help,
disabled = false,
maximumCharacters = 0,
maximumCharacters,
minRows = 3,
required = false,
...props
}) => {
const [remainingChars, setRemainingChars] = useState(maximumCharacters);
const [inputValue, setValue] = useState(props.input.value ?? "");

const handleTextAreaChange = (event) => {
setValue(event.target.value);
if (maximumCharacters > 0) {
const input = event.target.value;
const remaining = maximumCharacters - input.length;
setRemainingChars(remaining);
}
};

useEffect(() => {
const input = props.input.value;
const remaining = maximumCharacters - input.length;
setRemainingChars(remaining);
}, []);

return (
<FormConsumer>
{(value: IFormContext) => {
Expand Down Expand Up @@ -68,15 +51,19 @@ const RenderAutoSizeField: FC<AutoSizeProps> = ({
{...props.input}
autoSize={{ minRows: minRows }}
placeholder={props.placeholder}
onChange={handleTextAreaChange}
value={inputValue}
/>
{maximumCharacters > 0 && (
<Row justify="space-between">
<Row
justify="space-between"
className={`form-item-help ${props.input.name}-form-help`}
>
{help ? (
<span>{help}</span>
) : (
<span>{`Maximum ${maximumCharacters} characters`}</span>
<span className="flex-end">{`${remainingChars} / ${maximumCharacters}`}</span>
</Row>
)}
)}
<span className="flex-end">{`${maximumCharacters -
props.input.value.length} / ${maximumCharacters}`}</span>
</Row>
</>
</Form.Item>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React from "react";
import { render } from "enzyme";
import RenderFileUpload from "./RenderFileUpload";
import { ReduxWrapper } from "@mds/common/tests/utils/ReduxWrapper";
import { PDF } from "../..";

jest.mock("filepond", () => {
const filepond = jest.requireActual("filepond");
Expand All @@ -16,7 +17,7 @@ test("RenderFileUpload component renders correctly", () => {
const props = {
shouldAbortUpload: false,
shouldReplaceFile: false,
acceptedFileTypesMap: {},
acceptedFileTypesMap: PDF,
beforeDropFile: jest.fn(),
beforeAddFile: jest.fn(),
allowRevert: true,
Expand All @@ -27,7 +28,6 @@ test("RenderFileUpload component renders correctly", () => {
maxFileSize: 1024,
onProcessFiles: jest.fn(),
onAbort: jest.fn(),
labelIdle: "Drag and drop your files here",
itemInsertLocation: "before",
};

Expand Down
131 changes: 88 additions & 43 deletions services/common/src/components/forms/RenderFileUpload.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import "filepond-polyfill";
import { FilePond, registerPlugin } from "react-filepond";
import { Form, Switch, notification } from "antd";
import { Form, Popover, Switch, notification } from "antd";
import { invert, uniq } from "lodash";
import { FunnelPlotOutlined } from "@ant-design/icons";
import "filepond/dist/filepond.min.css";
Expand Down Expand Up @@ -58,7 +58,14 @@ interface FileUploadProps extends BaseInputProps {
allowReorder?: boolean;
maxFileSize?: string;
itemInsertLocation?: ItemInsertLocationType;
// for a completely customized drop label
labelIdle?: string;
// otherwise- can set the "Drag & drop" text,
labelInstruction?: string;
// and the accepted file types to display (empty will use extensions from acceptedFileTypesMap)
listedFileTypes?: string[];
// true for "We accept most common ${listedFileTypes.join()} files" language + popover
abbrevLabel?: boolean;
maxFiles?: number;

beforeAddFile?: (file?: any) => any;
Expand All @@ -82,9 +89,9 @@ const defaultProps = {
onAbort: () => {},
onInit: () => {},
itemInsertLocation: "before" as ItemInsertLocationType,
labelIdle:
'<strong>Drag & Drop your files or <span class="filepond--label-action">Browse</span></strong><br> \
<div>Accepted filetypes: .kmz, .doc, .docx, .xlsx, .pdf</div>',
labelInstruction:
'<strong>Drag & Drop your files or <span class="filepond--label-action">Browse</span></strong>',
abbrevLabel: false,
beforeAddFile: () => {},
beforeDropFile: () => {},
file: null,
Expand All @@ -107,6 +114,26 @@ export const FileUpload = (props: FileUploadProps) => {
// and replace file functionality
const [uploadData, setUploadData] = useState<{ [fileId: string]: MultipartDocumentUpload }>({});

const getFilePondLabel = () => {
const {
labelIdle,
labelInstruction,
listedFileTypes,
abbrevLabel,
acceptedFileTypesMap,
} = props;
if (labelIdle) {
return labelIdle;
}
const fileTypeDisplayString = listedFileTypes
? listedFileTypes.join(", ")
: Object.keys(acceptedFileTypesMap).join(", ");
const secondLine = abbrevLabel
? `<div>We accept most common ${fileTypeDisplayString} files</div>`
: `<div>Accepted filetypes: ${fileTypeDisplayString}</div>`;
return `${labelInstruction}<br>${secondLine}`;
};

// Stores metadata and process function for each file, so we can manually
// trigger it. Currently, this is being used for the replace file functionality
// which dynamically changes the URL of the upload if you confirm the replacement
Expand Down Expand Up @@ -453,46 +480,64 @@ export const FileUpload = (props: FileUploadProps) => {
(props.meta?.warning && <span>{props.meta?.warning}</span>))
}
>
<FilePond
ref={(ref) => (filepond = ref)}
server={server}
name="file"
beforeDropFile={props.beforeDropFile}
beforeAddFile={props.beforeAddFile}
allowRevert={props.allowRevert}
onremovefile={props.onRemoveFile}
allowMultiple={props.allowMultiple}
onaddfilestart={props.addFileStart}
allowReorder={props.allowReorder}
maxParallelUploads={1}
maxFileSize={props.maxFileSize}
// maxFiles={props.maxFiles || undefined}
allowFileTypeValidation={acceptedFileTypes.length > 0}
acceptedFileTypes={acceptedFileTypes}
onaddfile={handleFileAdd}
onprocessfiles={props.onProcessFiles}
onprocessfileabort={props.onAbort}
oninit={props.onInit}
labelIdle={props?.labelIdle}
itemInsertLocation={props?.itemInsertLocation}
credits={null}
fileValidateTypeLabelExpectedTypesMap={fileValidateTypeLabelExpectedTypesMap}
fileValidateTypeDetectType={(source, type) =>
new Promise((resolve, reject) => {
// If the browser can't automatically detect the file's MIME type, use the one stored in the "accepted file types" map.
if (!type) {
const exts = source.name.split(".");
const ext = exts && exts.length > 0 && `.${exts.pop()}`;
if (ext && ext in props.acceptedFileTypesMap) {
type = props.acceptedFileTypesMap[ext];
} else {
reject(type);
<>
<FilePond
ref={(ref) => (filepond = ref)}
server={server}
name="file"
beforeDropFile={props.beforeDropFile}
beforeAddFile={props.beforeAddFile}
allowRevert={props.allowRevert}
onremovefile={props.onRemoveFile}
allowMultiple={props.allowMultiple}
onaddfilestart={props.addFileStart}
allowReorder={props.allowReorder}
maxParallelUploads={1}
maxFileSize={props.maxFileSize}
allowFileTypeValidation={acceptedFileTypes.length > 0}
acceptedFileTypes={acceptedFileTypes}
onaddfile={handleFileAdd}
onprocessfiles={props.onProcessFiles}
onprocessfileabort={props.onAbort}
oninit={props.onInit}
labelIdle={getFilePondLabel()}
itemInsertLocation={props?.itemInsertLocation}
credits={null}
fileValidateTypeLabelExpectedTypesMap={fileValidateTypeLabelExpectedTypesMap}
fileValidateTypeDetectType={(source, type) =>
new Promise((resolve, reject) => {
// If the browser can't automatically detect the file's MIME type, use the one stored in the "accepted file types" map.
if (!type) {
const exts = source.name.split(".");
const ext = exts && exts.length > 0 && `.${exts.pop()}`;
if (ext && ext in props.acceptedFileTypesMap) {
type = props.acceptedFileTypesMap[ext];
} else {
reject(type);
}
}
}
resolve(type);
})
}
/>
resolve(type);
})
}
/>
{props.abbrevLabel && (
<div className="filepond-popover-container">
<Popover
content={
<>
<strong>Accepted File Types:</strong>
<p>{Object.keys(props.acceptedFileTypesMap).join(", ")}</p>
</>
}
placement="topRight"
color="white"
overlayClassName="filepond-filetypes-popover"
>
View accepted file types
</Popover>
</div>
)}
</>
</Form.Item>
</div>
);
Expand Down
6 changes: 3 additions & 3 deletions services/common/src/components/forms/RenderSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ interface SelectProps extends BaseInputProps {

export const RenderSelect: FC<SelectProps> = ({
label = "",
labelSubtitle,
id,
meta,
input,
Expand All @@ -45,7 +46,7 @@ export const RenderSelect: FC<SelectProps> = ({
return (
<Form.Item
name={input.name}
label={getFormItemLabel(label, required)}
label={getFormItemLabel(label, required, labelSubtitle)}
required={required}
validateStatus={
isDirty || meta.touched ? (meta.error && "error") || (meta.warning && "warning") : ""
Expand All @@ -56,7 +57,7 @@ export const RenderSelect: FC<SelectProps> = ({
(meta.warning && <span>{meta.warning}</span>))
}
id={id}
getValueProps={() => ({ value: input.value })}
getValueProps={() => input.value !== "" && { value: input.value }}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholder wasn't showing because I think redux was supplying it with "" when it was empty.

>
<Select
virtual={false}
Expand All @@ -70,7 +71,6 @@ export const RenderSelect: FC<SelectProps> = ({
optionFilterProp="children"
filterOption={caseInsensitiveLabelFilter}
id={id}
value={data.length && input.value ? input.value : null}
onChange={(changeValue) => {
setIsDirty(true);
input.onChange(changeValue);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ exports[`RenderFileUpload component renders correctly 1`] = `
class="filepond--wrapper"
>
<input
accept=""
accept="application/pdf"
multiple=""
name="file"
type="file"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ export const FacilityOperator: FC = () => {
maximumCharacters={4000}
rows={3}
component={RenderAutoSizeField}
help="Briefly describe: Overview of the project. The primary activity of the facility. If there is not enough space, you may attach additional information, including conceptual site plans."
/>
<Row gutter={16}>
<Col md={12} sm={24}>
Expand Down
Loading
Loading