Skip to content

Commit

Permalink
Merge pull request #1870 from Inist-CNRS/feat/dnd-in-field-composed-of
Browse files Browse the repository at this point in the history
Feat: add dnd support in composed field
  • Loading branch information
slax57 authored Jan 30, 2024
2 parents f97c93f + 1751c9c commit 08ef37e
Showing 1 changed file with 159 additions and 3 deletions.
162 changes: 159 additions & 3 deletions src/app/js/fields/FieldComposedOf.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Autocomplete,
TextField,
MenuItem,
Chip,
} from '@mui/material';
import translate from 'redux-polyglot/translate';
import compose from 'recompose/compose';
Expand All @@ -20,6 +21,149 @@ import {
} from '../propTypes';
import FieldRepresentation from './FieldRepresentation';
import { getFieldForSpecificScope } from '../../../common/scope';
import {
SortableContext,
arrayMove,
horizontalListSortingStrategy,
sortableKeyboardCoordinates,
useSortable,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import {
DndContext,
DragOverlay,
KeyboardSensor,
PointerSensor,
TouchSensor,
closestCenter,
useSensor,
useSensors,
} from '@dnd-kit/core';

const SortableItem = ({ option, onDelete, isActive }) => {
const {
attributes,
listeners,
setNodeRef,
transform,
isDragging,
} = useSortable({
id: option.name,
});
const style = {
transform: CSS.Transform.toString(transform),
transition: 'transform 0ms ease',
};

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
<Chip
label={<FieldRepresentation field={option} />}
onDelete={() => onDelete(option.name)}
sx={{
'&': {
cursor: 'grab',
boxShadow: isActive
? 'rgba(0, 0, 0, 0.35) 0px 5px 15px'
: 'none',
opacity: isDragging ? 0 : 1,
},
}}
/>
</div>
);
};

SortableItem.propTypes = {
option: fieldPropTypes.isRequired,
onDelete: PropTypes.func,
isActive: PropTypes.bool,
};

const SortableChips = ({ onChange, onDelete, options }) => {
const [activeId, setActiveId] = React.useState(null);
const sensors = useSensors(
useSensor(PointerSensor, {
activationConstraint: {
distance: 2,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
}),
useSensor(TouchSensor, {
activationConstraint: {
distance: 2,
},
}),
);

const handleDragEnd = event => {
const { active, over } = event;
if (active && over && active.id !== over.id) {
const oldIndex = options.findIndex(
option => option.name === active.id,
);
const newIndex = options.findIndex(
option => option.name === over.id,
);
const newFields = arrayMove(options, oldIndex, newIndex);
onChange({
isComposedOf: newFields.length > 0,
fields: newFields.map(field => field.name),
});
}
setActiveId(null);
};

const handleDragStart = ({ active }) => {
if (!active) {
return;
}
setActiveId(active.id);
};

const handleDragCancel = () => setActiveId(null);

return (
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
onDragCancel={handleDragCancel}
>
<SortableContext
items={options.map(option => option.name)}
strategy={horizontalListSortingStrategy}
>
{options.map(option => (
<SortableItem
key={option.name}
option={option}
onDelete={onDelete}
/>
))}
</SortableContext>
<DragOverlay>
{activeId ? (
<SortableItem
isActive={true}
option={options.find(
option => option.name === activeId,
)}
/>
) : null}
</DragOverlay>
</DndContext>
);
};

SortableChips.propTypes = {
options: PropTypes.arrayOf(fieldPropTypes).isRequired,
onDelete: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
};

const FieldComposedOf = ({
onChange,
Expand All @@ -33,6 +177,14 @@ const FieldComposedOf = ({
return fields.find(field => field.name === column);
});

const onDelete = name => {
const newFields = columns.filter(column => column !== name);
onChange({
isComposedOf: newFields.length > 0,
fields: newFields,
});
};

const handleChange = (event, value) => {
onChange({
isComposedOf: value.length > 0,
Expand All @@ -49,9 +201,6 @@ const FieldComposedOf = ({
multiple
fullWidth
options={getFieldForSpecificScope(fields, scope, subresourceId)}
getOptionLabel={option => (
<FieldRepresentation field={option} shortMode />
)}
value={autocompleteValue ?? []}
renderInput={params => (
<TextField
Expand All @@ -74,6 +223,13 @@ const FieldComposedOf = ({
</MenuItem>
)}
onChange={handleChange}
renderTags={props => (
<SortableChips
onChange={onChange}
onDelete={onDelete}
options={props}
/>
)}
/>
</Box>
);
Expand Down

0 comments on commit 08ef37e

Please sign in to comment.