1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Adding Input component and updating TextFieldBase (#17664)

* Adding Input component and updating TextField

* Exporting from index, removing as prop and updating snapshot
This commit is contained in:
George Marshall 2023-02-21 10:35:28 -08:00 committed by GitHub
parent 1b382e9f2d
commit 527387bbfe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 918 additions and 359 deletions

View File

@ -20,6 +20,7 @@
@import 'button-link/button-link';
@import 'button-primary/button-primary';
@import 'button-secondary/button-secondary';
@import 'input/input';
// Molecules
@import 'picker-network/picker-network';
@import 'tag-url/tag-url';

View File

@ -10,7 +10,7 @@ exports[`FormTextField should render correctly 1`] = `
>
<input
autocomplete="off"
class="box mm-text mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent"
class="box mm-text mm-input mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent box--border-style-none"
focused="false"
type="text"
value=""

View File

@ -27,6 +27,7 @@ export { PickerNetwork } from './picker-network';
export { Tag } from './tag';
export { TagUrl } from './tag-url';
export { Text, TEXT_DIRECTIONS } from './text';
export { Input, INPUT_TYPES } from './input';
export { TextField, TEXT_FIELD_TYPES, TEXT_FIELD_SIZES } from './text-field';
export { TextFieldSearch } from './text-field-search';

View File

@ -0,0 +1,270 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { Input } from './input';
# Input
`Input` lets user enter a text data. Its a light-weighted borderless input used inside of custom inputs. See [TextField](/docs/components-componentlibrary-textfield--default-story#textfield) for common text input.
<Canvas>
<Story id="components-componentlibrary-input--default-story" />
</Canvas>
## Props
The `Input` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props
<ArgsTable of={Input} />
### Type
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`
Defaults to `INPUT_TYPES.TEXT`.
<Canvas>
<Story id="components-componentlibrary-input--type" />
</Canvas>
```jsx
import { Input, INPUT_TYPES } from '../../component-library';
<Input type={INPUT_TYPES.TEXT} />
<Input type={INPUT_TYPES.NUMBER} />
<Input type={INPUT_TYPES.PASSWORD} />
<Input type={INPUT_TYPES.SEARCH} />
```
### Ref
Use the `ref` prop to access the ref of the `<input />` html element of `Input`. This is useful for focusing the input from a button or other component.
<Canvas>
<Story id="components-componentlibrary-input--ref" />
</Canvas>
```jsx
import { Button, Input } from '../../component-library';
const inputRef = useRef(null);
const [value, setValue] = useState('');
const handleOnClick = () => {
inputRef.current.focus();
};
const handleOnChange = (e) => {
setValue(e.target.value);
};
<Input
ref={inputRef}
value={value}
onChange={handleOnChange}
/>
<Button marginLeft={1} onClick={handleOnClick}>
Edit
</Button>
```
### Auto Complete
Use the `autoComplete` prop to set the autocomplete html attribute. It allows the browser to predict the value based on earlier typed values.
<Canvas>
<Story id="components-componentlibrary-input--auto-complete" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input type={INPUT_TYPES.PASSWORD} autoComplete />;
```
### Auto Focus
Use the `autoFocus` prop to focus the `Input` during the first mount
To view story see [Canvas tab](/story/components-componentlibrary-input--auto-focus). Removing it from docs because created annoying reading experience 😁
```jsx
import { Input } from '../../component-library';
<Input autoFocus />;
```
### Default Value
Use the `defaultValue` prop to set the default value of the `Input`. Used for uncontrolled inputs.
<Canvas>
<Story id="components-componentlibrary-input--default-value" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input defaultValue="default value" />;
```
### Disabled
Use the `disabled` prop to set the disabled state of the `Input`
<Canvas>
<Story id="components-componentlibrary-input--disabled" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input disabled />;
```
### Error
Use the `error` prop to set `aria-invalid="true"`. This helps with screen readers for accessibility. There is no visual indicator for `error` this should be handled in the parent component.
<Canvas>
<Story id="components-componentlibrary-input--error-story" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input error />;
```
### Max Length
Use the `maxLength` prop to set the maximum allowed input characters for the `Input`
<Canvas>
<Story id="components-componentlibrary-input--max-length" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input maxLength={10} />;
```
### Read Only
Use the `readOnly` prop to set the `Input` to read only
<Canvas>
<Story id="components-componentlibrary-input--read-only" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input readOnly />;
```
### Required
Use the `required` prop to set the html `required` attribute used by the browser api. There is no visual indicator for `required` this should be handled in the parent component.
<Canvas>
<Story id="components-componentlibrary-input--required" />
</Canvas>
```jsx
import { Input } from '../../component-library';
// No visual indicator. Used by the browser api
<Input required />;
```
### Disable Style States
Use the `disableStyleStates` to remove disabled and focus styles
#### IMPORTANT NOTE
This sets the CSS to `outline: none` so ensure there is a proper fallback to enable accessibility for keyboard only and vision impaired users. Check `TextField` source code to see how it is done properly.
<Canvas>
<Story id="components-componentlibrary-input--disable-state-styles" />
</Canvas>
```jsx
import { Input } from '../../component-library';
<Input disableStyleStates />;
```
### Text Variant
Use the `textVariant` and `TextVariant` enum to change the font size and style of the input
#### IMPORTANT NOTE
This should RARELY be used but it is available for custom inputs that require larger text
<Canvas>
<Story id="components-componentlibrary-input--text-variant-story" />
</Canvas>
```jsx
import { TextVariant } from '../../../helpers/constants/design-system';
import { Input } from '../../component-library';
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.displayMd}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingLg}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingMd}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingSm}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyLgMedium}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyMdBold}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyMd}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodySm}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodySmBold}
/>
<Input
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyXs}
/>
```

View File

@ -0,0 +1,12 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Input should render correctly 1`] = `
<div>
<input
autocomplete="off"
class="box mm-text mm-input mm-text--body-md mm-text--color-text-default box--flex-direction-row box--background-color-transparent box--border-style-none"
type="text"
value=""
/>
</div>
`;

View File

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

View File

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

View File

@ -0,0 +1,170 @@
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 '../text';
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

@ -0,0 +1,16 @@
.mm-input {
--input-opacity-disabled: 0.5;
box-sizing: content-box;
&--disable-state-styles {
&:focus,
&:focus-visible {
outline: none;
}
}
&--disabled {
opacity: var(--input-opacity-disabled);
}
}

View File

@ -0,0 +1,287 @@
import React, { useRef } from 'react';
import { useArgs } from '@storybook/client-api';
import {
DISPLAY,
FLEX_DIRECTION,
TextVariant,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box/box';
import { Button } from '..';
import { INPUT_TYPES } from './input.constants';
import { Input } from './input';
import README from './README.mdx';
const marginSizeControlOptions = [
undefined,
0,
1,
2,
3,
4,
5,
6,
7,
8,
9,
10,
11,
12,
'auto',
];
export default {
title: 'Components/ComponentLibrary/Input',
component: Input,
parameters: {
docs: {
page: README,
},
},
argTypes: {
autoComplete: {
control: 'boolean',
},
autoFocus: {
control: 'boolean',
},
className: {
control: 'text',
},
defaultValue: {
control: 'text',
},
disabled: {
control: 'boolean',
},
disableStateStyles: {
control: 'boolean',
},
error: {
control: 'boolean',
},
id: {
control: 'text',
},
maxLength: {
control: 'number',
},
name: {
control: 'text',
},
onBlur: {
action: 'onBlur',
},
onChange: {
action: 'onChange',
},
onFocus: {
action: 'onFocus',
},
placeholder: {
control: 'text',
},
readOnly: {
control: 'boolean',
},
required: {
control: 'boolean',
},
type: {
control: 'select',
options: Object.values(INPUT_TYPES),
},
value: {
control: 'text',
},
textVariant: {
control: 'select',
options: Object.values(TextVariant),
},
marginTop: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginRight: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginBottom: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
marginLeft: {
options: marginSizeControlOptions,
control: 'select',
table: { category: 'box props' },
},
},
args: {
placeholder: 'Placeholder...',
value: '',
},
};
const Template = (args) => {
const [{ value }, updateArgs] = useArgs();
const handleOnChange = (e) => {
updateArgs({ value: e.target.value });
};
return <Input {...args} value={value} onChange={handleOnChange} />;
};
export const DefaultStory = Template.bind({});
DefaultStory.storyName = 'Default';
export const Type = (args) => (
<Box
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.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" />
</Box>
);
Type.args = {
value: undefined,
};
export const Ref = (args) => {
const [{ value }, updateArgs] = useArgs();
const inputRef = useRef(null);
const handleOnClick = () => {
inputRef.current.focus();
};
const handleOnChange = (e) => {
updateArgs({ value: e.target.value });
};
return (
<Box display={DISPLAY.FLEX}>
<Input {...args} ref={inputRef} value={value} onChange={handleOnChange} />
<Button marginLeft={1} onClick={handleOnClick}>
Edit
</Button>
</Box>
);
};
export const AutoComplete = Template.bind({});
AutoComplete.args = {
autoComplete: true,
type: INPUT_TYPES.PASSWORD,
placeholder: 'Enter password',
};
export const AutoFocus = Template.bind({});
AutoFocus.args = { autoFocus: true };
export const DefaultValue = () => (
<Input placeholder="Default value" defaultValue="Default value" />
);
export const Disabled = Template.bind({});
Disabled.args = { disabled: true };
export const ErrorStory = Template.bind({});
ErrorStory.args = { error: true };
ErrorStory.storyName = 'Error';
export const MaxLength = Template.bind({});
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 DisableStateStyles = Template.bind({});
DisableStateStyles.args = {
disableStateStyles: true,
};
export const TextVariantStory = (args) => {
const [{ value }, updateArgs] = useArgs();
const handleOnChange = (e) => {
updateArgs({ value: e.target.value });
};
return (
<Box
display={DISPLAY.INLINE_FLEX}
flexDirection={FLEX_DIRECTION.COLUMN}
gap={4}
>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.displayMd}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingLg}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingMd}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.headingSm}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyLgMedium}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyMdBold}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyMd}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodySm}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodySmBold}
/>
<Input
{...args}
value={value}
onChange={handleOnChange}
textVariant={TextVariant.bodyXs}
/>
</Box>
);
};
TextVariantStory.storyName = 'Text Variant';

View File

@ -0,0 +1,145 @@
/* eslint-disable jest/require-top-level-describe */
import React from 'react';
import { fireEvent, render, act } from '@testing-library/react';
import { renderWithUserEvent } from '../../../../test/lib/render-helpers';
import { TextVariant } from '../../../helpers/constants/design-system';
import { Input } from './input';
import { INPUT_TYPES } from './input.constants';
describe('Input', () => {
it('should render correctly', () => {
const { getByRole, container } = render(<Input />);
expect(getByRole('textbox')).toBeDefined();
expect(container).toMatchSnapshot();
});
it('should render correctly with custom className', () => {
const { getByRole } = render(<Input className="test" />);
expect(getByRole('textbox')).toHaveClass('test');
});
it('should render and be able to input text', () => {
const { getByTestId } = render(<Input data-testid="input" />);
const InputComponent = getByTestId('input');
expect(InputComponent.value).toBe(''); // initial value is empty string
fireEvent.change(InputComponent, { target: { value: 'text value' } });
expect(InputComponent.value).toBe('text value');
fireEvent.change(InputComponent, { target: { value: '' } }); // reset value
expect(InputComponent.value).toBe(''); // value is empty string after reset
});
it('should render without state styles when disableStateStyles is true', async () => {
const { getByTestId, user } = renderWithUserEvent(
<Input data-testid="input" disableStateStyles />,
);
const InputComponent = getByTestId('input');
await user.click(InputComponent);
expect(getByTestId('input')).toHaveFocus();
expect(getByTestId('input')).toHaveClass('mm-input--disable-state-styles');
expect(getByTestId('input')).not.toHaveClass('mm-input--disabled');
});
it('should render and fire onFocus and onBlur events', async () => {
const onFocus = jest.fn();
const onBlur = jest.fn();
const { getByTestId, user } = renderWithUserEvent(
<Input data-testid="input" onFocus={onFocus} onBlur={onBlur} />,
);
const InputComponent = getByTestId('input');
await user.click(InputComponent);
expect(onFocus).toHaveBeenCalledTimes(1);
fireEvent.blur(InputComponent);
expect(onBlur).toHaveBeenCalledTimes(1);
});
it('should pass ref to allow input to focus through another element', () => {
const ref = React.createRef();
const { getByRole } = renderWithUserEvent(<Input ref={ref} />);
act(() => ref.current.focus());
expect(getByRole('textbox')).toHaveFocus();
});
it('should render and fire onChange event', async () => {
const onChange = jest.fn();
const { getByTestId, user } = renderWithUserEvent(
<Input data-testid="input" onChange={onChange} />,
);
const InputComponent = getByTestId('input');
await user.type(InputComponent, '123');
expect(InputComponent).toHaveValue('123');
expect(onChange).toHaveBeenCalledTimes(3);
});
it('should render with different types', () => {
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" />
</>,
);
expect(getByTestId('input-text-default')).toHaveAttribute('type', 'text');
expect(getByTestId('input-text')).toHaveAttribute('type', 'text');
expect(getByTestId('input-number')).toHaveAttribute('type', 'number');
expect(getByTestId('input-password')).toHaveAttribute('type', 'password');
});
it('should render with autoComplete', () => {
const { getByTestId } = render(
<Input autoComplete data-testid="input-auto-complete" />,
);
expect(getByTestId('input-auto-complete')).toHaveAttribute(
'autocomplete',
'on',
);
});
it('should render with autoFocus', () => {
const { getByRole } = render(<Input autoFocus />);
expect(getByRole('textbox')).toHaveFocus();
});
it('should render with a defaultValue', () => {
const { getByRole } = render(<Input defaultValue="default value" />);
expect(getByRole('textbox').value).toBe('default value');
});
it('should render in disabled state and not focus or be clickable', async () => {
const mockOnFocus = jest.fn();
const { getByRole, getByTestId, user } = renderWithUserEvent(
<Input disabled onFocus={mockOnFocus} data-testid="input" />,
);
const InputComponent = getByTestId('input');
await user.click(InputComponent);
expect(getByRole('textbox')).toBeDisabled();
expect(mockOnFocus).toHaveBeenCalledTimes(0);
});
it('should render with maxLength and not allow more than the set characters', async () => {
const { getByRole, user } = renderWithUserEvent(<Input maxLength={5} />);
const InputComponent = getByRole('textbox');
await user.type(InputComponent, '1234567890');
expect(getByRole('textbox')).toBeDefined();
expect(InputComponent.maxLength).toBe(5);
expect(InputComponent.value).toBe('12345');
expect(InputComponent.value).toHaveLength(5);
});
it('should render with readOnly attr when readOnly is true', async () => {
const { getByTestId, getByRole, user } = renderWithUserEvent(
<Input readOnly data-testid="read-only" />,
);
const InputComponent = getByTestId('read-only');
await user.type(InputComponent, '1234567890');
expect(getByRole('textbox').value).toBe('');
expect(getByRole('textbox')).toHaveAttribute('readonly', '');
});
it('should render with required attr when required is true', () => {
const { getByTestId } = render(
<Input required data-testid="input-required" />,
);
expect(getByTestId('input-required')).toHaveAttribute('required', '');
});
it('should render with a different Text variant', () => {
const { getByTestId } = render(
<Input
data-testid="input-required"
textVariant={TextVariant.headingSm}
/>,
);
expect(getByTestId('input-required')).toHaveClass('mm-text--heading-sm');
});
});

View File

@ -1,338 +0,0 @@
import { Story, Canvas, ArgsTable } from '@storybook/addon-docs';
import { TextFieldBase } from './text-field-base';
### This is a base component. It should not be used in your feature code directly but as a "base" for other UI components
# TextFieldBase
The `TextFieldBase` is the base component for all text fields. It should not be used directly. It functions as both a uncontrolled and controlled input.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--default-story" />
</Canvas>
## Props
The `TextFieldBase` accepts all props below as well as all [Box](/docs/components-ui-box--default-story#props) component props
<ArgsTable of={TextFieldBase} />
### Size
Use the `size` prop to set the height of the `TextFieldBase`.
Possible sizes include:
- `sm` 32px
- `md` 40px
- `lg` 48px
Defaults to `md`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--size-story" />
</Canvas>
```jsx
import { Size } from '../../../helpers/constants/design-system';
import { TextFieldBase } from '../../component-library';
<TextFieldBase size={Size.SM} />
<TextFieldBase size={Size.MD} />
<TextFieldBase size={Size.LG} />
```
### Type
Use the `type` prop to change the type of input.
Possible types include:
- `text`
- `number`
- `password`
Defaults to `text`.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--type" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase type="text" /> // (Default)
<TextFieldBase type="number" />
<TextFieldBase type="password" />
```
### Truncate
Use the `truncate` prop to truncate the text of the the `TextFieldBase`. Defaults to `true`.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--truncate" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase truncate />; // truncate is set to `true` by default
<TextFieldBase truncate={false} />;
```
### Left Accessory Right Accessory
Use the `leftAccessory` and `rightAccessory` props to add components such as icons or buttons to either side of the `TextFieldBase`.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--left-accessory-right-accessory" />
</Canvas>
```jsx
import { Color, DISPLAY } from '../../../helpers/constants/design-system';
import { ButtonIcon, Icon, ICON_NAMES, TextFieldBase } from '../../component-library';
<TextFieldBase
placeholder="Search"
leftAccessory={
<Icon
color={Color.iconAlternative}
name={ICON_NAMES.SEARCH}
/>
}
/>
<TextFieldBase
placeholder="Public address (0x), or ENS"
rightAccessory={
<ButtonIcon
iconName={ICON_NAMES.SCAN_BARCODE}
ariaLabel="Scan QR code"
iconProps={{ color: Color.primaryDefault }}
/>
}
/>
<TextFieldBase
placeholder="Enter amount"
type="number"
truncate
leftAccessory={<SelectTokenComponent />}
rightAccessory={<TokenValueInUSDComponent />}
/>
<TextFieldBase
placeholder="Public address (0x), or ENS"
truncate
leftAccessory={<AvatarAccount />}
rightAccessory={
isAddressValid && (
<Icon
name={ICON_NAMES.CHECK}
color={Color.successDefault}
/>
)
}
/>
```
### Input Ref
Use the `inputRef` prop to access the ref of the `<input />` html element of `TextFieldBase`. This is useful for focusing the input from a button or other component.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--input-ref" />
</Canvas>
```jsx
import { Button, TextFieldBase } from '../../component-library';
const inputRef = useRef(null);
const [value, setValue] = useState('');
const handleOnClick = () => {
inputRef.current.focus();
};
const handleOnChange = (e) => {
setValue(e.target.value);
};
<TextFieldBase
inputRef={inputRef}
value={value}
onChange={handleOnChange}
/>
<Button marginLeft={1} onClick={handleOnClick}>
Edit
</Button>
```
### Input Component
Use the `InputComponent` prop change the component used for the input element. This is useful for replacing the base input with a custom input while retaining the functionality of the `TextFieldBase`.
Defaults to the [Text](/docs/components-componentlibrary-text--default-story) component
To function fully the custom component should accept the following props:
- `aria-invalid`
- `as`
- `autoComplete`
- `autoFocus`
- `backgroundColor`
- `defaultValue`
- `disabled`
- `focused`
- `id`
- `margin`
- `maxLength`
- `name`
- `onBlur`
- `onChange`
- `onFocus`
- `padding`
- `paddingLeft`
- `paddingRight`
- `placeholder`
- `readOnly`
- `ref`
- `required`
- `value`
- `variant`
- `type`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--input-component" />
</Canvas>
```jsx
import { TextFieldBase, Icon, ICON_NAMES } from '../../component-library';
import { Size } from '../../../helpers/constants/design-system';
// should map the props to the custom input component
const CustomInputComponent = () => <div>{/* Custom input component */}</div>;
const TextFieldCustomInput = (args) => (
<TextFieldBase
size={Size.LG}
InputComponent={CustomInputComponent}
leftAccessory={
<Icon color={Color.iconAlternative} name={ICON_NAMES.WALLET} />
}
/>
);
```
### Auto Complete
Use the `autoComplete` prop to set the autocomplete html attribute. It allows the browser to predict the value based on earlier typed values.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--auto-complete" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase type="password" autoComplete />;
```
### Auto Focus
Use the `autoFocus` prop to focus the `TextFieldBase` during the first mount
<Canvas>
<Story id="components-componentlibrary-textfieldbase--auto-focus" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase autoFocus />;
```
### Default Value
Use the `defaultValue` prop to set the default value of the `TextFieldBase`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--default-value" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase defaultValue="default value" />;
```
### Disabled
Use the `disabled` prop to set the disabled state of the `TextFieldBase`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--disabled" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase disabled />;
```
### Error
Use the `error` prop to set the error state of the `TextFieldBase`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--error-story" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase error />;
```
### Max Length
Use the `maxLength` prop to set the maximum allowed input characters for the `TextFieldBase`
<Canvas>
<Story id="components-componentlibrary-textfieldbase--max-length" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase maxLength={10} />;
```
### Read Only
Use the `readOnly` prop to set the `TextFieldBase` to read only. When `readOnly` is true `TextFieldBase` will not have a focus state.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--read-only" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
<TextFieldBase readOnly />;
```
### Required
Use the `required` prop to set the `TextFieldBase` to required. Currently there is no visual difference to the `TextFieldBase` when required.
<Canvas>
<Story id="components-componentlibrary-textfieldbase--required" />
</Canvas>
```jsx
import { TextFieldBase } from '../../component-library';
// Currently no visual difference
<TextFieldBase required />;
```

View File

@ -11,7 +11,7 @@ exports[`TextFieldSearch should render correctly 1`] = `
/>
<input
autocomplete="off"
class="box mm-text mm-text-field__input mm-text--body-md mm-text--color-text-default box--margin-right-6 box--padding-right-4 box--padding-left-2 box--flex-direction-row box--background-color-transparent"
class="box mm-text mm-input mm-text-field__input mm-text--body-md mm-text--color-text-default box--margin-right-6 box--padding-right-4 box--padding-left-2 box--flex-direction-row box--background-color-transparent box--border-style-none"
focused="false"
type="search"
value=""

View File

@ -7,7 +7,7 @@ exports[`TextField should render correctly 1`] = `
>
<input
autocomplete="off"
class="box mm-text mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent"
class="box mm-text mm-input mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent box--border-style-none"
focused="false"
type="text"
value=""

View File

@ -6,14 +6,13 @@ import {
DISPLAY,
Size,
AlignItems,
TextVariant,
BorderRadius,
BackgroundColor,
} from '../../../helpers/constants/design-system';
import Box from '../../ui/box';
import { Text } from '../text';
import { Input } from '../input';
import { TEXT_FIELD_SIZES, TEXT_FIELD_TYPES } from './text-field.constants';
@ -42,7 +41,7 @@ export const TextField = ({
type = 'text',
truncate = true,
value,
InputComponent = Text,
InputComponent = Input,
...props
}) => {
const internalInputRef = useRef(null);
@ -114,8 +113,7 @@ export const TextField = ({
{startAccessory}
<InputComponent
aria-invalid={error}
as="input"
autoComplete={autoComplete ? 'on' : 'off'}
autoComplete={autoComplete}
autoFocus={autoFocus}
backgroundColor={BackgroundColor.transparent}
defaultValue={defaultValue}
@ -136,7 +134,6 @@ export const TextField = ({
ref={handleInputRef}
required={required}
value={value}
variant={TextVariant.bodyMd}
type={type}
{...inputProps} // before className so input className isn't overridden
className={classnames('mm-text-field__input', inputProps?.className)}

View File

@ -38,17 +38,7 @@
}
&__input {
border: none;
height: 100%;
width: 100%;
flex-grow: 1;
box-sizing: content-box;
margin: 0;
padding: 0;
&:focus,
&:focus-visible {
outline: none;
}
}
}

View File

@ -75,7 +75,7 @@ export const Text = React.forwardRef(
},
);
// // Set a default tag based on variant
// Set a default tag based on variant
const splitTag = Tag.split('-')[0];
if (splitTag === 'body') {
Tag = 'p';

View File

@ -98,7 +98,7 @@ exports[`Reveal Seed Page should match snapshot 1`] = `
>
<input
autocomplete="off"
class="box mm-text mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent"
class="box mm-text mm-input mm-text-field__input mm-text--body-md mm-text--color-text-default box--padding-right-4 box--padding-left-4 box--flex-direction-row box--background-color-transparent box--border-style-none"
data-testid="input-password"
focused="true"
id="password-box"