Skip to content

Commit

Permalink
Added regex prop to reject specified characters (#1781)
Browse files Browse the repository at this point in the history
  • Loading branch information
CalvinChiramal authored Aug 21, 2023
1 parent e725c17 commit 7c139fa
Show file tree
Hide file tree
Showing 5 changed files with 67 additions and 16 deletions.
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ export interface InputProps
labelProps?: LabelProps;
maxLength?: number;
unlimitedChars: boolean;
rejectCharsRegex?: RegExp;
}

export type LabelProps = {
Expand Down
17 changes: 16 additions & 1 deletion src/components/Input.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import React, { useState, forwardRef } from "react";
import { useId } from "@reach/auto-id";
import classnames from "classnames";
import PropTypes from "prop-types";
import { replace } from "ramda";

import { hyphenize } from "utils";

Expand All @@ -29,6 +30,7 @@ const Input = forwardRef(
maxLength,
unlimitedChars = false,
labelProps,
rejectCharsRegex,
...otherProps
},
ref
Expand All @@ -50,6 +52,14 @@ const Input = forwardRef(
const onChange = otherProps.onChange || onChangeInternal;
const isMaxLengthPresent = !!maxLength || maxLength === 0;

const handleRegexChange = e => {
const globalRegex = new RegExp(rejectCharsRegex, "g");
e.target.value = replace(globalRegex, "", e.target.value);
onChange(e);
};

const handleChange = rejectCharsRegex ? handleRegexChange : onChange;

return (
<div className={classnames(["neeto-ui-input__wrapper", className])}>
<div className="neeto-ui-input__label-wrapper">
Expand Down Expand Up @@ -101,7 +111,7 @@ const Input = forwardRef(
{...(isMaxLengthPresent && !unlimitedChars && { maxLength })}
{...otherProps}
value={value}
onChange={onChange}
onChange={handleChange}
/>
{suffix && <div className="neeto-ui-input__suffix">{suffix}</div>}
</div>
Expand Down Expand Up @@ -197,6 +207,11 @@ Input.propTypes = {
* To specify whether the Input field is required or not.
*/
required: PropTypes.bool,
/**
* To specify a regex to be matched against the user input. Any character that matches it
* cannot be input by the user. It will also prevent such characters from being pasted into the input.
*/
rejectCharsRegex: PropTypes.instanceOf(RegExp),
};

export default Input;
16 changes: 16 additions & 0 deletions stories/Components/Input.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const metadata = {
url: "https://www.figma.com/file/zhdsnPzXzr264x1WUeVdmA/02-Components?node-id=104%3A11",
},
},
argTypes: { rejectCharsRegex: { control: "text" } },
};

const Template = args => <Input {...args} />;
Expand Down Expand Up @@ -177,6 +178,20 @@ FormikInputStory.parameters = {
},
};

const RejectCharsInputStory = args => (
<Input {...args} label="No numbers" rejectCharsRegex={/[0-9]+/} />
);

RejectCharsInputStory.storyName = "Reject specific characters";
RejectCharsInputStory.parameters = {
docs: {
description: {
story: `The prop \`rejectCharsRegex\` will accept a regex and any character that matches it
cannot be input by the user. It will also prevent such characters from being pasted into the input.`,
},
},
};

export {
Default,
Sizes,
Expand All @@ -189,6 +204,7 @@ export {
SearchInput,
InputWithMaxLength,
FormikInputStory,
RejectCharsInputStory,
};

export default metadata;
13 changes: 13 additions & 0 deletions tests/Input.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ describe("Input", () => {
expect(onChange).toHaveBeenCalledTimes(4);
});

it("should not show matched regex value", () => {
const { getByLabelText } = render(
<Input id="input" label="Input Label" rejectCharsRegex={/[0-9]+/} />
);
const inputField = getByLabelText("Input Label");
userEvent.type(inputField, "12345");
expect(inputField).not.toHaveValue("12345");

userEvent.type(inputField, "abc123");
expect(inputField).toHaveValue("abc");
expect(inputField).not.toHaveValue("123");
});

it("should display error message", () => {
const { getByText } = render(<Input error="Error message" label="input" />);
expect(getByText("Error message")).toBeInTheDocument();
Expand Down
36 changes: 21 additions & 15 deletions tests/formik/Input.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ const TestForm = ({ onSubmit }) => {
<h1>Sign Up</h1>
<Form
formikProps={{
initialValues: {
firstName: "",
lastName: "",
email: "",
},
initialValues: { firstName: "", lastName: "", email: "" },
validationSchema: yup.object().shape({
firstName: yup.string().required("First name is required"),
lastName: yup.string().required("Last name is required"),
Expand All @@ -32,7 +28,11 @@ const TestForm = ({ onSubmit }) => {
onSubmit: handleSubmit,
}}
>
<Input label="First Name" name="firstName" />
<Input
label="First Name"
name="firstName"
rejectCharsRegex={/[0-9]+/}
/>
<Input label="Last Name" name="lastName" />
<Input label="Email" name="email" type="email" />
<button type="submit">Submit</button>
Expand All @@ -44,12 +44,7 @@ const TestForm = ({ onSubmit }) => {
describe("formik/Input", () => {
it("should render without error", () => {
render(
<Form
formikProps={{
initialValues: {},
onSubmit: () => {},
}}
>
<Form formikProps={{ initialValues: {}, onSubmit: () => {} }}>
<Input label="Input label" name="test" />
</Form>
);
Expand Down Expand Up @@ -77,8 +72,19 @@ describe("formik/Input", () => {
render(<TestForm onSubmit={onSubmit} />);
userEvent.type(screen.getByLabelText("Email"), "john.doemail.com");
userEvent.click(screen.getByText("Submit"));
await waitFor(() =>
expect(screen.getByText("Invalid email address")).toBeInTheDocument()
);
expect(
await screen.findByText("First name is required")
).toBeInTheDocument();
});

it("should display validation error when string having only rejected characters is provided", async () => {
const onSubmit = jest.fn();
render(<TestForm onSubmit={onSubmit} />);
userEvent.type(screen.getByLabelText("First Name"), "123");
userEvent.click(screen.getByText("Submit"));

expect(
await screen.findByText("First name is required")
).toBeInTheDocument();
});
});

0 comments on commit 7c139fa

Please sign in to comment.