summaryrefslogtreecommitdiff
path: root/animism-align/frontend/common/form.component.js
diff options
context:
space:
mode:
Diffstat (limited to 'animism-align/frontend/common/form.component.js')
-rw-r--r--animism-align/frontend/common/form.component.js220
1 files changed, 220 insertions, 0 deletions
diff --git a/animism-align/frontend/common/form.component.js b/animism-align/frontend/common/form.component.js
new file mode 100644
index 0000000..f3775a2
--- /dev/null
+++ b/animism-align/frontend/common/form.component.js
@@ -0,0 +1,220 @@
+import React, { Component } from 'react';
+import { courtesyS } from '../util'
+
+export const TextInput = props => (
+ <label className={props.error ? 'error' : 'text'}>
+ {props.title && <span>{props.title}</span>}
+ <input
+ type="text"
+ required={props.required}
+ onChange={props.onChange}
+ name={props.name}
+ value={props.data[props.name]}
+ placeholder={props.placeholder}
+ autoComplete={props.autoComplete}
+ />
+ </label>
+)
+
+export const LabelDescription = props => (
+ <label className={'text description'}>
+ <span>{props.title}</span>
+ <span>{props.children}</span>
+ </label>
+)
+
+export const NumberInput = props => (
+ <label className={props.error ? 'error' : 'text'}>
+ <span>{props.title}</span>
+ <input
+ type="number"
+ required={props.required}
+ onChange={props.onChange}
+ name={props.name}
+ value={props.data[props.name]}
+ min={props.min}
+ max={props.max}
+ step={props.step || 1}
+ />
+ </label>
+)
+
+export const ColorInput = props => (
+ <label className={props.error ? 'error color' : 'text color'}>
+ <span>{props.title}</span>
+ <input
+ type="color"
+ required={props.required}
+ onChange={props.onChange}
+ name={props.name}
+ value={props.data[props.name]}
+ />
+ <input
+ type="text"
+ required={props.required}
+ onChange={props.onChange}
+ name={props.name}
+ value={props.data[props.name]}
+ />
+ </label>
+)
+
+export const TextArea = props => (
+ <label className={props.error ? 'textarea error' : 'textarea'}>
+ {props.title && <span>{props.title}</span>}
+ <textarea
+ onChange={props.onChange}
+ name={props.name}
+ value={props.data[props.name]}
+ />
+ </label>
+)
+
+export const Checkbox = props => (
+ <label className="checkbox">
+ <input
+ type="checkbox"
+ name={props.name}
+ value={1}
+ checked={props.checked}
+ onChange={(e) => props.onChange(props.name, e.target.checked)}
+ />
+ <span>{props.label}</span>
+ </label>
+)
+
+export const Radio = props => {
+ return (
+ <label className="radio">
+ <input
+ type="radio"
+ name={props.name}
+ value={props.value}
+ checked={props.value === props.currentValue}
+ onChange={() => props.onChange(props.name, props.value)}
+ />
+ <span>{props.label}</span>
+ </label>
+ )
+}
+
+export class Select extends Component {
+ state = {
+ focused: false,
+ }
+
+ render() {
+ const { name, selected, options, defaultOption, title, loading, onChange, className } = this.props
+ if (loading) {
+ return <label className='select'><div>Loading...</div></label>
+ }
+ const { focused } = this.state
+ return (
+ <label>
+ {title && <span>{title}</span>}
+ <div className={(focused ? 'select focus' : 'select') + " " + (className || "")}>
+ <div>{(options.find(opt => opt.name === selected) || {label: defaultOption}).label}</div>
+ <select
+ onFocus={() => this.setState({ focused: true })}
+ onBlur={() => this.setState({ focused: false })}
+ onChange={e => {
+ onChange(name, e.target.value)
+ // this.setState({ focused: false })
+ }}
+ value={selected || "__default__"}
+ >
+ {!selected && defaultOption && <option value="__default__">{defaultOption}</option>}
+ {options.map((option, i) => (
+ <option
+ key={option.name}
+ value={option.name}
+ disabled={option.disabled}
+ >{option.label}</option>
+ ))}
+ </select>
+ </div>
+ </label>
+ )
+ }
+}
+
+export class FileInputField extends Component {
+ state = {
+ count: 0,
+ }
+
+ handleChange(files) {
+ const { multiple, onChange } = this.props
+ if (!files) {
+ this.setState({ count: 0 })
+ } else {
+ this.setState({ count: multiple ? files.length : 0 })
+ }
+ onChange(files)
+ }
+
+ render() {
+ const { error, title, label, required, multiple, mime, name } = this.props
+ return (
+ <label className={error ? 'error' : 'text fileInput'}>
+ <span>{title}</span>
+ <div className="row">
+ <button>
+ {label || "Choose files"}
+ <FileInput
+ mime={mime}
+ multiple={multiple}
+ onChange={this.handleChange.bind(this)}
+ />
+ </button>
+ {!!this.state.count && <span>{courtesyS(this.state.count, "file")}{" selected"}</span>}
+ </div>
+ </label>
+ )
+ }
+}
+
+export class FileInput extends Component {
+ handleChange(e) {
+ let { multiple, mime } = this.props
+ if (!mime) {
+ mime = "image/"
+ }
+ const files = e.dataTransfer ? e.dataTransfer.files : e.target.files
+ let i
+ let file, selectedFiles = []
+ for (i = 0; i < files.length; i++) {
+ file = files[i]
+ if (file && file.type.indexOf(mime) === 0) {
+ if (multiple) {
+ selectedFiles.push(file)
+ } else {
+ break
+ }
+ }
+ }
+ if (multiple && selectedFiles.length) {
+ this.props.onChange(selectedFiles)
+ } else if (!multiple && file) {
+ this.props.onChange(file)
+ } else {
+ this.props.onChange()
+ }
+ }
+
+ render() {
+ return (
+ <input type="file" multiple={!!this.props.multiple} onChange={this.handleChange.bind(this)} />
+ )
+ }
+}
+
+export const SubmitButton = (props) => (
+ <label>
+ <span></span>
+ <button
+ className={props.className ? "submit " + props.className : "submit"}
+ onClick={props.onClick}
+ >{props.title}</button>
+ </label>
+)