diff --git a/ui/components/component-library/index.js b/ui/components/component-library/index.js index f7d25c3ef2bf..9df42ebec4fc 100644 --- a/ui/components/component-library/index.js +++ b/ui/components/component-library/index.js @@ -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'; diff --git a/ui/components/component-library/input/README.mdx b/ui/components/component-library/input/README.mdx index 308245a0a78a..a45c1244bcf5 100644 --- a/ui/components/component-library/input/README.mdx +++ b/ui/components/component-library/input/README.mdx @@ -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 @@ -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`. ```jsx -import { Input, INPUT_TYPES } from '../../component-library'; +import { Input, InputType } from '../../component-library'; - - - - + + + + ``` ### Ref @@ -81,9 +81,9 @@ Use the `autoComplete` prop to set the autocomplete html attribute. It allows th ```jsx -import { Input } from '../../component-library'; +import { Input, InputType } from '../../component-library'; -; +; ``` ### Auto Focus diff --git a/ui/components/component-library/input/__snapshots__/input.test.js.snap b/ui/components/component-library/input/__snapshots__/input.test.tsx.snap similarity index 100% rename from ui/components/component-library/input/__snapshots__/input.test.js.snap rename to ui/components/component-library/input/__snapshots__/input.test.tsx.snap diff --git a/ui/components/component-library/input/index.js b/ui/components/component-library/input/index.js deleted file mode 100644 index a8a921a476fb..000000000000 --- a/ui/components/component-library/input/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { Input } from './input'; -export { INPUT_TYPES } from './input.constants'; diff --git a/ui/components/component-library/input/index.ts b/ui/components/component-library/input/index.ts new file mode 100644 index 000000000000..1d3826b99c15 --- /dev/null +++ b/ui/components/component-library/input/index.ts @@ -0,0 +1,4 @@ +export { Input } from './input'; +export { InputType } from './input.types'; + +export type { InputProps } from './input.types'; diff --git a/ui/components/component-library/input/input.constants.js b/ui/components/component-library/input/input.constants.js deleted file mode 100644 index 3d0ee82f0f54..000000000000 --- a/ui/components/component-library/input/input.constants.js +++ /dev/null @@ -1,6 +0,0 @@ -export const INPUT_TYPES = { - TEXT: 'text', - NUMBER: 'number', - PASSWORD: 'password', - SEARCH: 'search', -}; diff --git a/ui/components/component-library/input/input.js b/ui/components/component-library/input/input.js deleted file mode 100644 index f4c7510bd803..000000000000 --- a/ui/components/component-library/input/input.js +++ /dev/null @@ -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, - ) => ( - - ), -); - -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'; diff --git a/ui/components/component-library/input/input.stories.js b/ui/components/component-library/input/input.stories.tsx similarity index 84% rename from ui/components/component-library/input/input.stories.js rename to ui/components/component-library/input/input.stories.tsx index 6cc5a3cf6ab4..676687350561 100644 --- a/ui/components/component-library/input/input.stories.js +++ b/ui/components/component-library/input/input.stories.tsx @@ -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; const Template = (args) => { const [{ value }, updateArgs] = useArgs(); @@ -141,14 +141,14 @@ DefaultStory.storyName = 'Default'; export const Type = (args) => ( - - - + + + ); @@ -158,17 +158,21 @@ Type.args = { export const Ref = (args) => { const [{ value }, updateArgs] = useArgs(); - const inputRef = useRef(null); + const inputRef = useRef(null); const handleOnClick = () => { - inputRef.current.focus(); + inputRef.current?.focus(); }; const handleOnChange = (e) => { updateArgs({ value: e.target.value }); }; return ( - + - @@ -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 ( { it('should render correctly', () => { @@ -72,9 +72,9 @@ describe('Input', () => { const { getByTestId } = render( <> - - - + + + , ); expect(getByTestId('input-text-default')).toHaveAttribute('type', 'text'); diff --git a/ui/components/component-library/input/input.tsx b/ui/components/component-library/input/input.tsx new file mode 100644 index 000000000000..5ff59c7a696b --- /dev/null +++ b/ui/components/component-library/input/input.tsx @@ -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( + ( + { + 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, + ref: PolymorphicRef, + ) => ( + )} + /> + ), +); diff --git a/ui/components/component-library/input/input.types.ts b/ui/components/component-library/input/input.types.ts new file mode 100644 index 000000000000..6d0c5fe26d40 --- /dev/null +++ b/ui/components/component-library/input/input.types.ts @@ -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 = + PolymorphicComponentPropWithRef; + +export type InputComponent = ( + props: InputProps, +) => React.ReactElement | null;