Add options to radio custom field

[MAILPOET-2453]
This commit is contained in:
Pavel Dohnal
2019-12-17 16:43:39 +01:00
committed by Rostislav Wolný
parent 655ab85937
commit 9b755dc397
6 changed files with 257 additions and 5 deletions

View File

@@ -6,20 +6,40 @@ import {
import PropTypes from 'prop-types';
import MailPoet from 'mailpoet';
import SettingsPreview from './settings_preview.jsx';
const CustomFieldSettings = ({
mandatory,
values,
isSaving,
onSave,
}) => {
const [localMandatory, setLocalMandatory] = useState(mandatory);
const [localValues, setLocalValues] = useState(values);
const update = (value) => {
setLocalValues(localValues.map((valueInSelection) => {
if (value.id !== valueInSelection.id) {
return valueInSelection;
}
return value;
}));
};
const remove = (valueId) => {
setLocalValues(
localValues.filter((value) => valueId !== value.id)
);
};
return (
<>
<div className="custom-field-settings">
<Button
isPrimary
isDefault
onClick={() => onSave({
mandatory: localMandatory,
values: localValues,
})}
isBusy={isSaving}
disabled={isSaving}
@@ -27,17 +47,40 @@ const CustomFieldSettings = ({
>
{MailPoet.I18n.t('customFieldSaveCTA')}
</Button>
<SettingsPreview
remove={remove}
update={update}
values={localValues}
onReorder={setLocalValues}
/>
<Button
isLink
onClick={() => setLocalValues([
...localValues,
{
id: `${Math.random() * 1000}-${Date.now()}`,
name: `Option ${localValues.length + 1}`,
},
])}
className="button-on-top"
>
{MailPoet.I18n.t('customFieldAddItem')}
</Button>
<ToggleControl
label={MailPoet.I18n.t('blockMandatory')}
checked={localMandatory}
onChange={setLocalMandatory}
/>
</>
</div>
);
};
CustomFieldSettings.propTypes = {
mandatory: PropTypes.bool,
values: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
})),
onSave: PropTypes.func.isRequired,
isSaving: PropTypes.bool,
};
@@ -45,6 +88,7 @@ CustomFieldSettings.propTypes = {
CustomFieldSettings.defaultProps = {
mandatory: false,
isSaving: false,
values: [],
};
export default CustomFieldSettings;

View File

@@ -3,6 +3,7 @@ import {
Panel,
PanelBody,
TextControl,
ToggleControl,
} from '@wordpress/components';
import { InspectorControls } from '@wordpress/block-editor';
import PropTypes from 'prop-types';
@@ -24,16 +25,19 @@ const CustomRadioEdit = ({ attributes, setAttributes }) => {
<PanelBody title={MailPoet.I18n.t('customFieldSettings')} initialOpen>
<CustomFieldSettings
mandatory={attributes.mandatory}
values={attributes.values}
isSaving={isSaving}
onSave={(params) => saveCustomField({
customFieldId: attributes.customFieldId,
data: {
params: {
required: params.mandatory ? '1' : undefined,
values: params.values.map((value) => ({ value: value.name })),
},
},
onFinish: () => setAttributes({
mandatory: params.mandatory,
values: params.values,
}),
})}
/>
@@ -69,6 +73,17 @@ const CustomRadioEdit = ({ attributes, setAttributes }) => {
<>
{inspectorControls}
{getLabel()}
{Array.isArray(attributes.values) && attributes.values.map((value) => (
<div key={value.id}>
<label>
<input
type="radio"
disabled
/>
{value.name}
</label>
</div>
))}
</>
);
};
@@ -76,8 +91,12 @@ const CustomRadioEdit = ({ attributes, setAttributes }) => {
CustomRadioEdit.propTypes = {
attributes: PropTypes.shape({
label: PropTypes.string.isRequired,
labelWithinInput: PropTypes.bool.isRequired,
values: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
})),
mandatory: PropTypes.bool.isRequired,
hideLabel: PropTypes.bool,
}).isRequired,
setAttributes: PropTypes.func.isRequired,
};

View File

@@ -0,0 +1,172 @@
import React, {
useRef,
useCallback,
useState,
useEffect,
} from 'react';
import PropTypes from 'prop-types';
import { Dashicon } from '@wordpress/components';
import { partial } from 'lodash';
import { DndProvider, useDrag, useDrop } from 'react-dnd';
import Backend from 'react-dnd-html5-backend';
const PreviewItem = ({
value,
index,
moveItem,
remove,
onUpdate,
dragFinished,
}) => {
const ref = useRef(null);
const [, drop] = useDrop({
accept: 'item',
hover(item, monitor) {
if (!ref.current) {
return;
}
const dragIndex = item.index;
const hoverIndex = index;
// Don't replace items with themselves
if (dragIndex === hoverIndex) {
return;
}
// Determine rectangle on screen
const hoverBoundingRect = ref.current.getBoundingClientRect();
// Get vertical middle
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
// Determine mouse position
const clientOffset = monitor.getClientOffset();
// Get pixels to the top
const hoverClientY = clientOffset.y - hoverBoundingRect.top;
// Only perform the move when the mouse has crossed half of the items height
// When dragging downwards, only move when the cursor is below 50%
// When dragging upwards, only move when the cursor is above 50%
// Dragging downwards
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
return;
}
// Dragging upwards
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
return;
}
// Time to actually perform the action
moveItem(dragIndex, hoverIndex);
// Note: we're mutating the monitor item here!
// Generally it's better to avoid mutations,
// but it's good here for the sake of performance
// to avoid expensive index searches.
// eslint-disable-next-line no-param-reassign
item.index = hoverIndex;
},
});
const [{ isDragging }, drag] = useDrag({
item: { type: 'item', id: value.id, index },
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
end() {
dragFinished();
},
});
const opacity = isDragging ? 0.2 : 1;
drag(drop(ref));
return (
<div
className="mailpoet-form-segments-settings-list"
key={value.id}
ref={ref}
style={{ opacity }}
>
<input
type="radio"
disabled
key={`check-${value.id}`}
/>
<input
type="text"
value={value.name}
onChange={(event) => onUpdate(value.id, event.target.value)}
/>
<Dashicon
icon="no-alt"
color="#900"
className="mailpoet-form-segments-segment-remove"
onClick={partial(remove, value.id)}
/>
</div>
);
};
PreviewItem.propTypes = {
value: PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
}).isRequired,
onUpdate: PropTypes.func.isRequired,
moveItem: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
index: PropTypes.number.isRequired,
dragFinished: PropTypes.func.isRequired,
};
const Preview = ({
values,
update,
remove,
onReorder,
}) => {
const [valuesWhileMoved, setSegments] = useState(values);
const moveItem = useCallback(
(dragIndex, hoverIndex) => {
const result = Array.from(valuesWhileMoved);
const [removed] = result.splice(dragIndex, 1);
result.splice(hoverIndex, 0, removed);
setSegments(result);
},
[valuesWhileMoved, setSegments],
);
useEffect(() => {
setSegments(values);
}, [values]);
if (valuesWhileMoved.length === 0) {
return null;
}
const onUpdate = (valueId, text) => {
const value = valuesWhileMoved.find((v) => v.id === valueId);
value.name = text;
update(value);
};
return (
<DndProvider backend={Backend}>
{valuesWhileMoved.map((value, index) => (
<PreviewItem
key={value.id}
index={index}
value={value}
moveItem={moveItem}
remove={remove}
onUpdate={onUpdate}
dragFinished={() => onReorder(valuesWhileMoved)}
/>
))}
</DndProvider>
);
};
Preview.propTypes = {
values: PropTypes.arrayOf(PropTypes.shape({
name: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
}).isRequired).isRequired,
update: PropTypes.func.isRequired,
remove: PropTypes.func.isRequired,
onReorder: PropTypes.func.isRequired,
};
export default Preview;

View File

@@ -22,7 +22,10 @@ const mapCustomField = (item, customFields, mappedCommonProperties) => {
mapped.attributes.hideLabel = !!item.params.hide_label;
}
if (Object.prototype.hasOwnProperty.call(item.params, 'values') && Array.isArray(item.params.values)) {
mapped.attributes.values = item.params.values.map((value) => ({ name: value.value }));
mapped.attributes.values = item.params.values.map((value) => ({
name: value.value,
id: `${Math.random().toString()}-${Date.now()}`,
}));
}
}
return mapped;