Skip to content

Commit

Permalink
feat(formComponent): add Form, FormGroup, ActionGroup PF4 components (#…
Browse files Browse the repository at this point in the history
…667)

* Added HTML Constant files

For importing needed HTML constants

* feat(formComponent): Adds form, formGroup, actionGroup components

fix #390
  • Loading branch information
rvsia authored and amarie401 committed Oct 16, 2018
1 parent 40eae3a commit c38779c
Show file tree
Hide file tree
Showing 24 changed files with 1,410 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { SFC, HTMLProps, ReactNode } from 'react';

export interface ActionGroupProps extends React.Component<HTMLDivElement> {
children?: ReactNode;
className?: string;
}

declare const ActionGroup: SFC<ActionGroupProps>;

export default ActionGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from '@patternfly/patternfly-next/components/Form/form.css';
import { css, getModifier } from '@patternfly/react-styles';
import { FormContext } from '../Form/FormContext';

const propTypes = {
/** Anything that can be rendered as ActionGroup content. */
children: PropTypes.node,
/** Additional classes added to the ActionGroup. */
className: PropTypes.string
};

const defaultProps = {
children: null,
className: ''
};

const ActionGroup = ({ className, children, ...props }) => {
const customClassName = css(styles.formGroup, getModifier(styles, 'action', styles.modifiers.info), className);
const classesHorizontal = css(styles.formHorizontalGroup);

return (
<FormContext.Consumer>
{({ isHorizontal }) => (
<div {...props} className={customClassName}>
{isHorizontal ? <div className={classesHorizontal}>{children}</div> : children}
</div>
)}
</FormContext.Consumer>
);
};

ActionGroup.propTypes = propTypes;
ActionGroup.defaultProps = defaultProps;

export default ActionGroup;
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react';
import { mount } from 'enzyme';
import ActionGroup from './ActionGroup';
import Form from './Form';

describe('ActionGroup component', () => {
test('should render default action group variant', () => {
const view = mount(
<ActionGroup>
<div>Hello</div>
</ActionGroup>
);
expect(view).toMatchSnapshot();
});

test('should render horizontal form ActionGroup variant', () => {
const view = mount(
<Form isHorizontal>
<ActionGroup />
</Form>
);
expect(view).toMatchSnapshot();
});
});
11 changes: 11 additions & 0 deletions packages/patternfly-4/react-core/src/components/Form/Form.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { SFC, HTMLProps, ReactNode } from 'react';

export interface FormProps extends React.Component<HTMLFormElement> {
isHorizontal?: boolean;
children?: ReactNode;
className?: string;
}

declare const Form: SFC<FormProps>;

export default Form;
17 changes: 17 additions & 0 deletions packages/patternfly-4/react-core/src/components/Form/Form.docs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Simple from './examples/SimpleForm';
import Horizontal from './examples/HorizontalForm';
import Various from './examples/VariousLabelsForm';
import Alternative from './examples/AlternativeForm';
import Invalid from './examples/InvalidForm';
import PropTypes from 'prop-types';
import { Form, FormGroup, ActionGroup } from '@patternfly/react-core';

export default {
title: 'Form',
components: {
Form,
FormGroup,
ActionGroup
},
examples: [Simple, Horizontal, Alternative, Invalid, Various]
};
34 changes: 34 additions & 0 deletions packages/patternfly-4/react-core/src/components/Form/Form.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react';
import styles from '@patternfly/patternfly-next/components/Form/form.css';
import PropTypes from 'prop-types';
import { css } from '@patternfly/react-styles';
import { FormContext } from './FormContext';

const propTypes = {
/** Anything that can be rendered as Form content. */
children: PropTypes.node,
/** Additional classes added to the Form. */
className: PropTypes.string,
/** Sets the Form to horizontal. */
isHorizontal: PropTypes.bool
};

const defaultProps = {
children: null,
className: '',
isHorizontal: false
};

const Form = ({ className, children, isHorizontal, ...props }) => (
<form
{...props}
className={css(styles.form, isHorizontal ? styles.modifiers.horizontal : styles.modifiers.info, className)}
>
<FormContext.Provider value={{ isHorizontal }}>{children}</FormContext.Provider>
</form>
);

Form.propTypes = propTypes;
Form.defaultProps = defaultProps;

export default Form;
15 changes: 15 additions & 0 deletions packages/patternfly-4/react-core/src/components/Form/Form.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from 'react';
import { shallow } from 'enzyme';
import Form from './Form';

describe('Form component', () => {
test('should render default form variant', () => {
const view = shallow(<Form />);
expect(view).toMatchSnapshot();
});

test('should render horizontal form variant', () => {
const view = shallow(<Form isHorizontal />);
expect(view).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import React from 'react';

export const FormContext = React.createContext({
isHorizontal: false
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import PropTypes from 'prop-types';
import { SFC, HTMLProps, ReactNode } from 'react';

export interface FormGroupProps extends React.Component<HTMLDivElement> {
children?: ReactNode;
className?: string;
isValid?: boolean;
isRequired?: boolean;
label?: ReactNode;
helperText?: ReactNode;
helperTextInvalid?: ReactNode;
fieldId: string;
isInline?: boolean;
}

declare const FormGroup: SFC<FormGroupProps>;

export default FormGroup;
114 changes: 114 additions & 0 deletions packages/patternfly-4/react-core/src/components/Form/FormGroup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import React from 'react';
import PropTypes from 'prop-types';
import styles from '@patternfly/patternfly-next/components/Form/form.css';
import { ASTERISK } from '../../internal/htmlConstants';
import { FormContext } from '../Form/FormContext';
import { css, getModifier } from '@patternfly/react-styles';

const childrenArrayContains = (childrenArray, attribute, errorMessage) => {
let throwError = true;
childrenArray.forEach(child => {
if (child.props[attribute]) {
throwError = false;
}
});
if (throwError) {
return new Error(errorMessage);
}
return null;
};

const propTypes = {
/** Anything that can be rendered as FormGroup content. */
children: props => {
if (!props.label) {
if (Array.isArray(props.children)) {
return childrenArrayContains(
props.children,
'aria-label',
`When label is not defined, at least one of Form Group children should have an aria-label attribute.`
);
} else if (!props.children.props['aria-label']) {
return new Error(`When label is not defined, a Form Group child should have an aria-label attribute.`);
}
}
if (Array.isArray(props.children)) {
return childrenArrayContains(props.children, 'id', `At least one of Form Group children should have an id.`);
} else if (!props.children.props.id) {
return new Error(`A child element of Form Group has to have an id.`);
}
return null;
},
/** Additional classes added to the FormGroup. */
className: PropTypes.string,
/** Label text before the field. */
label: PropTypes.node,
/** Sets the FormGroup required. */
isRequired: PropTypes.bool,
/** Sets the FormGroup isValid. */
isValid: PropTypes.bool,
/** Sets the FormGroup isInline. */
isInline: PropTypes.bool,
/** Helper text after the field. It can be a simple text or an object. */
helperText: PropTypes.node,
/** Helper text after the field when the field is isValid. It can be a simple text or an object. */
helperTextInvalid: PropTypes.node,
/** ID of the included field. It has to be the same for proper working. */
fieldId: PropTypes.string.isRequired
};

const defaultProps = {
children: null,
className: '',
label: undefined,
isRequired: false,
isValid: true,
isInline: false,
helperText: undefined,
helperTextInvalid: undefined
};

const FormGroup = ({
className,
children,
label,
isRequired,
isValid,
isInline,
helperText,
helperTextInvalid,
fieldId,
...props
}) => (
<FormContext.Consumer>
{({ isHorizontal }) => (
<div {...props} className={css(styles.formGroup, getModifier(styles, isInline && 'inline'), className)}>
{label && (
<label className={css(styles.formLabel)} htmlFor={fieldId}>
{label}
{isRequired && (
<span className={css(styles.formLabelRequired)} aria-hidden="true">
{ASTERISK}
</span>
)}
</label>
)}
{isHorizontal ? <div className={css(styles.formHorizontalGroup)}>{children}</div> : children}
{((isValid && helperText) || (!isValid && helperTextInvalid)) && (
<div
className={css(styles.formHelperText, getModifier(styles, !isValid && 'error', styles.modifiers.info))}
id={`${fieldId}-helper`}
aria-live="polite"
>
{isValid ? helperText : helperTextInvalid}
</div>
)}
</div>
)}
</FormContext.Consumer>
);

FormGroup.propTypes = propTypes;
FormGroup.defaultProps = defaultProps;

export default FormGroup;
Loading

0 comments on commit c38779c

Please sign in to comment.