Skip to content

Commit

Permalink
feat: added FormControl input mask (#2626)
Browse files Browse the repository at this point in the history
  • Loading branch information
PKulkoRaccoonGang authored Dec 8, 2023
1 parent b28ecea commit 8f2c06c
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 35 deletions.
78 changes: 53 additions & 25 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"react-colorful": "^5.6.1",
"react-dropzone": "^14.2.1",
"react-focus-on": "^3.5.4",
"react-imask": "^7.1.3",
"react-loading-skeleton": "^3.1.0",
"react-popper": "^2.2.5",
"react-proptype-conditional-require": "^1.0.4",
Expand Down Expand Up @@ -190,5 +191,8 @@
"www",
"icons",
"dependent-usage-analyzer"
]
],
"overrides": {
"@testing-library/dom": "9.3.3"
}
}
9 changes: 5 additions & 4 deletions src/DataTable/tests/TableActions.test.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import classNames from 'classnames';
import TableActions from '../TableActions';
Expand Down Expand Up @@ -216,9 +216,10 @@ describe('<TableActions />', () => {
expect(overflowToggle).toBeInTheDocument();

userEvent.click(overflowToggle);

const buttons = screen.getAllByRole('button');
expect(buttons.length).toBeGreaterThan(1);
waitFor(() => {
const buttons = screen.getAllByRole('button');
expect(buttons.length).toBeGreaterThan(1);
});
});

it('renders the correct alt text for the dropdown', () => {
Expand Down
8 changes: 7 additions & 1 deletion src/Form/FormControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useCallback, useEffect } from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import RBFormControl from 'react-bootstrap/FormControl';
import { IMaskInput } from 'react-imask';
import { useFormGroupContext } from './FormGroupContext';
import FormControlFeedback from './FormControlFeedback';
import FormControlDecoratorGroup from './FormControlDecoratorGroup';
Expand All @@ -17,6 +18,7 @@ const FormControl = React.forwardRef(({
floatingLabel,
autoResize,
onChange,
inputMask,
...props
}, ref) => {
const {
Expand Down Expand Up @@ -71,7 +73,7 @@ const FormControl = React.forwardRef(({
className={className}
>
<RBFormControl
as={as}
as={inputMask ? IMaskInput : as}
ref={resolvedRef}
size={size}
isInvalid={isInvalid}
Expand All @@ -80,6 +82,7 @@ const FormControl = React.forwardRef(({
'has-value': hasValue,
})}
onChange={handleOnChange}
mask={inputMask}
{...controlProps}
/>
</FormControlDecoratorGroup>
Expand Down Expand Up @@ -122,6 +125,8 @@ FormControl.propTypes = {
isInvalid: PropTypes.bool,
/** Only for `as="textarea"`. Specifies whether the input can be resized according to the height of content. */
autoResize: PropTypes.bool,
/** Specifies what format to use for the input mask. */
inputMask: PropTypes.string,
};

FormControl.defaultProps = {
Expand All @@ -140,6 +145,7 @@ FormControl.defaultProps = {
isValid: undefined,
isInvalid: undefined,
autoResize: false,
inputMask: undefined,
};

export default FormControl;
143 changes: 142 additions & 1 deletion src/Form/form-control.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ or [select attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element
}
```


## Input types

```jsx live
Expand Down Expand Up @@ -163,6 +162,148 @@ or [select attributes](https://developer.mozilla.org/en-US/docs/Web/HTML/Element
}
```

## Input masks
Paragon uses the [react-imask](https://www.npmjs.com/package/react-imask) library,
which allows you to add masks of different types for inputs.
To create your own mask, you need to pass the required mask pattern (`+{1} (000) 000-0000`) to the `inputMask` property. <br />
See [react-imask](https://imask.js.org) for documentation on available props.

```jsx live
() => {
{/* start example state */}
const [maskType, setMaskType] = useState('phone');
{/* end example state */}

const inputsWithMask = {
phone: (
<>
<h3>Phone</h3>
<Form.Group>
<Form.Control
inputMask="+{1} (000) 000-0000"
value={value}
onChange={handleChange}
leadingElement={<Icon src={FavoriteBorder} />}
floatingLabel="What is your phone number?"
/>
</Form.Group>
</>
),
creditCard: (<>
<h3>Credit card</h3>
<Form.Group>
<Form.Control
inputMask="0000 0000 0000 0000"
value={value}
onChange={handleChange}
leadingElement={<Icon src={FavoriteBorder} />}
floatingLabel="What is your credit card number?"
/>
</Form.Group>
</>),
securePassword: (<>
<h3>Secure password</h3>
<Form.Group>
<Form.Control
inputMask="XXX-XX-0000"
value={value}
definitions={{
X: {
mask: '0',
displayChar: 'X',
placeholderChar: '#',
},
}}
onChange={handleChange}
leadingElement={<Icon src={FavoriteBorder} />}
floatingLabel="What is your password?"
/>
</Form.Group>
</>),
OTPpassword: (<>
<h3>OTP password</h3>
<Form.Group>
<Form.Control
inputMask="G-00000"
value={value}
onChange={handleChange}
leadingElement={<Icon src={FavoriteBorder} />}
floatingLabel="What is your OPT password?"
/>
</Form.Group>
</>),
price: (
<>
<h3>Course priсe</h3>
<Form.Group>
<Form.Control
inputMask="$num"
blocks={{
num: {
// nested masks are available!
mask: Number,
thousandsSeparator: ' '
}
}}
value={value}
onChange={handleChange}
leadingElement={<Icon src={FavoriteBorder} />}
floatingLabel="What is the price of this course?"
/>
</Form.Group>
</>
),
};

const [value, setValue] = useState('');

const handleChange = (e) => setValue(e.target.value);

return (
<>
{/* start example form block */}
<ExamplePropsForm
inputs={[
{ value: maskType, setValue: setMaskType, options: Object.keys(inputsWithMask), name: 'Mask variants' },
]}
/>
{/* end example form block */}

{inputsWithMask[maskType]}
</>
);
}
```

## Input masks with clear value
To get a value without a mask, you need to use `onChange` instead of `onAccept` to handle changes.

```jsx live
() => {
const [value, setValue] = useState('');

return (
<>
<Form.Group>
<Form.Control
inputMask="+{1} (000) 000-0000"
leadingElement={<Icon src={FavoriteBorder} />}
trailingElement={<Icon src={Verified} />}
floatingLabel="What is your phone number?"
value={value}
// depending on prop above first argument is
// `value` if `unmask=false`,
// `unmaskedValue` if `unmask=true`,
// `typedValue` if `unmask='typed'`
onAccept={(_, mask) => setValue(mask._unmaskedValue)}
/>
</Form.Group>
Unmasked value: {JSON.stringify(value)}
</>
);
}
```

## Textarea autoResize

`autoResize` prop allows input to be resized according to the content height.
Expand Down
Loading

0 comments on commit 8f2c06c

Please sign in to comment.