1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import * as React from 'react';
6 import { Chip, Grid } from '@material-ui/core';
7 import { DragSource, DragSourceSpec, DragSourceCollector, ConnectDragSource, DropTarget, DropTargetSpec, DropTargetCollector, ConnectDropTarget } from 'react-dnd';
8 import { compose } from 'lodash/fp';
9 interface ChipsProps<Value> {
11 getLabel?: (value: Value) => string;
12 filler?: React.ReactNode;
13 onChange: (value: Value[]) => void;
15 export class Chips<Value> extends React.Component<ChipsProps<Value>> {
17 const { values, filler } = this.props;
18 return <Grid container spacing={8}>
19 {values.map(this.renderChip)}
20 {filler && <Grid item xs>{filler}</Grid>}
24 renderChip = (value: Value, index: number) =>
25 <Grid item key={index}>
26 <this.chip {...{ value }} />
31 dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
32 beginDrag: ({ value }) => ({ value }),
33 endDrag: ({ value: dragValue }, monitor) => {
34 const result = monitor.getDropResult();
36 const { value: dropValue } = monitor.getDropResult();
37 const dragIndex = this.props.values.indexOf(dragValue);
38 const dropIndex = this.props.values.indexOf(dropValue);
39 const newValues = this.props.values.slice(0);
40 if (dragIndex < dropIndex) {
41 newValues.splice(dragIndex, 1);
42 newValues.splice(dropIndex - 1 || 0, 0, dragValue);
43 } else if (dragIndex > dropIndex) {
44 newValues.splice(dragIndex, 1);
45 newValues.splice(dropIndex, 0, dragValue);
47 this.props.onChange(newValues);
52 dragCollector: DragSourceCollector<{}> = connect => ({
53 connectDragSource: connect.dragSource(),
56 dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
57 drop: ({ value }) => ({ value }),
60 dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
61 connectDropTarget: connect.dropTarget(),
62 isOver: monitor.isOver(),
65 DragSource(this.type, this.dragSpec, this.dragCollector),
66 DropTarget(this.type, this.dropSpec, this.dropCollector),
68 ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) =>
75 color={isOver ? 'primary' : 'default'}
76 onDelete={this.deleteValue(value)}
77 label={this.props.getLabel ?
78 this.props.getLabel(value)
79 : typeof value === 'object'
80 ? JSON.stringify(value)
86 deleteValue = (value: Value) => () => {
87 const { values } = this.props;
88 const index = values.indexOf(value);
89 const newValues = values.slice(0);
90 newValues.splice(index, 1);
91 this.props.onChange(newValues);
95 interface CollectedProps {
96 connectDragSource: ConnectDragSource;
97 connectDropTarget: ConnectDropTarget;
102 interface DraggableChipProps<Value> {