1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00

Migrate component to TS: Input (#20094)

* Migrating Input component

* Adjusting types and fixing design system import

---------

Co-authored-by: georgewrmarshall <george.marshall@consensys.net>
This commit is contained in:
Dhruv 2023-07-25 09:43:17 +05:30 committed by GitHub
parent 84ff66c373
commit 5693d1945a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 231 additions and 217 deletions

View File

@ -31,7 +31,7 @@ export { PickerNetwork } from './picker-network';
export { Tag } from './tag';
export { TagUrl } from './tag-url';
export { Text, ValidTag, TextDirection, InvisibleCharacter } from './text';
export { Input, INPUT_TYPES } from './input';
export { Input, InputType } from './input';
export { TextField, TEXT_FIELD_TYPES, TEXT_FIELD_SIZES } from './text-field';
export { TextFieldSearch } from './text-field-search';
export { ModalContent, ModalContentSize } from './modal-content';

View File

@ -12,7 +12,7 @@ import { Input } from './input';
## Props
The `Input` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props
The `Input` accepts all props below as well as all [Box](/docs/components-componentlibrary-box--docs#props) component props
<ArgsTable of={Input} />
@ -22,24 +22,24 @@ Use the `type` prop to change the type of input.
Possible types include:
- `INPUT_TYPES.TEXT`
- `INPUT_TYPES.NUMBER`
- `INPUT_TYPES.PASSWORD`
- `INPUT_TYPES.SEARCH`
- `InputType.Text`
- `InputType.Number`
- `InputType.Password`
- `InputType.Search`
Defaults to `INPUT_TYPES.TEXT`.
Defaults to `InputType.Text`.
<Canvas>
<Story id="components-componentlibrary-input--type" />
</Canvas>
```jsx
import { Input, INPUT_TYPES } from '../../component-library';
import { Input, InputType } from '../../component-library';
<Input type={INPUT_TYPES.TEXT} />
<Input type={INPUT_TYPES.NUMBER} />
<Input type={INPUT_TYPES.PASSWORD} />
<Input type={INPUT_TYPES.SEARCH} />
<Input type={InputType.Text} />
<Input type={InputType.Number} />
<Input type={InputType.Password} />
<Input type={InputType.Search} />
```
### Ref
@ -81,9 +81,9 @@ Use the `autoComplete` prop to set the autocomplete html attribute. It allows th
</Canvas>
```jsx
import { Input } from '../../component-library';
import { Input, InputType } from '../../component-library';
<Input type={INPUT_TYPES.PASSWORD} autoComplete />;
<Input type={InputType.Password} autoComplete />;
```
### Auto Focus

View File

@ -1,2 +0,0 @@
export { Input } from './input';
export { INPUT_TYPES } from './input.constants';

View File

@ -0,0 +1,4 @@
export { Input } from './input';
export { InputType } from './input.types';
export type { InputProps } from './input.types';

View File

@ -1,6 +0,0 @@
export const INPUT_TYPES = {
TEXT: 'text',
NUMBER: 'number',
PASSWORD: 'password',
SEARCH: 'search',
};

View File

@ -1,170 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import {
TextVariant,
BackgroundColor,
BorderStyle,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box';
import { Text } from '..';
import { INPUT_TYPES } from './input.constants';
export const Input = React.forwardRef(
(
{
autoComplete,
autoFocus,
className,
defaultValue,
disabled,
error,
id,
maxLength,
name,
onBlur,
onChange,
onFocus,
placeholder,
readOnly,
required,
type = 'text',
value,
textVariant = TextVariant.bodyMd,
disableStateStyles,
...props
},
ref,
) => (
<Text
className={classnames(
'mm-input',
{
'mm-input--disable-state-styles': disableStateStyles,
'mm-input--disabled': disabled && !disableStateStyles,
},
className,
)}
aria-invalid={error}
as="input"
autoComplete={autoComplete ? 'on' : 'off'}
autoFocus={autoFocus}
backgroundColor={BackgroundColor.transparent}
borderStyle={BorderStyle.none}
defaultValue={defaultValue}
disabled={disabled}
id={id}
margin={0}
maxLength={maxLength}
name={name}
onBlur={onBlur}
onChange={onChange}
onFocus={onFocus}
padding={0}
placeholder={placeholder}
readOnly={readOnly}
ref={ref}
required={required}
value={value}
variant={textVariant}
type={type}
{...props}
/>
),
);
Input.propTypes = {
/**
* Autocomplete allows the browser to predict the value based on earlier typed values
*/
autoComplete: PropTypes.bool,
/**
* If `true`, the input will be focused during the first mount.
*/
autoFocus: PropTypes.bool,
/**
* An additional className to apply to the input
*/
className: PropTypes.string,
/**
* The default input value, useful when not controlling the component.
*/
defaultValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* If `true`, the input will be disabled.
*/
disabled: PropTypes.bool,
/**
* Disables focus state by setting CSS outline: none;
* !!IMPORTANT!!
* If this is set to true ensure there is a proper fallback
* to enable accessibility for keyboard only and vision impaired users
*/
disableStateStyles: PropTypes.bool,
/**
* If `true`, aria-invalid will be true
*/
error: PropTypes.bool,
/**
* The id of the `input` element.
*/
id: PropTypes.string,
/**
* Max number of characters to allow
*/
maxLength: PropTypes.number,
/**
* Name attribute of the `input` element.
*/
name: PropTypes.string,
/**
* Callback fired on blur
*/
onBlur: PropTypes.func,
/**
* Callback fired when the value is changed.
*/
onChange: PropTypes.func,
/**
* Callback fired on focus
*/
onFocus: PropTypes.func,
/**
* The short hint displayed in the input before the user enters a value.
*/
placeholder: PropTypes.string,
/**
* It prevents the user from changing the value of the field (not from interacting with the field).
*/
readOnly: PropTypes.bool,
/**
* If `true`, the input will be required. Currently no visual difference is shown.
*/
required: PropTypes.bool,
/**
* Use this to override the text variant of the Text component.
* Should only be used for approved custom input components
* Use the TextVariant enum
*/
textVariant: PropTypes.oneOf(Object.values(TextVariant)),
/**
* Type of the input element. Can be INPUT_TYPES.TEXT, INPUT_TYPES.PASSWORD, INPUT_TYPES.NUMBER
* Defaults to INPUT_TYPES.TEXT ('text')
* If you require another type add it to INPUT_TYPES
*/
type: PropTypes.oneOf(Object.values(INPUT_TYPES)),
/**
* The input value, required for a controlled component.
*/
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
/**
* Input accepts all the props from Box
*/
...Box.propTypes,
};
Input.displayName = 'Input';

View File

@ -1,16 +1,16 @@
import React, { useRef } from 'react';
import { Meta } from '@storybook/react';
import { useArgs } from '@storybook/client-api';
import {
DISPLAY,
FLEX_DIRECTION,
Display,
FlexDirection,
TextVariant,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
import { Button } from '..';
import { Button, Box, BUTTON_VARIANT } from '..';
import { INPUT_TYPES } from './input.constants';
import { InputType } from './input.types';
import { Input } from './input';
import README from './README.mdx';
@ -92,7 +92,7 @@ export default {
},
type: {
control: 'select',
options: Object.values(INPUT_TYPES),
options: Object.values(InputType),
},
value: {
control: 'text',
@ -126,7 +126,7 @@ export default {
placeholder: 'Placeholder...',
value: '',
},
};
} as Meta<typeof Input>;
const Template = (args) => {
const [{ value }, updateArgs] = useArgs();
@ -141,14 +141,14 @@ DefaultStory.storyName = 'Default';
export const Type = (args) => (
<Box
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
display={Display.InlineFlex}
flexDirection={FlexDirection.Column}
gap={4}
>
<Input {...args} placeholder="Default" />
<Input {...args} type={INPUT_TYPES.PASSWORD} placeholder="Password" />
<Input {...args} type={INPUT_TYPES.NUMBER} placeholder="Number" />
<Input {...args} type={INPUT_TYPES.SEARCH} placeholder="Search" />
<Input {...args} type={InputType.Password} placeholder="Password" />
<Input {...args} type={InputType.Number} placeholder="Number" />
<Input {...args} type={InputType.Search} placeholder="Search" />
</Box>
);
@ -158,17 +158,21 @@ Type.args = {
export const Ref = (args) => {
const [{ value }, updateArgs] = useArgs();
const inputRef = useRef(null);
const inputRef = useRef<HTMLInputElement>(null);
const handleOnClick = () => {
inputRef.current.focus();
inputRef.current?.focus();
};
const handleOnChange = (e) => {
updateArgs({ value: e.target.value });
};
return (
<Box display={DISPLAY.FLEX}>
<Box display={Display.Flex}>
<Input {...args} ref={inputRef} value={value} onChange={handleOnChange} />
<Button marginLeft={1} onClick={handleOnClick}>
<Button
variant={BUTTON_VARIANT.PRIMARY}
marginLeft={1}
onClick={handleOnClick}
>
Edit
</Button>
</Box>
@ -178,7 +182,7 @@ export const Ref = (args) => {
export const AutoComplete = Template.bind({});
AutoComplete.args = {
autoComplete: true,
type: INPUT_TYPES.PASSWORD,
type: InputType.Password,
placeholder: 'Enter password',
};
@ -201,8 +205,9 @@ MaxLength.args = { maxLength: 10, placeholder: 'Max length 10' };
export const ReadOnly = Template.bind({});
ReadOnly.args = { readOnly: true, value: 'Read only' };
export const Required = Template.bind({});
Required.args = { required: true, placeholder: 'Required' };
export const RequiredStory = Template.bind({});
RequiredStory.args = { required: true, placeholder: 'Required' };
RequiredStory.storyName = 'Required';
export const DisableStateStyles = Template.bind({});
DisableStateStyles.args = {
@ -216,8 +221,8 @@ export const TextVariantStory = (args) => {
};
return (
<Box
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
display={Display.InlineFlex}
flexDirection={FlexDirection.Column}
gap={4}
>
<Input

View File

@ -5,7 +5,7 @@ import { renderWithUserEvent } from '../../../../test/lib/render-helpers';
import { TextVariant } from '../../../helpers/constants/design-system';
import { Input } from './input';
import { INPUT_TYPES } from './input.constants';
import { InputType } from './input.types';
describe('Input', () => {
it('should render correctly', () => {
@ -72,9 +72,9 @@ describe('Input', () => {
const { getByTestId } = render(
<>
<Input data-testid="input-text-default" />
<Input type={INPUT_TYPES.TEXT} data-testid="input-text" />
<Input type={INPUT_TYPES.NUMBER} data-testid="input-number" />
<Input type={INPUT_TYPES.PASSWORD} data-testid="input-password" />
<Input type={InputType.Text} data-testid="input-text" />
<Input type={InputType.Number} data-testid="input-number" />
<Input type={InputType.Password} data-testid="input-password" />
</>,
);
expect(getByTestId('input-text-default')).toHaveAttribute('type', 'text');

View File

@ -0,0 +1,76 @@
import React from 'react';
import classnames from 'classnames';
import {
TextVariant,
BackgroundColor,
BorderStyle,
} from '../../../helpers/constants/design-system';
import { Text, TextProps } from '../text';
import { PolymorphicRef } from '../box';
import { InputProps, InputType, InputComponent } from './input.types';
export const Input: InputComponent = React.forwardRef(
<C extends React.ElementType = 'input'>(
{
autoComplete,
autoFocus,
className = '',
defaultValue,
disabled,
error,
id,
maxLength,
name,
onBlur,
onChange,
onFocus,
placeholder,
readOnly,
required,
type = InputType.Text,
value,
textVariant = TextVariant.bodyMd,
disableStateStyles,
...props
}: InputProps<C>,
ref: PolymorphicRef<C>,
) => (
<Text
className={classnames(
'mm-input',
{
'mm-input--disable-state-styles': Boolean(disableStateStyles),
'mm-input--disabled':
Boolean(disabled) && Boolean(disableStateStyles),
},
className,
)}
aria-invalid={error}
as="input"
autoComplete={autoComplete ? 'on' : 'off'}
autoFocus={autoFocus}
backgroundColor={BackgroundColor.transparent}
borderStyle={BorderStyle.none}
defaultValue={defaultValue}
disabled={disabled}
id={id}
margin={0}
maxLength={maxLength}
name={name}
onBlur={onBlur}
onChange={onChange}
onFocus={onFocus}
padding={0}
placeholder={placeholder}
readOnly={readOnly}
ref={ref}
required={required}
value={value}
variant={textVariant}
type={type}
{...(props as TextProps<C>)}
/>
),
);

View File

@ -0,0 +1,107 @@
import type {
StyleUtilityProps,
PolymorphicComponentPropWithRef,
} from '../box';
import { TextVariant } from '../../../helpers/constants/design-system';
export enum InputType {
Text = 'text',
// eslint-disable-next-line @typescript-eslint/no-shadow
Number = 'number',
Password = 'password',
Search = 'search',
}
export interface InputStyleProps extends StyleUtilityProps {
/**
* Autocomplete allows the browser to predict the value based on earlier typed values
*/
autoComplete?: boolean;
/**
* If `true`, the input will be focused during the first mount.
*/
autoFocus?: boolean;
/**
* An additional className to apply to the input
*/
className?: string;
/**
* The default input value, useful when not controlling the component.
*/
defaultValue?: string | number;
/**
* If `true`, the input will be disabled.
*/
disabled?: boolean;
/**
* Disables focus state by setting CSS outline: none;
* !!IMPORTANT!!
* If this is set to true ensure there is a proper fallback
* to enable accessibility for keyboard only and vision impaired users
*/
disableStateStyles?: boolean;
/**
* If `true`, aria-invalid will be true
*/
error?: boolean;
/**
* The id of the `input` element.
*/
id?: string;
/**
* Max number of characters to allow
*/
maxLength?: number;
/**
* Name attribute of the `input` element.
*/
name?: string;
/**
* Callback fired on blur
*/
onBlur?: () => void;
/**
* Callback fired when the value is changed.
*/
onChange?: () => void;
/**
* Callback fired on focus
*/
onFocus?: () => void;
/**
* The short hint displayed in the input before the user enters a value.
*/
placeholder?: string;
/**
* It prevents the user from changing the value of the field (not from interacting with the field).
*/
readOnly?: boolean;
/**
* If `true`, the input will be required. Currently no visual difference is shown.
*/
required?: boolean;
/**
* Use this to override the text variant of the Text component.
* Should only be used for approved custom input components
* Use the TextVariant enum
*/
textVariant?: TextVariant;
/**
* Type of the input element. Can be InputType.Text, InputType.Password, InputType.Number
* Defaults to InputType.Text ('text')
* If you require another type add it to InputType
*/
type?: InputType;
/**
* The input value, required for a controlled component.
*/
value?: string | number;
}
export type InputProps<C extends React.ElementType> =
PolymorphicComponentPropWithRef<C, InputStyleProps>;
export type InputComponent = <C extends React.ElementType = 'input'>(
props: InputProps<C>,
) => React.ReactElement | null;