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

Add trackUnsaved prop to SelectFieldV2 #4811

Merged
merged 10 commits into from
Nov 22, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@ Default.args = {
],
};

export const TrackUnsavedFalse = Template.bind({});
mnigh marked this conversation as resolved.
Show resolved Hide resolved
TrackUnsavedFalse.args = {
...Default.args,
trackUnsaved: false,
};

export const Required = Template.bind({});
Required.args = {
...Default.args,
Expand Down
50 changes: 49 additions & 1 deletion frontend/common/src/components/form/Select/SelectFieldV2.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
import React from "react";
import { Controller, FieldError, useFormContext } from "react-hook-form";
import type { RegisterOptions } from "react-hook-form";
import ReactSelect, { components, MultiValue, SingleValue } from "react-select";
import ReactSelect, {
components,
ContainerProps,
MultiValue,
SingleValue,
} from "react-select";
import type { NoticeProps, GroupBase, OptionsOrGroups } from "react-select";
import camelCase from "lodash/camelCase";
import flatMap from "lodash/flatMap";
import { useIntl } from "react-intl";
import { useFieldStateStyles } from "../../../helpers/formUtils";
import { errorMessages } from "../../../messages";
import { InputWrapper } from "../../inputPartials";

Expand All @@ -16,6 +22,18 @@ export type Group<T> = {
};
export type Options = OptionsOrGroups<Option, Group<Option>>;

declare module "react-select/dist/declarations/src/Select" {
export interface Props<
/* eslint-disable @typescript-eslint/no-shadow, @typescript-eslint/no-unused-vars */
Option,
IsMulti extends boolean,
Group extends GroupBase<Option>,
/* eslint-enable @typescript-eslint/no-shadow, @typescript-eslint/no-unused-vars */
mnigh marked this conversation as resolved.
Show resolved Hide resolved
> {
stateStyles?: Record<string, string>;
}
}

// TODO: Eventually extend react-select's Select Props, so that anything extra is passed through.
export interface SelectFieldV2Props {
/** Optional HTML id used to identify the element. Default: camelCase of `label`. */
Expand All @@ -37,6 +55,7 @@ export interface SelectFieldV2Props {
/** Whether to force all form values into array, even single Select. */
forceArrayFormValue?: boolean;
isLoading?: boolean;
trackUnsaved?: boolean;
}

// User-defined type guard for react-select's readonly Options.
Expand Down Expand Up @@ -88,6 +107,22 @@ const LocalizedNoOptionsMessage = <
</components.NoOptionsMessage>
);
};

const StateStyledSelectContainer = ({
children,
...props
}: ContainerProps<Option | Group<Option>>) => {
const { stateStyles } = props.selectProps;

return (
<div {...stateStyles} data-h2-radius="base(input)">
<components.SelectContainer {...props}>
{children}
</components.SelectContainer>
</div>
);
};

/**
* One-off hook to add default messages to validation rule object in place of booleans.
*
Expand Down Expand Up @@ -121,6 +156,7 @@ const SelectFieldV2 = ({
isMulti = false,
forceArrayFormValue = false,
isLoading = false,
trackUnsaved = true,
}: SelectFieldV2Props): JSX.Element => {
const { formatMessage } = useIntl();

Expand All @@ -140,6 +176,8 @@ const SelectFieldV2 = ({
// TODO: Set explicit TFieldValues. Defaults to Record<string, any>
} = useFormContext();

const stateStyles = useFieldStateStyles(name, !trackUnsaved);

const error = errors[name]?.message as FieldError;
const isRequired = !!rules?.required;
// react-hook-form has no way to set default messages when `{ required: true }`,
Expand All @@ -153,6 +191,7 @@ const SelectFieldV2 = ({
{...{ label, context, error }}
inputId={id}
required={isRequired}
trackUnsaved={trackUnsaved}
>
<div style={{ width: "100%" }}>
<Controller
Expand Down Expand Up @@ -224,6 +263,7 @@ const SelectFieldV2 = ({
components={{
LoadingMessage: LocalizedLoadingMessage,
NoOptionsMessage: LocalizedNoOptionsMessage,
SelectContainer: StateStyledSelectContainer,
}}
// Adds predictable prefix, helpful for both theming and Jest testing.
// E.g., `react-select__control` instead of `css-1s2u09g__control`.
Expand All @@ -235,6 +275,8 @@ const SelectFieldV2 = ({
onChange={convertSingleOrMultiOptionsToValues}
aria-label={label}
aria-required={isRequired}
{...stateStyles}
stateStyles={stateStyles}
styles={{
placeholder: (provided) => ({
...provided,
Expand All @@ -252,6 +294,12 @@ const SelectFieldV2 = ({
...provided,
...accessibleTextStyle,
}),
control: (provided) => ({
...provided,
backgroundColor: "inherit",
border: "none",
boxShadow: "none",
}),
// Setting the z-index to 11 since the InputLabel is set to 10.
menu: (provided) => ({ ...provided, zIndex: 11 }),
}}
Expand Down
11 changes: 3 additions & 8 deletions frontend/talentsearch/src/js/components/support/SupportForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ import * as React from "react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useIntl } from "react-intl";
import { toast } from "@common/components/Toast";
import { Input, Select, Submit, TextArea } from "@common/components/form";
import { Input, Submit, TextArea } from "@common/components/form";
import { errorMessages } from "@common/messages";
import { getFullNameLabel } from "@common/helpers/nameUtils";
import Pending from "@common/components/Pending";
import { useState } from "react";
import Button from "@common/components/Button";
import SelectFieldV2 from "@common/components/form/Select/SelectFieldV2";
import { useGetMeQuery, User } from "../../api/generated";
import {
API_SUPPORT_ENDPOINT,
Expand Down Expand Up @@ -158,7 +159,7 @@ export const SupportForm = ({
}}
trackUnsaved={false}
/>
<Select
<SelectFieldV2
id="subject"
name="subject"
rules={{
Expand All @@ -169,12 +170,6 @@ export const SupportForm = ({
id: "094835",
description: "Support form subject field label",
})}
nullSelection={intl.formatMessage({
defaultMessage: "Select...",
id: "S/n44h",
mnigh marked this conversation as resolved.
Show resolved Hide resolved
description:
"Support form subject field default placeholder shown when nothing actively selected",
})}
options={[
{
value: "bug",
Expand Down