Cleanup Form-Field
- avoid declaring components inside the body function of parent components https://dev.to/borasvm/react-create-component-inside-a-component-456b
This commit is contained in:
parent
ba5d96bb71
commit
1f367c641e
5 changed files with 324 additions and 195 deletions
52
ui/component/common/form-components/common.jsx
Normal file
52
ui/component/common/form-components/common.jsx
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import Button from 'component/button';
|
||||||
|
|
||||||
|
type CountInfoProps = {
|
||||||
|
charCount?: number,
|
||||||
|
textAreaMaxLength?: number,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CountInfo = (countInfoProps: CountInfoProps) => {
|
||||||
|
const { charCount, textAreaMaxLength } = countInfoProps;
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
return (
|
||||||
|
hasCharCount &&
|
||||||
|
textAreaMaxLength !== undefined && (
|
||||||
|
<span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type QuickActionProps = {
|
||||||
|
label?: string,
|
||||||
|
quickActionHandler?: (any) => any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const QuickAction = (quickActionProps: QuickActionProps) => {
|
||||||
|
const { label, quickActionHandler } = quickActionProps;
|
||||||
|
|
||||||
|
return label && quickActionHandler ? (
|
||||||
|
<div className="form-field__quick-action">
|
||||||
|
<Button button="link" onClick={quickActionHandler} label={label} />
|
||||||
|
</div>
|
||||||
|
) : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
type LabelProps = {
|
||||||
|
name: string,
|
||||||
|
label?: any,
|
||||||
|
errorMessage?: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Label = (labelProps: LabelProps) => {
|
||||||
|
const { name, label, errorMessage } = labelProps;
|
||||||
|
|
||||||
|
return <label htmlFor={name}>{errorMessage ? <span className="error__text">{errorMessage}</span> : label}</label>;
|
||||||
|
};
|
|
@ -1,16 +1,18 @@
|
||||||
// @flow
|
// @flow
|
||||||
import 'easymde/dist/easymde.min.css';
|
import 'easymde/dist/easymde.min.css';
|
||||||
|
|
||||||
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
|
import { FF_MAX_CHARS_DEFAULT } from 'constants/form-field';
|
||||||
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
import { openEditorMenu, stopContextMenu } from 'util/context-menu';
|
||||||
import { lazyImport } from 'util/lazyImport';
|
import { lazyImport } from 'util/lazyImport';
|
||||||
import Button from 'component/button';
|
|
||||||
import MarkdownPreview from 'component/common/markdown-preview';
|
import MarkdownPreview from 'component/common/markdown-preview';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOMServer from 'react-dom/server';
|
import ReactDOMServer from 'react-dom/server';
|
||||||
import SimpleMDE from 'react-simplemde-editor';
|
import SimpleMDE from 'react-simplemde-editor';
|
||||||
import type { ElementRef, Node } from 'react';
|
import type { ElementRef } from 'react';
|
||||||
import Drawer from '@mui/material/Drawer';
|
import { InputSimple, BlockWrapWrapper } from './input-simple';
|
||||||
import CommentSelectors from 'component/commentCreate/comment-selectors';
|
import { InputSelect } from './input-select';
|
||||||
|
import { CountInfo, QuickAction, Label } from './common';
|
||||||
|
import { TextareaWrapper } from './slim-input-field';
|
||||||
|
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
const TextareaWithSuggestions = lazyImport(() => import('component/textareaWithSuggestions' /* webpackChunkName: "suggestions" */));
|
const TextareaWithSuggestions = lazyImport(() => import('component/textareaWithSuggestions' /* webpackChunkName: "suggestions" */));
|
||||||
|
@ -29,7 +31,7 @@ type Props = {
|
||||||
hideSuggestions?: boolean,
|
hideSuggestions?: boolean,
|
||||||
inputButton?: React$Node,
|
inputButton?: React$Node,
|
||||||
isLivestream?: boolean,
|
isLivestream?: boolean,
|
||||||
label?: string | Node,
|
label?: any,
|
||||||
labelOnLeft: boolean,
|
labelOnLeft: boolean,
|
||||||
max?: number,
|
max?: number,
|
||||||
min?: number,
|
min?: number,
|
||||||
|
@ -124,56 +126,50 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
|
|
||||||
const errorMessage = typeof error === 'object' ? error.message : error;
|
const errorMessage = typeof error === 'object' ? error.message : error;
|
||||||
|
|
||||||
// Ideally, the character count should (and can) be appended to the
|
const wrapperProps = { type, helper };
|
||||||
// SimpleMDE's "options::status" bar. However, I couldn't figure out how
|
const labelProps = { name, label };
|
||||||
// to pass the current value to it's callback, nor query the current
|
const countInfoProps = { charCount, textAreaMaxLength };
|
||||||
// text length from the callback. So, we'll use our own widget.
|
const quickActionProps = { label: quickActionLabel, quickActionHandler };
|
||||||
const hasCharCount = charCount !== undefined && charCount >= 0;
|
const inputSimpleProps = { name, label, ...inputProps };
|
||||||
const countInfo = hasCharCount && textAreaMaxLength !== undefined && (
|
const inputSelectProps = { name, error, label, children, ...inputProps };
|
||||||
<span className="comment__char-count-mde">{`${charCount || '0'}/${textAreaMaxLength}`}</span>
|
|
||||||
);
|
|
||||||
|
|
||||||
const Wrapper = blockWrap
|
|
||||||
? ({ children: innerChildren }) => <fieldset-section class="radio">{innerChildren}</fieldset-section>
|
|
||||||
: ({ children: innerChildren }) => <span className="radio">{innerChildren}</span>;
|
|
||||||
|
|
||||||
const quickAction =
|
|
||||||
quickActionLabel && quickActionHandler ? (
|
|
||||||
<div className="form-field__quick-action">
|
|
||||||
<Button button="link" onClick={quickActionHandler} label={quickActionLabel} />
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
|
|
||||||
const inputSimple = (type: string) => (
|
|
||||||
<>
|
|
||||||
<input id={name} type={type} {...inputProps} />
|
|
||||||
<label htmlFor={name}>{label}</label>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const inputSelect = (selectClass: string) => (
|
|
||||||
<fieldset-section class={selectClass}>
|
|
||||||
{(label || errorMessage) && (
|
|
||||||
<label htmlFor={name}>{errorMessage ? <span className="error__text">{errorMessage}</span> : label}</label>
|
|
||||||
)}
|
|
||||||
<select id={name} {...inputProps}>
|
|
||||||
{children}
|
|
||||||
</select>
|
|
||||||
</fieldset-section>
|
|
||||||
);
|
|
||||||
|
|
||||||
const input = () => {
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'radio':
|
case 'radio':
|
||||||
return <Wrapper>{inputSimple('radio')}</Wrapper>;
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<BlockWrapWrapper blockWrap={blockWrap}>
|
||||||
|
<InputSimple {...inputSimpleProps} type="radio" />
|
||||||
|
</BlockWrapWrapper>
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
return <div className="checkbox">{inputSimple('checkbox')}</div>;
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<div className="checkbox">
|
||||||
|
<InputSimple {...inputSimpleProps} type="checkbox" />
|
||||||
|
</div>
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
case 'range':
|
case 'range':
|
||||||
return <div>{inputSimple('range')}</div>;
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<div className="range">
|
||||||
|
<InputSimple {...inputSimpleProps} type="range" />
|
||||||
|
</div>
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
case 'select':
|
case 'select':
|
||||||
return inputSelect('');
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<InputSelect {...inputSelectProps} />
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
case 'select-tiny':
|
case 'select-tiny':
|
||||||
return inputSelect('select--slim');
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<InputSelect {...inputSelectProps} className="select--slim" />
|
||||||
|
</FormFieldWrapper>
|
||||||
|
);
|
||||||
case 'markdown':
|
case 'markdown':
|
||||||
const handleEvents = { contextmenu: openEditorMenu };
|
const handleEvents = { contextmenu: openEditorMenu };
|
||||||
|
|
||||||
|
@ -232,14 +228,17 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
|
<div className="form-field--SimpleMDE" onContextMenu={stopContextMenu}>
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<div className="form-field__two-column">
|
<div className="form-field__two-column">
|
||||||
<div>
|
<div>
|
||||||
<label htmlFor={name}>{label}</label>
|
<Label {...labelProps} />
|
||||||
</div>
|
</div>
|
||||||
{quickAction}
|
|
||||||
|
<QuickAction {...quickActionProps} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<SimpleMDE
|
<SimpleMDE
|
||||||
{...inputProps}
|
{...inputProps}
|
||||||
id={name}
|
id={name}
|
||||||
|
@ -255,9 +254,11 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{countInfo}
|
|
||||||
|
<CountInfo {...countInfoProps} />
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
</div>
|
</div>
|
||||||
|
</FormFieldWrapper>
|
||||||
);
|
);
|
||||||
case 'textarea':
|
case 'textarea':
|
||||||
const closeSelector =
|
const closeSelector =
|
||||||
|
@ -266,6 +267,7 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
: () => {};
|
: () => {};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
<fieldset-section>
|
<fieldset-section>
|
||||||
<TextareaWrapper
|
<TextareaWrapper
|
||||||
isDrawerOpen={Boolean(this.state.drawerOpen)}
|
isDrawerOpen={Boolean(this.state.drawerOpen)}
|
||||||
|
@ -277,11 +279,13 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
slimInputButtonRef={slimInputButtonRef}
|
slimInputButtonRef={slimInputButtonRef}
|
||||||
tipModalOpen={tipModalOpen}
|
tipModalOpen={tipModalOpen}
|
||||||
>
|
>
|
||||||
{(!slimInput || this.state.drawerOpen) && (label || quickAction) && (
|
{(!slimInput || this.state.drawerOpen) && label && (
|
||||||
<div className="form-field__two-column">
|
<div className="form-field__two-column">
|
||||||
<label htmlFor={name}>{label}</label>
|
<Label {...labelProps} />
|
||||||
{quickAction}
|
|
||||||
{countInfo}
|
<QuickAction {...quickActionProps} />
|
||||||
|
|
||||||
|
<CountInfo {...countInfoProps} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
@ -328,94 +332,48 @@ export class FormField extends React.PureComponent<Props, State> {
|
||||||
)}
|
)}
|
||||||
</TextareaWrapper>
|
</TextareaWrapper>
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
|
</FormFieldWrapper>
|
||||||
);
|
);
|
||||||
default:
|
default:
|
||||||
const inputElement = <input type={type} id={name} {...inputProps} ref={this.input} />;
|
const inputElementProps = { type, name, ref: this.input, ...inputProps };
|
||||||
const inner = inputButton ? (
|
|
||||||
|
return (
|
||||||
|
<FormFieldWrapper {...wrapperProps}>
|
||||||
|
<fieldset-section>
|
||||||
|
{(label || errorMessage) && <Label {...labelProps} errorMessage={errorMessage} />}
|
||||||
|
|
||||||
|
{prefix && <label htmlFor={name}>{prefix}</label>}
|
||||||
|
|
||||||
|
{inputButton ? (
|
||||||
<input-submit>
|
<input-submit>
|
||||||
{inputElement}
|
<input {...inputElementProps} />
|
||||||
{inputButton}
|
{inputButton}
|
||||||
</input-submit>
|
</input-submit>
|
||||||
) : (
|
) : (
|
||||||
inputElement
|
<input {...inputElementProps} />
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<fieldset-section>
|
|
||||||
{(label || errorMessage) && (
|
|
||||||
<label htmlFor={name}>
|
|
||||||
{errorMessage ? <span className="error__text">{errorMessage}</span> : label}
|
|
||||||
</label>
|
|
||||||
)}
|
)}
|
||||||
{prefix && <label htmlFor={name}>{prefix}</label>}
|
|
||||||
{inner}
|
|
||||||
</fieldset-section>
|
</fieldset-section>
|
||||||
|
</FormFieldWrapper>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{type && input()}
|
|
||||||
{helper && <div className="form-field__help">{helper}</div>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default FormField;
|
export default FormField;
|
||||||
|
|
||||||
type TextareaWrapperProps = {
|
type WrapperProps = {
|
||||||
slimInput?: boolean,
|
type?: string,
|
||||||
slimInputButtonRef?: any,
|
children?: any,
|
||||||
children: Node,
|
helper?: any,
|
||||||
isDrawerOpen: boolean,
|
|
||||||
showSelectors?: boolean,
|
|
||||||
commentSelectorsProps?: any,
|
|
||||||
tipModalOpen?: boolean,
|
|
||||||
toggleDrawer: () => void,
|
|
||||||
closeSelector?: () => void,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function TextareaWrapper(wrapperProps: TextareaWrapperProps) {
|
const FormFieldWrapper = (wrapperProps: WrapperProps) => {
|
||||||
const {
|
const { type, children, helper } = wrapperProps;
|
||||||
children,
|
|
||||||
slimInput,
|
|
||||||
slimInputButtonRef,
|
|
||||||
isDrawerOpen,
|
|
||||||
commentSelectorsProps,
|
|
||||||
showSelectors,
|
|
||||||
tipModalOpen,
|
|
||||||
toggleDrawer,
|
|
||||||
closeSelector,
|
|
||||||
} = wrapperProps;
|
|
||||||
|
|
||||||
function handleCloseAll() {
|
return (
|
||||||
toggleDrawer();
|
<>
|
||||||
if (closeSelector) closeSelector();
|
{type && children}
|
||||||
}
|
{helper && <div className="form-field__help">{helper}</div>}
|
||||||
|
</>
|
||||||
return slimInput ? (
|
|
||||||
!isDrawerOpen ? (
|
|
||||||
<div ref={slimInputButtonRef} role="button" onClick={toggleDrawer}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Drawer
|
|
||||||
className="comment-create--drawer"
|
|
||||||
anchor="bottom"
|
|
||||||
open
|
|
||||||
onClose={handleCloseAll}
|
|
||||||
// The Modal tries to enforce focus when open and doesn't allow clicking or changing any
|
|
||||||
// other input boxes, so in this case it is disabled when trying to type in a custom tip
|
|
||||||
ModalProps={{ disableEnforceFocus: tipModalOpen }}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
|
|
||||||
{showSelectors && <CommentSelectors closeSelector={closeSelector} {...commentSelectorsProps} />}
|
|
||||||
</Drawer>
|
|
||||||
)
|
|
||||||
) : (
|
|
||||||
<>{children}</>
|
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
25
ui/component/common/form-components/input-select.jsx
Normal file
25
ui/component/common/form-components/input-select.jsx
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Label } from './common';
|
||||||
|
|
||||||
|
type InputSelectProps = {
|
||||||
|
name: string,
|
||||||
|
className?: string,
|
||||||
|
label?: any,
|
||||||
|
errorMessage?: any,
|
||||||
|
children?: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InputSelect = (inputSelectProps: InputSelectProps) => {
|
||||||
|
const { name, className, errorMessage, label, children, ...inputProps } = inputSelectProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<fieldset-section class={className || ''}>
|
||||||
|
{(label || errorMessage) && <Label name={name} label={label} errorMessage={errorMessage} />}
|
||||||
|
|
||||||
|
<select id={name} {...inputProps}>
|
||||||
|
{children}
|
||||||
|
</select>
|
||||||
|
</fieldset-section>
|
||||||
|
);
|
||||||
|
};
|
35
ui/component/common/form-components/input-simple.jsx
Normal file
35
ui/component/common/form-components/input-simple.jsx
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
// @flow
|
||||||
|
import * as React from 'react';
|
||||||
|
import { Label } from './common';
|
||||||
|
|
||||||
|
type InputSimpleProps = {
|
||||||
|
name: string,
|
||||||
|
type: string,
|
||||||
|
label?: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InputSimple = (inputSimpleProps: InputSimpleProps) => {
|
||||||
|
const { name, type, label, ...inputProps } = inputSimpleProps;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input id={name} type={type} {...inputProps} />
|
||||||
|
<Label name={name} label={label} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
type BlockWrapProps = {
|
||||||
|
blockWrap: boolean,
|
||||||
|
children?: any,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BlockWrapWrapper = (blockWrapProps: BlockWrapProps) => {
|
||||||
|
const { blockWrap, children } = blockWrapProps;
|
||||||
|
|
||||||
|
return blockWrap ? (
|
||||||
|
<fieldset-section class="radio">{children}</fieldset-section>
|
||||||
|
) : (
|
||||||
|
<span className="radio">{children}</span>
|
||||||
|
);
|
||||||
|
};
|
59
ui/component/common/form-components/slim-input-field.jsx
Normal file
59
ui/component/common/form-components/slim-input-field.jsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
// @flow
|
||||||
|
import React from 'react';
|
||||||
|
import Drawer from '@mui/material/Drawer';
|
||||||
|
import CommentSelectors from 'component/commentCreate/comment-selectors';
|
||||||
|
|
||||||
|
type TextareaWrapperProps = {
|
||||||
|
slimInput?: boolean,
|
||||||
|
slimInputButtonRef?: any,
|
||||||
|
children: any,
|
||||||
|
isDrawerOpen: boolean,
|
||||||
|
showSelectors?: boolean,
|
||||||
|
commentSelectorsProps?: any,
|
||||||
|
tipModalOpen?: boolean,
|
||||||
|
toggleDrawer: () => void,
|
||||||
|
closeSelector?: () => void,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TextareaWrapper = (wrapperProps: TextareaWrapperProps) => {
|
||||||
|
const {
|
||||||
|
children,
|
||||||
|
slimInput,
|
||||||
|
slimInputButtonRef,
|
||||||
|
isDrawerOpen,
|
||||||
|
commentSelectorsProps,
|
||||||
|
showSelectors,
|
||||||
|
tipModalOpen,
|
||||||
|
toggleDrawer,
|
||||||
|
closeSelector,
|
||||||
|
} = wrapperProps;
|
||||||
|
|
||||||
|
function handleCloseAll() {
|
||||||
|
toggleDrawer();
|
||||||
|
if (closeSelector) closeSelector();
|
||||||
|
}
|
||||||
|
|
||||||
|
return slimInput ? (
|
||||||
|
!isDrawerOpen ? (
|
||||||
|
<div ref={slimInputButtonRef} role="button" onClick={toggleDrawer}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Drawer
|
||||||
|
className="comment-create--drawer"
|
||||||
|
anchor="bottom"
|
||||||
|
open
|
||||||
|
onClose={handleCloseAll}
|
||||||
|
// The Modal tries to enforce focus when open and doesn't allow clicking or changing any
|
||||||
|
// other input boxes, so in this case it is disabled when trying to type in a custom tip
|
||||||
|
ModalProps={{ disableEnforceFocus: tipModalOpen }}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
|
||||||
|
{showSelectors && <CommentSelectors closeSelector={closeSelector} {...commentSelectorsProps} />}
|
||||||
|
</Drawer>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
children
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue