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:
parent
84ff66c373
commit
5693d1945a
@ -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';
|
||||
|
@ -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
|
||||
|
@ -1,2 +0,0 @@
|
||||
export { Input } from './input';
|
||||
export { INPUT_TYPES } from './input.constants';
|
4
ui/components/component-library/input/index.ts
Normal file
4
ui/components/component-library/input/index.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export { Input } from './input';
|
||||
export { InputType } from './input.types';
|
||||
|
||||
export type { InputProps } from './input.types';
|
@ -1,6 +0,0 @@
|
||||
export const INPUT_TYPES = {
|
||||
TEXT: 'text',
|
||||
NUMBER: 'number',
|
||||
PASSWORD: 'password',
|
||||
SEARCH: 'search',
|
||||
};
|
@ -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';
|
@ -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
|
@ -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');
|
76
ui/components/component-library/input/input.tsx
Normal file
76
ui/components/component-library/input/input.tsx
Normal 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>)}
|
||||
/>
|
||||
),
|
||||
);
|
107
ui/components/component-library/input/input.types.ts
Normal file
107
ui/components/component-library/input/input.types.ts
Normal 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;
|
Loading…
Reference in New Issue
Block a user