= (props) => {
+ const intl = useIntl();
+ const [revealed, setRevealed] = useState(false);
+
+ const { hint, error, label, className, ...rest } = props;
+
+ const toggleReveal = () => {
+ if (props.onToggleVisibility) {
+ props.onToggleVisibility();
+ } else {
+ setRevealed(!revealed);
+ }
+ };
+
+ const revealButton = (
+
+ );
+
+ return (
+
+ {label ? (
+
+
+ {revealButton}
+
+ ) : (<>
+
+ {revealButton}
+ >)}
+
+ );
+};
+
+export default ShowablePassword;
diff --git a/app/soapbox/features/forms/index.js b/app/soapbox/features/forms/index.js
deleted file mode 100644
index 06bddefa9..000000000
--- a/app/soapbox/features/forms/index.js
+++ /dev/null
@@ -1,314 +0,0 @@
-import classNames from 'classnames';
-import PropTypes from 'prop-types';
-import React, { useState } from 'react';
-import ImmutablePureComponent from 'react-immutable-pure-component';
-import { FormattedMessage } from 'react-intl';
-import { v4 as uuidv4 } from 'uuid';
-
-import { Text, Select } from '../../components/ui';
-
-export const FormPropTypes = {
- label: PropTypes.oneOfType([
- PropTypes.string,
- PropTypes.object,
- PropTypes.node,
- ]),
-};
-
-export const InputContainer = (props) => {
- const containerClass = classNames('input', {
- 'with_label': props.label,
- 'required': props.required,
- 'boolean': props.type === 'checkbox',
- 'field_with_errors': props.error,
- }, props.extraClass);
-
- return (
-
- {props.children}
- {props.hint && {props.hint}}
-
- );
-};
-
-InputContainer.propTypes = {
- label: FormPropTypes.label,
- hint: PropTypes.node,
- required: PropTypes.bool,
- type: PropTypes.string,
- children: PropTypes.node,
- extraClass: PropTypes.string,
- error: PropTypes.bool,
-};
-
-export const LabelInputContainer = ({ label, hint, children, ...props }) => {
- const [id] = useState(uuidv4());
- const childrenWithProps = React.Children.map(children, child => (
- React.cloneElement(child, { id: id, key: id })
- ));
-
- return (
-
-
-
- {childrenWithProps}
-
- {hint &&
{hint}}
-
- );
-};
-
-LabelInputContainer.propTypes = {
- label: FormPropTypes.label.isRequired,
- hint: PropTypes.node,
- children: PropTypes.node,
-};
-
-export const LabelInput = ({ label, dispatch, ...props }) => (
-
-
-
-);
-
-LabelInput.propTypes = {
- label: FormPropTypes.label.isRequired,
- dispatch: PropTypes.func,
-};
-
-export const LabelTextarea = ({ label, dispatch, ...props }) => (
-
-
-
-);
-
-LabelTextarea.propTypes = {
- label: FormPropTypes.label.isRequired,
- dispatch: PropTypes.func,
-};
-
-export class SimpleInput extends ImmutablePureComponent {
-
- static propTypes = {
- label: FormPropTypes.label,
- hint: PropTypes.node,
- error: PropTypes.bool,
- }
-
- render() {
- const { hint, error, ...props } = this.props;
- const Input = this.props.label ? LabelInput : 'input';
-
- return (
-
-
-
- );
- }
-
-}
-
-export class SimpleTextarea extends ImmutablePureComponent {
-
- static propTypes = {
- label: FormPropTypes.label,
- hint: PropTypes.node,
- }
-
- render() {
- const { hint, ...props } = this.props;
- const Input = this.props.label ? LabelTextarea : 'textarea';
-
- return (
-
-
-
- );
- }
-
-}
-
-export class SimpleForm extends ImmutablePureComponent {
-
- static propTypes = {
- children: PropTypes.node,
- className: PropTypes.string,
- };
-
- static defaultProps = {
- acceptCharset: 'UTF-8',
- onSubmit: e => {},
- };
-
- onSubmit = e => {
- this.props.onSubmit(e);
- e.preventDefault();
- }
-
- render() {
- const { className, children, onSubmit, ...props } = this.props;
- return (
-
- );
- }
-
-}
-
-export const FieldsGroup = ({ children }) => (
- {children}
-);
-
-FieldsGroup.propTypes = {
- children: PropTypes.node,
-};
-
-export const Checkbox = props => (
-
-);
-
-export class RadioGroup extends ImmutablePureComponent {
-
- static propTypes = {
- label: FormPropTypes.label,
- children: PropTypes.node,
- }
-
- render() {
- const { label, children, onChange } = this.props;
-
- const childrenWithProps = React.Children.map(children, child =>
- React.cloneElement(child, { onChange }),
- );
-
- return (
-
- );
- }
-
-}
-
-export class RadioItem extends ImmutablePureComponent {
-
- static propTypes = {
- label: FormPropTypes.label,
- hint: PropTypes.node,
- value: PropTypes.string.isRequired,
- checked: PropTypes.bool.isRequired,
- onChange: PropTypes.func,
- dispatch: PropTypes.func,
- }
-
- static defaultProps = {
- checked: false,
- }
-
- state = {
- id: uuidv4(),
- }
-
- render() {
- const { label, hint, dispatch, ...props } = this.props;
- const { id } = this.state;
-
- return (
-
-
-
- );
- }
-
-}
-
-export class SelectDropdown extends ImmutablePureComponent {
-
- static propTypes = {
- label: FormPropTypes.label,
- hint: PropTypes.node,
- items: PropTypes.object.isRequired,
- }
-
- render() {
- const { label, hint, items, ...props } = this.props;
-
- const optionElems = Object.keys(items).map(item => (
-
- ));
-
- const selectElem = ;
-
- return label ? (
- {selectElem}
- ) : selectElem;
- }
-
-}
-
-export const TextInput = props => (
-
-);
-
-export const FileChooser = props => (
-
-);
-
-FileChooser.defaultProps = {
- accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
-};
-
-export const FileChooserLogo = props => (
-
-);
-
-FileChooserLogo.defaultProps = {
- accept: ['image/svg', 'image/png'],
-};
-
-
-export class CopyableInput extends ImmutablePureComponent {
-
- static propTypes = {
- value: PropTypes.string,
- }
-
- setInputRef = c => {
- this.input = c;
- }
-
- handleCopyClick = e => {
- if (!this.input) return;
-
- this.input.select();
- this.input.setSelectionRange(0, 99999);
-
- document.execCommand('copy');
- }
-
- render() {
- const { value } = this.props;
-
- return (
-
-
-
-
- );
- }
-
-}
diff --git a/app/soapbox/features/forms/index.tsx b/app/soapbox/features/forms/index.tsx
new file mode 100644
index 000000000..3e778860f
--- /dev/null
+++ b/app/soapbox/features/forms/index.tsx
@@ -0,0 +1,264 @@
+import classNames from 'classnames';
+import React, { useState, useRef } from 'react';
+import { FormattedMessage } from 'react-intl';
+import { v4 as uuidv4 } from 'uuid';
+
+import { Text, Select } from '../../components/ui';
+
+interface IInputContainer {
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+ required?: boolean,
+ type?: string,
+ extraClass?: string,
+ error?: boolean,
+}
+
+export const InputContainer: React.FC = (props) => {
+ const containerClass = classNames('input', {
+ 'with_label': props.label,
+ 'required': props.required,
+ 'boolean': props.type === 'checkbox',
+ 'field_with_errors': props.error,
+ }, props.extraClass);
+
+ return (
+
+ {props.children}
+ {props.hint && {props.hint}}
+
+ );
+};
+
+interface ILabelInputContainer {
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+}
+
+export const LabelInputContainer: React.FC = ({ label, hint, children }) => {
+ const [id] = useState(uuidv4());
+ const childrenWithProps = React.Children.map(children, child => (
+ // @ts-ignore: not sure how to get the right type here
+ React.cloneElement(child, { id: id, key: id })
+ ));
+
+ return (
+
+
+
+ {childrenWithProps}
+
+ {hint &&
{hint}}
+
+ );
+};
+
+interface ILabelInput {
+ label?: React.ReactNode,
+}
+
+export const LabelInput: React.FC = ({ label, ...props }) => (
+
+
+
+);
+
+interface ILabelTextarea {
+ label?: React.ReactNode,
+}
+
+export const LabelTextarea: React.FC = ({ label, ...props }) => (
+
+
+
+);
+
+interface ISimpleInput {
+ type: string,
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+ error?: boolean,
+}
+
+export const SimpleInput: React.FC = (props) => {
+ const { hint, label, error, ...rest } = props;
+ const Input = label ? LabelInput : 'input';
+
+ return (
+
+
+
+ );
+};
+
+interface ISimpleTextarea {
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+}
+
+export const SimpleTextarea: React.FC = (props) => {
+ const { hint, label, ...rest } = props;
+ const Input = label ? LabelTextarea : 'textarea';
+
+ return (
+
+
+
+ );
+};
+
+interface ISimpleForm {
+ className?: string,
+ onSubmit?: React.FormEventHandler,
+ acceptCharset?: string,
+}
+
+export const SimpleForm: React.FC = (props) => {
+ const {
+ className,
+ children,
+ onSubmit = () => {},
+ acceptCharset = 'UTF-8',
+ ...rest
+ } = props;
+
+ const handleSubmit: React.FormEventHandler = e => {
+ onSubmit(e);
+ e.preventDefault();
+ };
+
+ return (
+
+ );
+};
+
+export const FieldsGroup: React.FC = ({ children }) => (
+ {children}
+);
+
+export const Checkbox: React.FC = (props) => (
+
+);
+
+interface IRadioGroup {
+ label?: React.ReactNode,
+ onChange?: React.ChangeEventHandler,
+}
+
+export const RadioGroup: React.FC = (props) => {
+ const { label, children, onChange } = props;
+
+ const childrenWithProps = React.Children.map(children, child =>
+ // @ts-ignore
+ React.cloneElement(child, { onChange }),
+ );
+
+ return (
+
+ );
+};
+
+interface IRadioItem {
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+ value: string,
+ checked: boolean,
+ onChange?: React.ChangeEventHandler,
+}
+
+export const RadioItem: React.FC = (props) => {
+ const { current: id } = useRef(uuidv4());
+ const { label, hint, checked = false, ...rest } = props;
+
+ return (
+
+
+
+ );
+};
+
+interface ISelectDropdown {
+ label?: React.ReactNode,
+ hint?: React.ReactNode,
+ items: Record,
+ defaultValue?: string,
+ onChange?: React.ChangeEventHandler,
+}
+
+export const SelectDropdown: React.FC = (props) => {
+ const { label, hint, items, ...rest } = props;
+
+ const optionElems = Object.keys(items).map(item => (
+
+ ));
+
+ // @ts-ignore
+ const selectElem = ;
+
+ return label ? (
+ {selectElem}
+ ) : selectElem;
+};
+
+export const TextInput: React.FC = props => (
+
+);
+
+export const FileChooser : React.FC = (props) => (
+
+);
+
+FileChooser.defaultProps = {
+ accept: ['image/jpeg', 'image/png', 'image/gif', 'image/webp'],
+};
+
+export const FileChooserLogo: React.FC = props => (
+
+);
+
+FileChooserLogo.defaultProps = {
+ accept: ['image/svg', 'image/png'],
+};
+
+interface ICopyableInput {
+ value: string,
+}
+
+export const CopyableInput: React.FC = ({ value }) => {
+ const node = useRef(null);
+
+ const handleCopyClick: React.MouseEventHandler = () => {
+ if (!node.current) return;
+
+ node.current.select();
+ node.current.setSelectionRange(0, 99999);
+
+ document.execCommand('copy');
+ };
+
+ return (
+
+
+
+
+ );
+};
diff --git a/app/soapbox/features/preferences/index.tsx b/app/soapbox/features/preferences/index.tsx
index 54d8e4171..2574b01af 100644
--- a/app/soapbox/features/preferences/index.tsx
+++ b/app/soapbox/features/preferences/index.tsx
@@ -134,7 +134,7 @@ const Preferences = () => {
}>
) => onSelectChange(event, ['locale'])}
/>
@@ -142,7 +142,7 @@ const Preferences = () => {
}>
) => onSelectChange(event, ['displayMedia'])}
/>
diff --git a/app/soapbox/features/soapbox_config/index.js b/app/soapbox/features/soapbox_config/index.js
index 16cff42f3..8c5a8816c 100644
--- a/app/soapbox/features/soapbox_config/index.js
+++ b/app/soapbox/features/soapbox_config/index.js
@@ -21,7 +21,6 @@ import {
SimpleInput,
SimpleTextarea,
FileChooserLogo,
- FormPropTypes,
Checkbox,
} from 'soapbox/features/forms';
import ThemeToggle from 'soapbox/features/ui/components/theme_toggle';
@@ -509,7 +508,7 @@ class ColorWithPicker extends ImmutablePureComponent {
static propTypes = {
buttonId: PropTypes.string.isRequired,
- label: FormPropTypes.label,
+ label: PropTypes.node,
value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
}
@@ -559,7 +558,7 @@ export class IconPicker extends ImmutablePureComponent {
static propTypes = {
icons: PropTypes.object,
- label: FormPropTypes.label,
+ label: PropTypes.node,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
}