Skip to content

Instantly share code, notes, and snippets.

@RomiC
Created November 1, 2019 09:31
Show Gist options
  • Save RomiC/6f5167e8ba84cda82dc6aed96f891bf1 to your computer and use it in GitHub Desktop.
Save RomiC/6f5167e8ba84cda82dc6aed96f891bf1 to your computer and use it in GitHub Desktop.
React Form Component
import React, {PureComponent, ReactNode, ReactNodeArray} from 'react';
import {
formColumn12,
formColumn2,
formColumn3,
formColumn4,
formColumn5,
formColumn6,
formColumn8,
rightFormColumn
} from './form.css';
interface FormColumnProps {
children: ReactNode | ReactNodeArray;
className?: string;
/**
* @default
*/
span?: 2 | 3 | 4 | 5 | 6 | 8 | 12;
/**
* Column content alignment
* @default 'left'
*/
align?: 'left' | 'right';
}
const columnSpanClassMap = {
2: formColumn2,
3: formColumn3,
4: formColumn4,
5: formColumn5,
6: formColumn6,
8: formColumn8,
12: formColumn12
};
export default class FormColumn extends PureComponent<FormColumnProps> {
render() {
return (
<div
className={`${columnSpanClassMap[this.props.span || 6]} ${this.props.align === 'right' ? rightFormColumn : ''} ${this.props.className || ''}`}>
{this.props.children}
</div>
);
}
}
import React, {PureComponent} from 'react';
import {formError} from './form.css';
interface FormErrorProps {
children: string;
}
export default class FormError extends PureComponent<FormErrorProps> {
render() {
return <div className={formError}>{this.props.children}</div>;
}
}
import createElementProps from 'degiro-frontend-core/lib/components/component/create-element-props';
import React, {HTMLProps, PureComponent} from 'react';
import {formHeading, formHeadingPrimary} from './form.css';
interface FormHeadingProps extends HTMLProps<HTMLHeadingElement> {
isPrimary?: boolean;
}
export default class FormHeading extends PureComponent<FormHeadingProps> {
render() {
const {className, isPrimary} = this.props;
const elementProps = createElementProps(this.props, ['level']);
elementProps.className = isPrimary ? formHeadingPrimary : formHeading;
if (className) {
elementProps.className += ` ${className}`;
}
return React.createElement(`h${isPrimary ? '1' : '2'}`, elementProps);
}
}
import React, {PureComponent, ReactNode, ReactNodeArray} from 'react';
import {formRow} from './form.css';
interface FormRowProps {
children: ReactNode | ReactNodeArray;
className?: string;
}
export default class FormRow extends PureComponent<FormRowProps> {
render() {
return <div className={`${formRow} ${this.props.className || ''}`}>{this.props.children}</div>;
}
}
.formRow {
display: flex;
flex-wrap: wrap;
padding: calc(var(--grid) * 2) 0;
width: 100%;
}
.formColumn {
display: block;
padding: 0 calc(var(--grid) * 2);
}
.formColumn2 {
composes: formColumn;
width: 16.666666665%;
}
.formColumn3 {
composes: formColumn;
width: 25%;
}
.formColumn4 {
composes: formColumn;
width: 33.33333333%;
}
.formColumn5 {
composes: formColumn;
width: 41.66666666%;
}
.formColumn6 {
composes: formColumn;
width: 50%;
}
.formColumn8 {
composes: formColumn;
width: 66.66666666%;
}
.formColumn12 {
composes: formColumn;
width: 100%;
}
.rightFormColumn {
text-align: right;
}
.formHeading {
color: var(--inactive1-color);
font-size: var(--body-text-small-font-size);
font-weight: normal;
line-height: 1.25rem;
margin: 0;
padding-bottom: var(--grid);
}
.formHeadingPrimary {
margin: 0;
}
.formError {
border: var(--border-width) solid var(--border5-color);
color: var(--red1-color);
display: block;
padding: calc(var(--grid) * 2);
width: 100%;
}
import React, {FormEventHandler, MouseEventHandler, PureComponent} from 'react';
import FormColumn from './form-column';
import FormError from './form-error';
import FormHeading from './form-heading';
import FormRow from './form-row';
export interface FormFields {
[K: string]: string | string[];
}
type FormFieldElement = HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement;
interface FormProps {
className?: string;
onInput?(fields: any): void;
onSubmit?(fields: any): void;
}
export default class Form extends PureComponent<FormProps> {
static Row = FormRow;
static Column = FormColumn;
static Heading = FormHeading;
static Error = FormError;
private submitButton: HTMLButtonElement | undefined;
private getFields(form: HTMLFormElement): FormFields | undefined {
const formFields: FormFields = {};
for (const field of form.querySelectorAll<FormFieldElement>('input,select,textarea')) {
const {name, type, value} = field;
if (!name || field.disabled || type === 'submit') {
continue;
}
let fieldValue: string | string[] | null = null;
switch (field.tagName) {
case 'INPUT':
switch (type) {
case 'radio':
case 'checkbox':
if ((field as HTMLInputElement).checked) {
fieldValue = value;
}
break;
default:
fieldValue = value;
}
break;
case 'SELECT':
if ((field as HTMLSelectElement).multiple) {
const values = [];
for (const option of (field as HTMLSelectElement).options) {
if (option.selected) {
values.push(option.value);
}
}
fieldValue = values.length > 1 ? values : values[0];
} else {
fieldValue = value;
}
break;
case 'TEXTAREA':
fieldValue = value;
break;
default:
}
// Case for non-checked radio and checkbox inputs
if (fieldValue === null) {
continue;
}
const currentValue = formFields[name];
if (currentValue != null && type !== 'radio') {
formFields[name] = [
...(Array.isArray(currentValue) ? currentValue : [currentValue]),
...(Array.isArray(fieldValue) ? fieldValue : [fieldValue])
];
} else {
formFields[name] = fieldValue;
}
}
return formFields;
}
private onInput: FormEventHandler<HTMLFormElement> = (event) => {
const {onInput} = this.props;
const fields = this.getFields(event.currentTarget);
if (typeof onInput === 'function' && fields != null) {
onInput(fields);
}
};
private appendSubmitButtonValue = (fields: FormFields): void => {
if (!this.submitButton) {
return;
}
fields[this.submitButton.name] = this.submitButton.value;
this.submitButton = undefined;
};
private onSubmit: FormEventHandler<HTMLFormElement> = (event) => {
const {onSubmit} = this.props;
const fields = this.getFields(event.currentTarget);
if (typeof onSubmit === 'function' && fields != null) {
event.preventDefault();
// We'll simulate form element behaviour,
// when a form submit pressed button value as well
this.appendSubmitButtonValue(fields);
onSubmit(fields);
}
};
private onClick: MouseEventHandler<HTMLFormElement> = (event) => {
const {target} = event;
if ((target as HTMLElement).tagName === 'BUTTON') {
const button = target as HTMLButtonElement;
if (button.type === 'submit' && button.name) {
this.submitButton = target as HTMLButtonElement;
}
}
};
render() {
return (
<form
className={`${this.props.className || ''}`}
onInput={this.onInput}
onClick={this.onClick}
onSubmit={this.onSubmit}>
{this.props.children}
</form>
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment