Created
November 1, 2019 09:31
-
-
Save RomiC/6f5167e8ba84cda82dc6aed96f891bf1 to your computer and use it in GitHub Desktop.
React Form Component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | |
); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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>; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.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%; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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