lbry-desktop/ui/component/common/form-components/form-field.jsx

249 lines
7.5 KiB
React
Raw Normal View History

2018-03-26 23:32:43 +02:00
// @flow
2020-02-21 23:05:10 +01:00
import type { ElementRef, Node } from 'react';
2019-05-08 03:42:56 +02:00
import React from 'react';
import ReactDOMServer from 'react-dom/server';
2019-05-08 03:42:56 +02:00
import SimpleMDE from 'react-simplemde-editor';
import MarkdownPreview from 'component/common/markdown-preview';
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
import 'easymde/dist/easymde.min.css';
import Button from 'component/button';
2019-04-03 07:56:58 +02:00
2018-03-26 23:32:43 +02:00
type Props = {
name: string,
2020-02-21 23:05:10 +01:00
label?: string | Node,
2019-04-24 16:02:08 +02:00
render?: () => React$Node,
2018-03-26 23:32:43 +02:00
prefix?: string,
postfix?: string,
error?: string | boolean,
2019-04-24 16:02:08 +02:00
helper?: string | React$Node,
2018-03-26 23:32:43 +02:00
type?: string,
onChange?: any => any,
defaultValue?: string | number,
placeholder?: string | number,
2019-04-24 16:02:08 +02:00
children?: React$Node,
2018-03-26 23:32:43 +02:00
stretch?: boolean,
2018-06-14 22:10:50 +02:00
affixClass?: string, // class applied to prefix/postfix label
2018-09-22 03:20:58 +02:00
autoFocus?: boolean,
2019-02-13 17:27:20 +01:00
labelOnLeft: boolean,
inputProps?: {
2018-09-04 19:18:11 +02:00
disabled?: boolean,
},
2019-04-24 16:02:08 +02:00
inputButton?: React$Node,
2019-02-18 18:24:56 +01:00
blockWrap: boolean,
charCount?: number,
textAreaMaxLength?: number,
range?: number,
min?: number,
max?: number,
quickActionLabel?: string,
quickActionHandler?: any => any,
2018-03-26 23:32:43 +02:00
};
export class FormField extends React.PureComponent<Props> {
2019-02-13 17:27:20 +01:00
static defaultProps = {
labelOnLeft: false,
2019-02-18 18:24:56 +01:00
blockWrap: true,
2019-02-13 17:27:20 +01:00
};
2019-04-24 16:02:08 +02:00
input: { current: ElementRef<any> };
2019-02-20 06:20:29 +01:00
2019-02-18 18:24:56 +01:00
constructor(props: Props) {
2018-09-22 03:20:58 +02:00
super(props);
this.input = React.createRef();
}
componentDidMount() {
const { autoFocus } = this.props;
const input = this.input.current;
if (input && autoFocus) {
input.focus();
}
}
2018-03-26 23:32:43 +02:00
render() {
const {
render,
label,
prefix,
postfix,
error,
helper,
name,
type,
children,
stretch,
2018-06-14 22:10:50 +02:00
affixClass,
2018-09-22 03:20:58 +02:00
autoFocus,
2019-02-13 17:27:20 +01:00
inputButton,
labelOnLeft,
2019-02-18 18:24:56 +01:00
blockWrap,
charCount,
textAreaMaxLength = FF_MAX_CHARS_DEFAULT,
quickActionLabel,
quickActionHandler,
2018-03-26 23:32:43 +02:00
...inputProps
} = this.props;
const errorMessage = typeof error === 'object' ? error.message : error;
2019-02-18 18:24:56 +01:00
const Wrapper = blockWrap
2019-11-22 22:13:00 +01:00
? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
: ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
2019-02-18 18:24:56 +01:00
const quickAction =
quickActionLabel && quickActionHandler ? (
<div className="form-field__quick-action">
<Button button="link" onClick={quickActionHandler} label={quickActionLabel} />
</div>
) : null;
2018-03-26 23:32:43 +02:00
let input;
if (type) {
2019-02-13 17:27:20 +01:00
if (type === 'radio') {
input = (
2019-02-18 18:24:56 +01:00
<Wrapper>
2019-11-22 22:13:00 +01:00
<input id={name} type="radio" {...inputProps} />
<label htmlFor={name}>{label}</label>
2019-02-18 18:24:56 +01:00
</Wrapper>
2019-02-13 17:27:20 +01:00
);
} else if (type === 'checkbox') {
input = (
2019-11-22 22:13:00 +01:00
<div className="checkbox">
<input id={name} type="checkbox" {...inputProps} />
<label htmlFor={name}>{label}</label>
</div>
2019-02-13 17:27:20 +01:00
);
} else if (type === 'range') {
input = (
<div>
<input id={name} type="range" {...inputProps} />
<label htmlFor={name}>{label}</label>
</div>
);
2019-02-13 17:27:20 +01:00
} else if (type === 'select') {
2018-03-26 23:32:43 +02:00
input = (
2019-02-13 17:27:20 +01:00
<fieldset-section>
{(label || errorMessage) && (
<label htmlFor={name}>{errorMessage ? <span className="error__text">{errorMessage}</span> : label}</label>
)}
2019-02-13 17:27:20 +01:00
<select id={name} {...inputProps}>
{children}
</select>
2019-02-13 17:27:20 +01:00
</fieldset-section>
2018-03-26 23:32:43 +02:00
);
} else if (type === 'markdown') {
2018-06-25 22:42:49 +02:00
const handleEvents = {
2018-07-29 01:48:54 +02:00
contextmenu: openEditorMenu,
2018-06-25 22:42:49 +02:00
};
2018-07-29 01:48:54 +02:00
const getInstance = editor => {
// SimpleMDE max char check
editor.codemirror.on('beforeChange', (instance, changes) => {
if (textAreaMaxLength && changes.update) {
var str = changes.text.join('\n');
var delta = str.length - (instance.indexFromPos(changes.to) - instance.indexFromPos(changes.from));
if (delta <= 0) {
return;
}
delta = instance.getValue().length + delta - textAreaMaxLength;
if (delta > 0) {
str = str.substr(0, str.length - delta);
changes.update(changes.from, changes.to, str.split('\n'));
}
}
});
};
// Ideally, the character count should (and can) be appended to the
// SimpleMDE's "options::status" bar. However, I couldn't figure out how
// to pass the current value to it's callback, nor query the current
// text length from the callback. So, we'll use our own widget.
const hasCharCount = charCount !== undefined && charCount >= 0;
const countInfo = hasCharCount && (
<span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
);
2018-03-26 23:32:43 +02:00
input = (
2019-03-21 16:22:23 +01:00
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
2019-02-13 17:27:20 +01:00
<fieldset-section>
<div className="form-field__two-column">
<div>
<label htmlFor={name}>{label}</label>
</div>
{quickAction}
</div>
2019-05-08 03:42:56 +02:00
<SimpleMDE
{...inputProps}
id={name}
type="textarea"
events={handleEvents}
getMdeInstance={getInstance}
2019-05-08 03:42:56 +02:00
options={{
2020-01-24 21:32:22 +01:00
spellChecker: true,
2019-05-08 03:42:56 +02:00
hideIcons: ['heading', 'image', 'fullscreen', 'side-by-side'],
previewRender(plainText) {
const preview = <MarkdownPreview content={plainText} />;
return ReactDOMServer.renderToString(preview);
},
}}
/>
{countInfo}
2019-02-13 17:27:20 +01:00
</fieldset-section>
2018-03-26 23:32:43 +02:00
</div>
);
2018-04-06 07:15:29 +02:00
} else if (type === 'textarea') {
const hasCharCount = charCount !== undefined && charCount >= 0;
const countInfo = hasCharCount && (
2019-10-01 00:08:16 +02:00
<span className="comment__char-count">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
);
2019-02-13 17:27:20 +01:00
input = (
<fieldset-section>
<div className="form-field__two-column">
<div>
<label htmlFor={name}>{label}</label>
</div>
{quickAction}
</div>
2019-11-04 13:50:51 +01:00
<textarea type={type} id={name} maxLength={textAreaMaxLength} ref={this.input} {...inputProps} />
{countInfo}
2019-02-13 17:27:20 +01:00
</fieldset-section>
);
2018-03-26 23:32:43 +02:00
} else {
2019-02-13 17:27:20 +01:00
const inputElement = <input type={type} id={name} {...inputProps} ref={this.input} />;
const inner = inputButton ? (
<input-submit>
{inputElement}
{inputButton}
</input-submit>
) : (
inputElement
);
input = (
<React.Fragment>
<fieldset-section>
2020-02-06 19:49:05 +01:00
{(label || errorMessage) && (
2019-11-22 22:13:00 +01:00
<label htmlFor={name}>
{errorMessage ? <span className="error__text">{errorMessage}</span> : label}
2019-11-22 22:13:00 +01:00
</label>
)}
2019-09-26 18:07:11 +02:00
{prefix && <label htmlFor={name}>{prefix}</label>}
2019-02-13 17:27:20 +01:00
{inner}
</fieldset-section>
</React.Fragment>
);
2018-03-26 23:32:43 +02:00
}
}
return (
2019-02-13 17:27:20 +01:00
<React.Fragment>
{input}
2019-03-21 16:22:23 +01:00
{helper && <div className="form-field__help">{helper}</div>}
2019-02-13 17:27:20 +01:00
</React.Fragment>
2018-03-26 23:32:43 +02:00
);
}
}
export default FormField;