Add options to radio custom field
[MAILPOET-2453]
This commit is contained in:
committed by
Rostislav Wolný
parent
655ab85937
commit
9b755dc397
@@ -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;
|
||||
|
@@ -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,
|
||||
};
|
||||
|
@@ -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;
|
@@ -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;
|
||||
|
Reference in New Issue
Block a user