Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Translate EuiFormRow to TypeScript #2712

Merged
merged 12 commits into from
Jan 16, 2020
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## [`master`](https://github.com/elastic/eui/tree/master)

- Converted `EuiFormRow` to Typescript ([#2712](https://github.com/elastic/eui/pull/2712))
- Added `nested` glyph to `EuiIcon` ([#2707](https://github.com/elastic/eui/pull/2707))
- Added `tableLayout` prop to `EuiTable`, `EuiBasicTable` and `EuiInMemoryTable` to provide the option of auto layout ([#2697](https://github.com/elastic/eui/pull/2697))

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
"@types/react-is": "^16.7.1",
"@types/react-virtualized": "^9.18.7",
"@types/resize-observer-browser": "^0.1.1",
"@types/sinon": "^7.5.1",
"@types/tabbable": "^3.1.0",
"@types/uuid": "^3.4.4",
"@typescript-eslint/eslint-plugin": "^1.13.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('EuiFormRow', () => {
});

test('no children is an error', () => {
// @ts-ignore
expect(() => <EuiFormRow {...requiredProps} />).toThrow();
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React, { cloneElement, Component, Children } from 'react';
import PropTypes from 'prop-types';
import React, {cloneElement, Component, Children, HTMLAttributes, ReactNode} from 'react';
import classNames from 'classnames';
import {CommonProps, keysOf} from '../../common';

import { get } from '../../../services/objects';
import { withRequiredProp } from '../../../utils/prop_types/with_required_prop';
import {get} from '../../../services/objects';

import { EuiFormHelpText } from '../form_help_text';
import { EuiFormErrorText } from '../form_error_text';
import { EuiFormLabel } from '../form_label';
import {EuiFormHelpText} from '../form_help_text';
import {EuiFormErrorText} from '../form_error_text';
import {EuiFormLabel} from '../form_label';

import makeId from './make_id';

Expand All @@ -21,10 +20,46 @@ const displayToClassNameMap = {
'euiFormRow--compressed euiFormRow--horizontal euiFormRow--hasSwitch',
};

export const DISPLAYS = Object.keys(displayToClassNameMap);
export const DISPLAYS = keysOf(displayToClassNameMap);

export class EuiFormRow extends Component {
constructor(props) {
export type EuiFormRowDisplayKeys = keyof typeof displayToClassNameMap;

interface EuiFormRowState {
isFocused: boolean;
id: string;
};

export type EuiFormRowProps = CommonProps
& HTMLAttributes<HTMLDivElement & HTMLFieldSetElement>
& {
display?: EuiFormRowDisplayKeys;
hasEmptyLabelSpace?: boolean;
fullWidth?: boolean;
describedByIds?: string[];
labelType?: 'label' | 'legend';
hasChildLabel?: boolean;
children: ReactNode;
label?: ReactNode;
labelAppend?: any;
id?: string;
isInvalid?: boolean;
error?: ReactNode | Array<ReactNode>;
helpText?: ReactNode;
compressed?: boolean;
displayOnly?: boolean;
};

export class EuiFormRow extends Component<EuiFormRowProps, EuiFormRowState> {
static defaultProps = {
display: 'row',
hasEmptyLabelSpace: false,
fullWidth: false,
describedByIds: [],
labelType: 'label',
hasChildLabel: true,
};

constructor(props: EuiFormRowProps) {
super(props);

this.state = {
Expand All @@ -36,7 +71,7 @@ export class EuiFormRow extends Component {
this.onBlur = this.onBlur.bind(this);
}

onFocus(...args) {
onFocus(...args: any[]) {
// Doing this to allow onFocus to be called correctly from the child input element as this component overrides it
const onChildFocus = get(this.props, 'children.props.onFocus');
if (onChildFocus) {
Expand All @@ -48,7 +83,7 @@ export class EuiFormRow extends Component {
});
}

onBlur(...args) {
onBlur(...args: any[]) {
// Doing this to allow onBlur to be called correctly from the child input element as this component overrides it
const onChildBlur = get(this.props, 'children.props.onBlur');
if (onChildBlur) {
Expand Down Expand Up @@ -81,16 +116,19 @@ export class EuiFormRow extends Component {
...rest
} = this.props;

const { id } = this.state;
const {id} = this.state;

/**
* Remove when `compressed` is deprecated
*/
let shimDisplay;
let shimDisplay: EuiFormRowDisplayKeys;
if (compressed && display === 'row') {
shimDisplay = 'rowCompressed';
} else {
shimDisplay = display;
/**
* Safe use of ! as prop default is 'row'
*/
shimDisplay = display!;
}

/**
Expand Down Expand Up @@ -151,7 +189,7 @@ export class EuiFormRow extends Component {
isInvalid={isInvalid}
aria-invalid={isInvalid}
htmlFor={!isLegend && hasChildLabel ? id : undefined}
type={labelType}>
type={labelType as 'label' | undefined}>
{label}
</EuiFormLabel>
{labelAppend && ' '}
Expand All @@ -160,8 +198,11 @@ export class EuiFormRow extends Component {
);
}

const optionalProps = {};
const describingIds = [...describedByIds];
const optionalProps: React.AriaAttributes = {};
/**
* Safe use of ! as default prop is []
*/
const describingIds = [...describedByIds!];

if (optionalHelpText) {
describingIds.push(optionalHelpText.props.id);
Expand All @@ -175,6 +216,7 @@ export class EuiFormRow extends Component {
optionalProps['aria-describedby'] = describingIds.join(' ');
}

// @ts-ignore
const field = cloneElement(Children.only(children), {
id,
onFocus: this.onFocus,
Expand All @@ -184,7 +226,10 @@ export class EuiFormRow extends Component {

const fieldWrapperClasses = classNames('euiFormRow__fieldWrapper', {
euiFormRow__fieldWrapperDisplayOnly:
displayOnly || display.startsWith('center'),
/**
* Safe use of ! as default prop is 'row'
*/
displayOnly || display!.startsWith('center'),
});

const Element = labelType === 'legend' ? 'fieldset' : 'div';
Expand All @@ -201,72 +246,3 @@ export class EuiFormRow extends Component {
);
}
}

EuiFormRow.propTypes = {
children: PropTypes.element.isRequired,
className: PropTypes.string,
/**
* Escape hatch to not render duplicate labels if the child also renders a label
*/
junlarsen marked this conversation as resolved.
Show resolved Hide resolved
hasChildLabel: PropTypes.bool,
label: PropTypes.node,
/**
* Sets the type of html element the label should be based
* on the form row contents. For instance checkbox groups
* should use 'legend' instead of the default 'label'
*/
labelType: PropTypes.oneOf(['label', 'legend']),
/**
* Adds an extra node to the right of the form label without
* being contained inside the form label. Good for things
* like documentation links.
*/
labelAppend: withRequiredProp(
PropTypes.node,
'label',
'appending to the label requires that the label also exists'
),
id: PropTypes.string,
isInvalid: PropTypes.bool,
error: PropTypes.oneOfType([
PropTypes.node,
PropTypes.arrayOf(PropTypes.node),
]),
helpText: PropTypes.node,
hasEmptyLabelSpace: PropTypes.bool,
fullWidth: PropTypes.bool,
/**
* IDs of additional elements that should be part of children's `aria-describedby`
*/
describedByIds: PropTypes.array,
/**
* **DEPRECATED: use `display: rowCompressed` instead.**
* When `true`, tightens up the spacing.
*/
compressed: PropTypes.bool,
/**
* When `rowCompressed`, just tightens up the spacing;
* Set to `columnCompressed` if compressed
* and horizontal layout is needed.
* Set to `center` or `centerCompressed` to align non-input
* content better with inline rows.
* Set to `columnCompressedSwitch` if the form control being passed
* as the child is a switch.
*/
display: PropTypes.oneOf(DISPLAYS),
/**
* **DEPRECATED: use `display: center` instead.**
* Vertically centers non-input style content so it aligns
* better with input style content.
*/
displayOnly: PropTypes.bool,
};

EuiFormRow.defaultProps = {
display: 'row',
hasEmptyLabelSpace: false,
fullWidth: false,
describedByIds: [],
labelType: 'label',
hasChildLabel: true,
};
2 changes: 1 addition & 1 deletion src/components/form/form_row/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {

declare module '@elastic/eui' {
/**
* @see './form_row.js'
* @see './form_row.ts'
*/
export type EuiFormRowCommonProps = CommonProps & {
error?: ReactNode | ReactNode[];
Expand Down