Shared:Components:TextInput: rewrite to typescript
This commit is contained in:
parent
d36d822fb2
commit
1e9cb7e35a
@ -36,6 +36,7 @@
|
||||
"e2e.test:translation": "yarn workspaces foreach -vptiR --from '{@docspace/client,@docspace/login}' run test:translation:model"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react-text-mask": "^5.4.14",
|
||||
"he": "^1.2.0",
|
||||
"madge": "^6.1.0",
|
||||
"shx": "^0.3.4",
|
||||
|
@ -1,2 +0,0 @@
|
||||
// @ts-expect-error TS(2304): Cannot find name 'from'.
|
||||
export default from "./text-input";
|
@ -1,48 +0,0 @@
|
||||
import React from "react";
|
||||
// @ts-expect-error TS(7016): Could not find a declaration file for module 'reac... Remove this comment to see the full error message
|
||||
import MaskedInput from "react-text-mask";
|
||||
|
||||
/* eslint-disable no-unused-vars, react/prop-types */
|
||||
const Input = ({
|
||||
isAutoFocussed,
|
||||
isDisabled,
|
||||
isReadOnly,
|
||||
hasError,
|
||||
hasWarning,
|
||||
scale,
|
||||
withBorder,
|
||||
keepCharPositions,
|
||||
guide,
|
||||
fontWeight,
|
||||
isBold,
|
||||
forwardedRef,
|
||||
className,
|
||||
theme,
|
||||
dir = "auto",
|
||||
...props
|
||||
}: any) => {
|
||||
const rest = {};
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'autoFocus' does not exist on type '{}'.
|
||||
if (isAutoFocussed) rest.autoFocus = true;
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'ref' does not exist on type '{}'.
|
||||
if (forwardedRef) rest.ref = forwardedRef;
|
||||
|
||||
return props.mask != null ? (
|
||||
<MaskedInput
|
||||
className={`${className} not-selectable`}
|
||||
keepCharPositions={true}
|
||||
guide={false}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
className={`${className} not-selectable`}
|
||||
dir={dir}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
export default Input;
|
@ -1,138 +0,0 @@
|
||||
import styled, { css } from "styled-components";
|
||||
import commonInputStyles from "./common-input-styles";
|
||||
import Input from "./input";
|
||||
import Base from "../themes/base";
|
||||
|
||||
import NoUserSelect from "../utils/commonStyles";
|
||||
/* eslint-enable react/prop-types, no-unused-vars */
|
||||
const StyledTextInput = styled(Input).attrs((props) => ({
|
||||
id: props.id,
|
||||
forwardedRef: props.forwardedRef,
|
||||
name: props.name,
|
||||
type: props.type,
|
||||
value: props.value,
|
||||
placeholder: props.placeholder,
|
||||
maxLength: props.maxLength,
|
||||
onChange: props.onChange,
|
||||
onBlur: props.onBlur,
|
||||
onFocus: props.onFocus,
|
||||
readOnly: props.isReadOnly,
|
||||
autoFocus: props.isAutoFocussed,
|
||||
autoComplete: props.autoComplete,
|
||||
tabIndex: props.tabIndex,
|
||||
disabled: props.isDisabled ? "disabled" : "",
|
||||
}))`
|
||||
${commonInputStyles}
|
||||
-webkit-appearance: ${(props) => props.theme.textInput.appearance};
|
||||
|
||||
background-color:
|
||||
${(props) => props.isDisabled ?
|
||||
props.theme.input.disableBackgroundColor
|
||||
: props.theme.input.backgroundColor};
|
||||
-webkit-text-fill-color: ${(props) => props.isDisabled ? props.theme.input.disableColor :
|
||||
props?.value?.length > 0
|
||||
? props.theme.text.color
|
||||
: props.theme.textInput.placeholderColor} !important;
|
||||
caret-color: ${(props) => props.isDisabled ? props.theme.input.disableColor : props.theme.text.color};
|
||||
-webkit-background-clip: text !important;
|
||||
box-shadow: inset 0 0 20px 20px
|
||||
${(props) => props.isDisabled ?
|
||||
props.theme.input.disableBackgroundColor
|
||||
: props.theme.input.backgroundColor} !important;
|
||||
|
||||
display: ${(props) => props.theme.textInput.display};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
line-height: ${(props) =>
|
||||
(props.size === "base" && props.theme.textInput.lineHeight.base) ||
|
||||
(props.size === "middle" && props.theme.textInput.lineHeight.middle) ||
|
||||
(props.size === "big" && props.theme.textInput.lineHeight.big) ||
|
||||
(props.size === "huge" && props.theme.textInput.lineHeight.huge) ||
|
||||
(props.size === "large" && props.theme.textInput.lineHeight.large)};
|
||||
font-size: ${(props) =>
|
||||
props.theme.getCorrectFontSize(
|
||||
(props.size === "base" && props.theme.textInput.fontSize.base) ||
|
||||
(props.size === "middle" && props.theme.textInput.fontSize.middle) ||
|
||||
(props.size === "big" && props.theme.textInput.fontSize.big) ||
|
||||
(props.size === "huge" && props.theme.textInput.fontSize.huge) ||
|
||||
(props.size === "large" && props.theme.textInput.fontSize.large)
|
||||
)};
|
||||
|
||||
font-weight: ${(props) =>
|
||||
props.fontWeight
|
||||
? props.fontWeight
|
||||
: props.isBold
|
||||
? 600
|
||||
: props.theme.textInput.fontWeight};
|
||||
|
||||
flex: ${(props) => props.theme.textInput.flex};
|
||||
outline: ${(props) => props.theme.textInput.outline};
|
||||
overflow: ${(props) => props.theme.textInput.overflow};
|
||||
opacity: ${(props) => props.theme.textInput.opacity};
|
||||
|
||||
width: ${(props) =>
|
||||
(props.scale && "100%") ||
|
||||
(props.size === "base" && props.theme.input.width.base) ||
|
||||
(props.size === "middle" && props.theme.input.width.middle) ||
|
||||
(props.size === "big" && props.theme.input.width.big) ||
|
||||
(props.size === "huge" && props.theme.input.width.huge) ||
|
||||
(props.size === "large" && props.theme.input.width.large)};
|
||||
padding: ${(props) =>
|
||||
(props.size === "base" && props.theme.textInput.padding.base) ||
|
||||
(props.size === "middle" && props.theme.textInput.padding.middle) ||
|
||||
(props.size === "big" && props.theme.textInput.padding.big) ||
|
||||
(props.size === "huge" && props.theme.textInput.padding.huge) ||
|
||||
(props.size === "large" && props.theme.textInput.padding.large)};
|
||||
|
||||
transition: ${(props) => props.theme.textInput.transition};
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
${(props) => !props.withBorder && `border: none;`}
|
||||
`;
|
||||
|
||||
StyledTextInput.defaultProps = { theme: Base };
|
||||
|
||||
export default StyledTextInput;
|
@ -1,64 +0,0 @@
|
||||
import React, { useState } from "react";
|
||||
import TextInput from "./";
|
||||
|
||||
export default {
|
||||
title: "Components/TextInput",
|
||||
component: TextInput,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: "Input field for single-line strings",
|
||||
},
|
||||
},
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/file/ZiW5KSwb4t7Tj6Nz5TducC/UI-Kit-DocSpace-1.0.0?type=design&node-id=633-3686&mode=dev",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
onBlur: { action: "onBlur" },
|
||||
onFocus: { action: "onFocus" },
|
||||
onChange: { action: "onChange" },
|
||||
scale: { description: "Indicates that the input field has scaled" },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = ({
|
||||
onChange,
|
||||
value,
|
||||
...args
|
||||
}: any) => {
|
||||
const [val, setValue] = useState(value);
|
||||
|
||||
return (
|
||||
<TextInput
|
||||
{...args}
|
||||
value={val}
|
||||
onChange={(e: any) => {
|
||||
setValue(e.target.value);
|
||||
onChange(e.target.value);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default = Template.bind({});
|
||||
// @ts-expect-error TS(2339): Property 'args' does not exist on type '({ onChang... Remove this comment to see the full error message
|
||||
Default.args = {
|
||||
id: "",
|
||||
name: "",
|
||||
placeholder: "This is placeholder",
|
||||
maxLength: 255,
|
||||
size: "base",
|
||||
isAutoFocussed: false,
|
||||
isDisabled: false,
|
||||
isReadOnly: false,
|
||||
hasError: false,
|
||||
hasWarning: false,
|
||||
scale: false,
|
||||
autoComplete: "off",
|
||||
tabIndex: 1,
|
||||
withBorder: true,
|
||||
mask: null,
|
||||
value: "",
|
||||
};
|
@ -1,82 +0,0 @@
|
||||
import React from "react";
|
||||
// @ts-expect-error TS(7016): Could not find a declaration file for module 'enzy... Remove this comment to see the full error message
|
||||
import { mount, shallow } from "enzyme";
|
||||
import TextInput from ".";
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'describe'. Do you need to instal... Remove this comment to see the full error message
|
||||
describe("<TextInput />", () => {
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("renders without error", () => {
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
const wrapper = mount(<TextInput value="text" onChange={jest.fn()} />);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper).toExist();
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("not re-render test", () => {
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<TextInput value="text" onChange={onChange} />
|
||||
).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(shouldUpdate).toBe(false);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("re-render test by value", () => {
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
const onChange = jest.fn();
|
||||
|
||||
const wrapper = shallow(
|
||||
<TextInput value="text" onChange={onChange} />
|
||||
).instance();
|
||||
|
||||
const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
...wrapper.props,
|
||||
value: "another text",
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(shouldUpdate).toBe(true);
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts id", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
<TextInput value="text" onChange={jest.fn()} id="testId" />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.prop("id")).toEqual("testId");
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts className", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
<TextInput value="text" onChange={jest.fn()} className="test" />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.prop("className")).toEqual("test");
|
||||
});
|
||||
|
||||
// @ts-expect-error TS(2582): Cannot find name 'it'. Do you need to install type... Remove this comment to see the full error message
|
||||
it("accepts style", () => {
|
||||
const wrapper = mount(
|
||||
// @ts-expect-error TS(2708): Cannot use namespace 'jest' as a value.
|
||||
<TextInput value="text" onChange={jest.fn()} style={{ color: "red" }} />
|
||||
);
|
||||
|
||||
// @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
|
||||
});
|
||||
});
|
@ -1,95 +0,0 @@
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import equal from "fast-deep-equal/react";
|
||||
import StyledTextInput from "./styled-text-input";
|
||||
|
||||
class TextInput extends React.Component {
|
||||
shouldComponentUpdate(nextProps: any) {
|
||||
return !equal(this.props, nextProps);
|
||||
}
|
||||
|
||||
render() {
|
||||
// console.log(`TextInput render id=${this.props.id}`);
|
||||
|
||||
return <StyledTextInput {...this.props} />;
|
||||
}
|
||||
}
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'propTypes' does not exist on type 'typeo... Remove this comment to see the full error message
|
||||
TextInput.propTypes = {
|
||||
/** Used as HTML `id` property */
|
||||
id: PropTypes.string,
|
||||
/** Forwarded ref */
|
||||
forwardedRef: PropTypes.object,
|
||||
/** Used as HTML `name` property */
|
||||
name: PropTypes.string,
|
||||
/** Supported type of the input fields. */
|
||||
type: PropTypes.oneOf(["text", "password", "email", "tel", "search"]),
|
||||
/** Value of the input */
|
||||
value: PropTypes.string.isRequired,
|
||||
/** Default maxLength value of the input */
|
||||
maxLength: PropTypes.number,
|
||||
/** Placeholder text for the input */
|
||||
placeholder: PropTypes.string,
|
||||
/** Used as HTML `tabindex` property */
|
||||
tabIndex: PropTypes.number,
|
||||
/** input text mask */
|
||||
mask: PropTypes.oneOfType([PropTypes.array, PropTypes.func]),
|
||||
/** Allows to add or delete characters without changing the positions of the existing characters.*/
|
||||
keepCharPositions: PropTypes.bool,
|
||||
/** When guide is true, Text Mask always shows both placeholder characters and non-placeholder mask characters. */
|
||||
guide: PropTypes.bool,
|
||||
/** Supported size of the input fields. */
|
||||
size: PropTypes.oneOf(["base", "middle", "big", "huge", "large"]),
|
||||
/** Indicates the input field has scale */
|
||||
scale: PropTypes.bool,
|
||||
/** Called with the new value. Required when input is not read only. Parent should pass it back as `value` */
|
||||
onChange: PropTypes.func,
|
||||
/** Called when field is blurred */
|
||||
onBlur: PropTypes.func,
|
||||
/** Called when field is focused */
|
||||
onFocus: PropTypes.func,
|
||||
/** Focus the input field on initial render */
|
||||
isAutoFocussed: PropTypes.bool,
|
||||
/** Indicates that the field cannot be used (e.g not authorised, or changes not saved) */
|
||||
isDisabled: PropTypes.bool,
|
||||
/** Indicates that the field is displaying read-only content */
|
||||
isReadOnly: PropTypes.bool,
|
||||
/** Indicates the input field has an error */
|
||||
hasError: PropTypes.bool,
|
||||
/** Indicates the input field has a warning */
|
||||
hasWarning: PropTypes.bool,
|
||||
/** Used as HTML `autocomplete` property */
|
||||
autoComplete: PropTypes.string,
|
||||
/** Accepts class */
|
||||
className: PropTypes.string,
|
||||
/** Accepts css style */
|
||||
style: PropTypes.oneOfType([PropTypes.object, PropTypes.array]),
|
||||
/** Sets the font weight */
|
||||
fontWeight: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
/** Sets font weight value to 600 */
|
||||
isBold: PropTypes.bool,
|
||||
/** Indicates that component contain border */
|
||||
withBorder: PropTypes.bool,
|
||||
};
|
||||
|
||||
// @ts-expect-error TS(2339): Property 'defaultProps' does not exist on type 'ty... Remove this comment to see the full error message
|
||||
TextInput.defaultProps = {
|
||||
type: "text",
|
||||
// Empty placeholder by default needed for RTL mode to make :placeholder-shown work to put cursor on the right side of input
|
||||
placeholder: " ",
|
||||
value: "",
|
||||
maxLength: 255,
|
||||
size: "base",
|
||||
scale: false,
|
||||
tabIndex: -1,
|
||||
hasError: false,
|
||||
hasWarning: false,
|
||||
autoComplete: "off",
|
||||
withBorder: true,
|
||||
keepCharPositions: false,
|
||||
guide: false,
|
||||
isBold: false,
|
||||
};
|
||||
|
||||
export default TextInput;
|
@ -29,8 +29,12 @@ import {
|
||||
import { Checkbox } from "./checkbox";
|
||||
import { Toast, toastr, ToastType } from "./toast";
|
||||
import { Textarea } from "./textarea";
|
||||
import { TextInput, InputSize, InputType } from "./text-input";
|
||||
|
||||
export {
|
||||
TextInput,
|
||||
InputSize,
|
||||
InputType,
|
||||
Textarea,
|
||||
Toast,
|
||||
toastr,
|
||||
|
@ -5,7 +5,7 @@ Input field for single-line strings
|
||||
### Usage
|
||||
|
||||
```js
|
||||
import TextInput from "@docspace/components/text-input";
|
||||
import { TextInput } from "@docspace/shared/components";
|
||||
```
|
||||
|
||||
```js
|
15
packages/shared/components/text-input/TextInput.enums.ts
Normal file
15
packages/shared/components/text-input/TextInput.enums.ts
Normal file
@ -0,0 +1,15 @@
|
||||
export const enum InputType {
|
||||
text = "text",
|
||||
password = "password",
|
||||
email = "email",
|
||||
tel = "tel",
|
||||
search = "search",
|
||||
}
|
||||
|
||||
export const enum InputSize {
|
||||
base = "base",
|
||||
middle = "middle",
|
||||
big = "big",
|
||||
huge = "huge",
|
||||
large = "large",
|
||||
}
|
69
packages/shared/components/text-input/TextInput.stories.tsx
Normal file
69
packages/shared/components/text-input/TextInput.stories.tsx
Normal file
@ -0,0 +1,69 @@
|
||||
import React, { useState } from "react";
|
||||
import { Meta, StoryObj } from "@storybook/react";
|
||||
|
||||
import { TextInputPure } from "./TextInput";
|
||||
import { TextInputProps } from "./TextInput.types";
|
||||
import { InputSize, InputType } from "./TextInput.enums";
|
||||
|
||||
const meta = {
|
||||
title: "Components/TextInput",
|
||||
component: TextInputPure,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: "Input field for single-line strings",
|
||||
},
|
||||
},
|
||||
design: {
|
||||
type: "figma",
|
||||
url: "https://www.figma.com/file/ZiW5KSwb4t7Tj6Nz5TducC/UI-Kit-DocSpace-1.0.0?type=design&node-id=633-3686&mode=dev",
|
||||
},
|
||||
},
|
||||
argTypes: {
|
||||
onBlur: { action: "onBlur" },
|
||||
onFocus: { action: "onFocus" },
|
||||
onChange: { action: "onChange" },
|
||||
scale: { description: "Indicates that the input field has scaled" },
|
||||
},
|
||||
} satisfies Meta<typeof TextInputPure>;
|
||||
type Story = StoryObj<typeof TextInputPure>;
|
||||
|
||||
export default meta;
|
||||
|
||||
const Template = ({ onChange, value, ...args }: TextInputProps) => {
|
||||
const [val, setValue] = useState(value);
|
||||
|
||||
return (
|
||||
<TextInputPure
|
||||
{...args}
|
||||
value={val}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(e.target.value);
|
||||
onChange?.(e);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const Default: Story = {
|
||||
render: (args) => <Template {...args} />,
|
||||
args: {
|
||||
id: "",
|
||||
name: "",
|
||||
placeholder: "This is placeholder",
|
||||
maxLength: 255,
|
||||
size: InputSize.base,
|
||||
type: InputType.text,
|
||||
isAutoFocussed: false,
|
||||
isDisabled: false,
|
||||
isReadOnly: false,
|
||||
hasError: false,
|
||||
hasWarning: false,
|
||||
scale: false,
|
||||
autoComplete: "off",
|
||||
tabIndex: 1,
|
||||
withBorder: true,
|
||||
mask: undefined,
|
||||
value: "",
|
||||
},
|
||||
};
|
149
packages/shared/components/text-input/TextInput.styled.ts
Normal file
149
packages/shared/components/text-input/TextInput.styled.ts
Normal file
@ -0,0 +1,149 @@
|
||||
import styled from "styled-components";
|
||||
|
||||
import { commonInputStyle, NoUserSelect } from "../../constants";
|
||||
import { Base } from "../../themes";
|
||||
|
||||
import { InputSize } from "./TextInput.enums";
|
||||
|
||||
import { Input } from "./sub-components/Input";
|
||||
|
||||
const StyledTextInput = styled(Input).attrs((props) => ({
|
||||
id: props.id,
|
||||
forwardedRef: props.forwardedRef,
|
||||
name: props.name,
|
||||
type: props.type,
|
||||
value: props.value,
|
||||
placeholder: props.placeholder,
|
||||
maxLength: props.maxLength,
|
||||
onChange: props.onChange,
|
||||
onBlur: props.onBlur,
|
||||
onFocus: props.onFocus,
|
||||
readOnly: props.isReadOnly,
|
||||
autoFocus: props.isAutoFocussed,
|
||||
autoComplete: props.autoComplete,
|
||||
tabIndex: props.tabIndex,
|
||||
disabled: props.isDisabled ? "disabled" : "",
|
||||
}))`
|
||||
${commonInputStyle}
|
||||
-webkit-appearance: ${(props) => props.theme.textInput.appearance};
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.input.disableBackgroundColor
|
||||
: props.theme.input.backgroundColor};
|
||||
-webkit-text-fill-color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.input.disableColor
|
||||
: props?.value?.length > 0
|
||||
? props.theme.text.color
|
||||
: props.theme.textInput.placeholderColor} !important;
|
||||
caret-color: ${(props) =>
|
||||
props.isDisabled ? props.theme.input.disableColor : props.theme.text.color};
|
||||
-webkit-background-clip: text !important;
|
||||
box-shadow: inset 0 0 20px 20px
|
||||
${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.input.disableBackgroundColor
|
||||
: props.theme.input.backgroundColor} !important;
|
||||
|
||||
display: ${(props) => props.theme.textInput.display};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
line-height: ${(props) =>
|
||||
(props.size === InputSize.base && props.theme.textInput.lineHeight.base) ||
|
||||
(props.size === InputSize.middle &&
|
||||
props.theme.textInput.lineHeight.middle) ||
|
||||
(props.size === InputSize.big && props.theme.textInput.lineHeight.big) ||
|
||||
(props.size === InputSize.huge && props.theme.textInput.lineHeight.huge) ||
|
||||
(props.size === InputSize.large && props.theme.textInput.lineHeight.large)};
|
||||
font-size: ${(props) =>
|
||||
props.theme.getCorrectFontSize(
|
||||
(props.size === InputSize.base && props.theme.textInput.fontSize.base) ||
|
||||
(props.size === InputSize.middle &&
|
||||
props.theme.textInput.fontSize.middle) ||
|
||||
(props.size === InputSize.big && props.theme.textInput.fontSize.big) ||
|
||||
(props.size === InputSize.huge &&
|
||||
props.theme.textInput.fontSize.huge) ||
|
||||
(props.size === InputSize.large &&
|
||||
props.theme.textInput.fontSize.large) ||
|
||||
props.theme.textInput.fontSize.base,
|
||||
)};
|
||||
|
||||
font-weight: ${(props) =>
|
||||
props.fontWeight
|
||||
? props.fontWeight
|
||||
: props.isBold
|
||||
? 600
|
||||
: props.theme.textInput.fontWeight};
|
||||
|
||||
flex: ${(props) => props.theme.textInput.flex};
|
||||
outline: ${(props) => props.theme.textInput.outline};
|
||||
overflow: ${(props) => props.theme.textInput.overflow};
|
||||
opacity: ${(props) => props.theme.textInput.opacity};
|
||||
|
||||
width: ${(props) =>
|
||||
(props.scale && "100%") ||
|
||||
(props.size === InputSize.base && props.theme.input.width.base) ||
|
||||
(props.size === InputSize.middle && props.theme.input.width.middle) ||
|
||||
(props.size === InputSize.big && props.theme.input.width.big) ||
|
||||
(props.size === InputSize.huge && props.theme.input.width.huge) ||
|
||||
(props.size === InputSize.large && props.theme.input.width.large)};
|
||||
padding: ${(props) =>
|
||||
(props.size === InputSize.base && props.theme.textInput.padding.base) ||
|
||||
(props.size === InputSize.middle && props.theme.textInput.padding.middle) ||
|
||||
(props.size === InputSize.big && props.theme.textInput.padding.big) ||
|
||||
(props.size === InputSize.huge && props.theme.textInput.padding.huge) ||
|
||||
(props.size === InputSize.large && props.theme.textInput.padding.large)};
|
||||
|
||||
transition: ${(props) => props.theme.textInput.transition};
|
||||
|
||||
::-webkit-input-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
:-moz-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
::-moz-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
:-ms-input-placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: ${(props) =>
|
||||
props.isDisabled
|
||||
? props.theme.textInput.disablePlaceholderColor
|
||||
: props.theme.textInput.placeholderColor};
|
||||
font-family: ${(props) => props.theme.fontFamily};
|
||||
${NoUserSelect}
|
||||
}
|
||||
|
||||
${(props) => !props.withBorder && `border: none;`}
|
||||
`;
|
||||
|
||||
StyledTextInput.defaultProps = { theme: Base };
|
||||
|
||||
export { StyledTextInput };
|
87
packages/shared/components/text-input/TextInput.test.tsx
Normal file
87
packages/shared/components/text-input/TextInput.test.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React from "react";
|
||||
import { screen, render } from "@testing-library/react";
|
||||
import "@testing-library/jest-dom";
|
||||
|
||||
import { TextInput } from "./TextInput";
|
||||
import { InputSize, InputType } from "./TextInput.enums";
|
||||
|
||||
describe("<TextInput />", () => {
|
||||
it("renders without error", () => {
|
||||
render(
|
||||
<TextInput
|
||||
value="text"
|
||||
size={InputSize.base}
|
||||
type={InputType.text}
|
||||
onChange={jest.fn()}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("text-input")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
//
|
||||
// it("not re-render test", () => {
|
||||
//
|
||||
// const onChange = jest.fn();
|
||||
|
||||
// const wrapper = shallow(
|
||||
// <TextInput value="text" onChange={onChange} />
|
||||
// ).instance();
|
||||
|
||||
// const shouldUpdate = wrapper.shouldComponentUpdate(wrapper.props);
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(shouldUpdate).toBe(false);
|
||||
// });
|
||||
|
||||
//
|
||||
// it("re-render test by value", () => {
|
||||
//
|
||||
// const onChange = jest.fn();
|
||||
|
||||
// const wrapper = shallow(
|
||||
// <TextInput value="text" onChange={onChange} />
|
||||
// ).instance();
|
||||
|
||||
// const shouldUpdate = wrapper.shouldComponentUpdate({
|
||||
// ...wrapper.props,
|
||||
// value: "another text",
|
||||
// });
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(shouldUpdate).toBe(true);
|
||||
// });
|
||||
|
||||
//
|
||||
// it("accepts id", () => {
|
||||
// const wrapper = mount(
|
||||
//
|
||||
// <TextInput value="text" onChange={jest.fn()} id="testId" />
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.prop("id")).toEqual("testId");
|
||||
// });
|
||||
|
||||
//
|
||||
// it("accepts className", () => {
|
||||
// const wrapper = mount(
|
||||
//
|
||||
// <TextInput value="text" onChange={jest.fn()} className="test" />
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.prop("className")).toEqual("test");
|
||||
// });
|
||||
|
||||
//
|
||||
// it("accepts style", () => {
|
||||
// const wrapper = mount(
|
||||
//
|
||||
// <TextInput value="text" onChange={jest.fn()} style={{ color: "red" }} />
|
||||
// );
|
||||
|
||||
// // @ts-expect-error TS(2304): Cannot find name 'expect'.
|
||||
// expect(wrapper.getDOMNode().style).toHaveProperty("color", "red");
|
||||
// });
|
||||
});
|
22
packages/shared/components/text-input/TextInput.tsx
Normal file
22
packages/shared/components/text-input/TextInput.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
import React from "react";
|
||||
import equal from "fast-deep-equal/react";
|
||||
|
||||
import { StyledTextInput } from "./TextInput.styled";
|
||||
import { TextInputProps } from "./TextInput.types";
|
||||
|
||||
const compare = (
|
||||
prevProps: Readonly<TextInputProps>,
|
||||
nextProps: Readonly<TextInputProps>,
|
||||
) => {
|
||||
return !equal(prevProps, nextProps);
|
||||
};
|
||||
|
||||
export const TextInputPure = (props: TextInputProps) => {
|
||||
return <StyledTextInput {...props} data-testid="text-input" />;
|
||||
};
|
||||
|
||||
const TextInput = React.memo(TextInputPure, compare);
|
||||
|
||||
TextInput.displayName = "TextInput";
|
||||
|
||||
export { TextInput };
|
61
packages/shared/components/text-input/TextInput.types.ts
Normal file
61
packages/shared/components/text-input/TextInput.types.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { Mask } from "react-text-mask";
|
||||
|
||||
import { InputSize, InputType } from "./TextInput.enums";
|
||||
|
||||
export interface TextInputProps {
|
||||
/** Used as HTML `id` property */
|
||||
id?: string;
|
||||
/** Forwarded ref */
|
||||
forwardedRef?: React.Ref<HTMLInputElement>;
|
||||
/** Used as HTML `name` property */
|
||||
name?: string;
|
||||
/** Supported type of the input fields. */
|
||||
type: InputType;
|
||||
/** Value of the input */
|
||||
value: string;
|
||||
/** Default maxLength value of the input */
|
||||
maxLength?: number;
|
||||
/** Placeholder text for the input */
|
||||
placeholder?: string;
|
||||
/** Used as HTML `tabindex` property */
|
||||
tabIndex?: number;
|
||||
/** input text mask */
|
||||
mask?: Mask | ((value: string) => Mask);
|
||||
/** Allows to add or delete characters without changing the positions of the existing characters. */
|
||||
keepCharPositions?: boolean;
|
||||
/** When guide is true, Text Mask always shows both placeholder characters and non-placeholder mask characters. */
|
||||
guide?: boolean;
|
||||
/** Supported size of the input fields. */
|
||||
size: InputSize;
|
||||
/** Indicates the input field has scale */
|
||||
scale?: boolean;
|
||||
/** Called with the new value. Required when input is not read only. Parent should pass it back as `value` */
|
||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
/** Called when field is blurred */
|
||||
onBlur?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
/** Called when field is focused */
|
||||
onFocus?: (e: React.FocusEvent<HTMLInputElement>) => void;
|
||||
/** Focus the input field on initial render */
|
||||
isAutoFocussed?: boolean;
|
||||
/** Indicates that the field cannot be used (e.g not authorised, or changes not saved) */
|
||||
isDisabled?: boolean;
|
||||
/** Indicates that the field is displaying read-only content */
|
||||
isReadOnly?: boolean;
|
||||
/** Indicates the input field has an error */
|
||||
hasError?: boolean;
|
||||
/** Indicates the input field has a warning */
|
||||
hasWarning?: boolean;
|
||||
/** Used as HTML `autocomplete` property */
|
||||
autoComplete?: string;
|
||||
/** Accepts class */
|
||||
className?: string;
|
||||
/** Accepts css style */
|
||||
style?: React.CSSProperties;
|
||||
/** Sets the font weight */
|
||||
fontWeight?: number | string;
|
||||
/** Sets font weight value to 600 */
|
||||
isBold?: boolean;
|
||||
/** Indicates that component contain border */
|
||||
withBorder?: boolean;
|
||||
dir?: string;
|
||||
}
|
2
packages/shared/components/text-input/index.ts
Normal file
2
packages/shared/components/text-input/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export { TextInput } from "./TextInput";
|
||||
export { InputSize, InputType } from "./TextInput.enums";
|
@ -0,0 +1,65 @@
|
||||
import React from "react";
|
||||
import MaskedInput from "react-text-mask";
|
||||
|
||||
import { TextInputProps } from "../TextInput.types";
|
||||
|
||||
const Input = ({
|
||||
isAutoFocussed,
|
||||
isDisabled,
|
||||
isReadOnly,
|
||||
hasError,
|
||||
hasWarning,
|
||||
scale,
|
||||
withBorder,
|
||||
keepCharPositions,
|
||||
guide,
|
||||
fontWeight,
|
||||
isBold,
|
||||
forwardedRef,
|
||||
className,
|
||||
dir = "auto",
|
||||
size,
|
||||
mask,
|
||||
...props
|
||||
}: TextInputProps) => {
|
||||
const rest = {
|
||||
autoFocus: isAutoFocussed,
|
||||
ref: forwardedRef || null,
|
||||
};
|
||||
|
||||
return mask ? (
|
||||
<MaskedInput
|
||||
className={`${className} not-selectable`}
|
||||
keepCharPositions
|
||||
guide={false}
|
||||
mask={mask}
|
||||
{...props}
|
||||
/>
|
||||
) : (
|
||||
<input
|
||||
className={`${className} not-selectable`}
|
||||
dir={dir}
|
||||
{...props}
|
||||
{...rest}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
Input.defaultProps = {
|
||||
type: "text",
|
||||
// Empty placeholder by default needed for RTL mode to make :placeholder-shown work to put cursor on the right side of input
|
||||
placeholder: " ",
|
||||
value: "",
|
||||
maxLength: 255,
|
||||
size: "base",
|
||||
scale: false,
|
||||
tabIndex: -1,
|
||||
hasError: false,
|
||||
hasWarning: false,
|
||||
autoComplete: "off",
|
||||
withBorder: true,
|
||||
keepCharPositions: false,
|
||||
guide: false,
|
||||
isBold: false,
|
||||
};
|
||||
export { Input };
|
10
yarn.lock
10
yarn.lock
@ -9326,6 +9326,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-text-mask@npm:^5.4.14":
|
||||
version: 5.4.14
|
||||
resolution: "@types/react-text-mask@npm:5.4.14"
|
||||
dependencies:
|
||||
"@types/react": "*"
|
||||
checksum: 52ddb3f68673a833da5c14fb21d3341f60975908849b99d7a3a6fde5f1c83d0a3e3a086f97fd3675788f15049dbd1988971e33263cdaead87ce825513e5b585c
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/react-textarea-autosize@npm:^4.3.3":
|
||||
version: 4.3.6
|
||||
resolution: "@types/react-textarea-autosize@npm:4.3.6"
|
||||
@ -14276,6 +14285,7 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "docspace@workspace:."
|
||||
dependencies:
|
||||
"@types/react-text-mask": ^5.4.14
|
||||
he: ^1.2.0
|
||||
madge: ^6.1.0
|
||||
shx: ^0.3.4
|
||||
|
Loading…
Reference in New Issue
Block a user