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, StyleRulesCallback, withStyles } from '@material-ui/core';
7 import { DragSource, DragSourceSpec, DragSourceCollector, ConnectDragSource, DropTarget, DropTargetSpec, DropTargetCollector, ConnectDropTarget } from 'react-dnd';
8 import { compose } from 'lodash/fp';
9 import { WithStyles } from '@material-ui/core/styles';
10 interface ChipsProps<Value> {
12 getLabel?: (value: Value) => string;
13 filler?: React.ReactNode;
16 onChange: (value: Value[]) => void;
20 type CssRules = 'root';
22 const styles: StyleRulesCallback<CssRules> = ({ spacing }) => ({
24 margin: `0px -${spacing.unit / 2}px`,
27 export const Chips = withStyles(styles)(
28 class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
30 const { values, filler } = this.props;
31 return <Grid container spacing={8} className={this.props.classes.root}>
32 {values.map(this.renderChip)}
33 {filler && <Grid item xs>{filler}</Grid>}
37 renderChip = (value: Value, index: number) =>
38 <Grid item key={index}>
39 <this.chip {...{ value }} />
44 dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
45 beginDrag: ({ value }) => ({ value }),
46 endDrag: ({ value: dragValue }, monitor) => {
47 const result = monitor.getDropResult();
49 const { value: dropValue } = monitor.getDropResult();
50 const dragIndex = this.props.values.indexOf(dragValue);
51 const dropIndex = this.props.values.indexOf(dropValue);
52 const newValues = this.props.values.slice(0);
53 if (dragIndex < dropIndex) {
54 newValues.splice(dragIndex, 1);
55 newValues.splice(dropIndex - 1 || 0, 0, dragValue);
56 } else if (dragIndex > dropIndex) {
57 newValues.splice(dragIndex, 1);
58 newValues.splice(dropIndex, 0, dragValue);
60 this.props.onChange(newValues);
65 dragCollector: DragSourceCollector<{}> = connect => ({
66 connectDragSource: connect.dragSource(),
69 dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
70 drop: ({ value }) => ({ value }),
73 dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
74 connectDropTarget: connect.dropTarget(),
75 isOver: monitor.isOver(),
78 DragSource(this.type, this.dragSpec, this.dragCollector),
79 DropTarget(this.type, this.dropSpec, this.dropCollector),
81 ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) => {
82 const connect = compose(
90 color={isOver ? 'primary' : 'default'}
91 onDelete={this.props.deletable
92 ? this.deleteValue(value)
94 clickable={this.props.clickable}
95 label={this.props.getLabel ?
96 this.props.getLabel(value)
97 : typeof value === 'object'
98 ? JSON.stringify(value)
102 return this.props.orderable
108 deleteValue = (value: Value) => () => {
109 const { values } = this.props;
110 const index = values.indexOf(value);
111 const newValues = values.slice(0);
112 newValues.splice(index, 1);
113 this.props.onChange(newValues);
117 interface CollectedProps {
118 connectDragSource: ConnectDragSource;
119 connectDropTarget: ConnectDropTarget;
124 interface DraggableChipProps<Value> {