Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 66 additions & 6 deletions packages/react-core/src/components/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import styles from '@patternfly/react-styles/css/components/FormControl/form-control';
import { css } from '@patternfly/react-styles';
import { ValidatedOptions } from '../../helpers/constants';
import { debounce, trimLeft } from '../../helpers/util';

export enum TextInputTypes {
text = 'text',
Expand All @@ -17,7 +18,8 @@ export enum TextInputTypes {
url = 'url'
}

export interface TextInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'disabled' | 'ref'> {
export interface TextInputProps
extends Omit<React.HTMLProps<HTMLInputElement>, 'onChange' | 'onFocus' | 'onBlur' | 'disabled' | 'ref'> {
/** Additional classes added to the TextInput. */
className?: string;
/** Flag to show if the input is disabled. */
Expand Down Expand Up @@ -51,7 +53,13 @@ export interface TextInputProps extends Omit<React.HTMLProps<HTMLInputElement>,
/** Aria-label. The input requires an associated id or aria-label. */
'aria-label'?: string;
/** A reference object to attach to the input box. */
innerRef?: React.Ref<any>;
innerRef?: React.RefObject<any>;
/** Trim text on left */
isLeftTruncated?: boolean;
/** Callback function when input is focused */
onFocus?: (event?: any) => void;
/** Callback function when input is blurred (focus leaves) */
onBlur?: (event?: any) => void;
}

export class TextInputBase extends React.Component<TextInputProps> {
Expand All @@ -64,8 +72,10 @@ export class TextInputBase extends React.Component<TextInputProps> {
isDisabled: false,
isReadOnly: false,
type: TextInputTypes.text,
isLeftTruncated: false,
onChange: (): any => undefined
};
inputRef = React.createRef<HTMLInputElement>();

constructor(props: TextInputProps) {
super(props);
Expand All @@ -81,15 +91,63 @@ export class TextInputBase extends React.Component<TextInputProps> {
}
};

componentDidMount() {
if (this.props.isLeftTruncated) {
this.handleResize();
window.addEventListener('resize', debounce(this.handleResize, 250));

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i just realized that the input box dimensions can change even if there was no window resize, for example if a sidebar is opened. Perhaps we should look at something like the ResizeObserver instead
https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver
Example implementation
https://github.com/ZeeCoder/use-resize-observer

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Opened followup issue for next milestone here #4710 @jenny-s51 @tlabaj

}
}

componentWillUnmount() {
if (this.props.isLeftTruncated) {
window.removeEventListener('resize', debounce(this.handleResize, 250));
}
}

handleResize = () => {
const inputRef = this.props.innerRef || this.inputRef;
if (inputRef && inputRef.current) {
trimLeft(inputRef.current, String(this.props.value));
}
};

restoreText = () => {
const inputRef = this.props.innerRef || this.inputRef;
// restore the value
(inputRef.current as HTMLInputElement).value = String(this.props.value);
// make sure we still see the rightmost value to preserve cursor click position
inputRef.current.scrollLeft = inputRef.current.scrollWidth;
};

onFocus = (event?: any) => {
const { isLeftTruncated, onFocus } = this.props;
if (isLeftTruncated) {
this.restoreText();
}
onFocus && onFocus(event);
};

onBlur = (event?: any) => {
const { isLeftTruncated, onBlur } = this.props;
if (isLeftTruncated) {
this.handleResize();
}
onBlur && onBlur(event);
};

render() {
const {
innerRef,
className,
type,
value,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onChange,
validated,
/* eslint-disable @typescript-eslint/no-unused-vars */
onChange,
onFocus,
onBlur,
isLeftTruncated,
/* eslint-enable @typescript-eslint/no-unused-vars */
isReadOnly,
isRequired,
isDisabled,
Expand All @@ -98,6 +156,8 @@ export class TextInputBase extends React.Component<TextInputProps> {
return (
<input
{...props}
onFocus={this.onFocus}
onBlur={this.onBlur}
className={css(
styles.formControl,
validated === ValidatedOptions.success && styles.modifiers.success,
Expand All @@ -111,12 +171,12 @@ export class TextInputBase extends React.Component<TextInputProps> {
required={isRequired}
disabled={isDisabled}
readOnly={isReadOnly}
ref={innerRef}
ref={innerRef || this.inputRef}
/>
);
}
}

export const TextInput = React.forwardRef((props: TextInputProps, ref: React.Ref<HTMLInputElement>) => (
<TextInputBase {...props} innerRef={ref} />
<TextInputBase {...props} innerRef={ref as React.MutableRefObject<any>} />
));
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ exports[`disabled text input 1`] = `
aria-label="disabled text input"
className="pf-c-form-control"
disabled={true}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={false}
required={false}
type="text"
Expand All @@ -19,7 +21,9 @@ exports[`invalid text input 1`] = `
aria-label="invalid text input"
className="pf-c-form-control"
disabled={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={false}
required={false}
type="text"
Expand All @@ -33,7 +37,9 @@ exports[`readonly text input 1`] = `
aria-label="readonly text input"
className="pf-c-form-control"
disabled={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={true}
required={false}
type="text"
Expand All @@ -47,7 +53,9 @@ exports[`simple text input 1`] = `
aria-label="simple text input"
className="pf-c-form-control"
disabled={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={false}
required={false}
type="text"
Expand All @@ -61,6 +69,7 @@ exports[`validated text input 1`] = `
className=""
innerRef={null}
isDisabled={false}
isLeftTruncated={false}
isReadOnly={false}
isRequired={false}
onChange={[MockFunction]}
Expand All @@ -84,6 +93,7 @@ exports[`validated text input success 1`] = `
className=""
innerRef={null}
isDisabled={false}
isLeftTruncated={false}
isReadOnly={false}
isRequired={false}
onChange={[MockFunction]}
Expand All @@ -97,7 +107,9 @@ exports[`validated text input success 1`] = `
aria-label="validated text input"
className="pf-c-form-control pf-m-success"
disabled={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={false}
required={false}
type="text"
Expand All @@ -120,6 +132,7 @@ exports[`validated text input success 2`] = `
className=""
innerRef={null}
isDisabled={false}
isLeftTruncated={false}
isReadOnly={false}
isRequired={false}
onChange={[MockFunction]}
Expand All @@ -133,7 +146,9 @@ exports[`validated text input success 2`] = `
aria-label="validated text input"
className="pf-c-form-control pf-m-warning"
disabled={false}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
readOnly={false}
required={false}
type="text"
Expand Down
53 changes: 48 additions & 5 deletions packages/react-core/src/components/TextInput/examples/TextInput.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,58 @@ class SimpleTextInput extends React.Component {
```

### Disabled

```js
import React from 'react';
import { TextInput } from '@patternfly/react-core';

<TextInput
value="disabled text input example"
type="text"
onChange={this.handleTextInputChange}
aria-label="disabled text input example"
isDisabled
/>;
```

### Truncated on Left

```js
import React from 'react';
import { TextInput } from '@patternfly/react-core';

<TextInput type="text" value="disabled text input example" aria-label="disabled text input example" isDisabled />
class LeftTruncatedTextInput extends React.Component {

constructor(props) {
super(props);
this.state = {
value: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.'
};
this.handleTextInputChange = value => {
this.setState({ value });
};
}

render() {
const { value } = this.state;
return (
<TextInput isLeftTruncated value={value} type="text" onChange={this.handleTextInputChange} aria-label="left-truncated text input example" />
);
}
}
```

### Read only

```js
import React from 'react';
import { TextInput } from '@patternfly/react-core';

<TextInput value="read only text input example" type="text" isReadOnly aria-label="readonly input example" />
<TextInput value="read only text input example" type="text" isReadOnly aria-label="readonly input example" />;
```

### Invalid

```js
import React from 'react';
import { TextInput, ValidatedOptions } from '@patternfly/react-core';
Expand Down Expand Up @@ -82,6 +118,7 @@ class InvalidTextInput extends React.Component {
```

### Select text using ref

```js
import React from 'react';
import { TextInput, Button } from '@patternfly/react-core';
Expand All @@ -91,8 +128,14 @@ TextInputSelectAll = () => {
const ref = React.useRef(null);
return (
<React.Fragment>
<TextInput ref={ref} value={value} onFocus={() => ref && ref.current && ref.current.select()} onChange={value => setValue(value)} aria-label="select-all" />
<TextInput
ref={ref}
value={value}
onFocus={() => ref && ref.current && ref.current.select()}
onChange={value => setValue(value)}
aria-label="select-all"
/>
</React.Fragment>
)
}
);
};
```
Loading