1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React from 'react';
6 import { Chip, Grid, StyleRulesCallback, withStyles } from '@material-ui/core';
17 import { compose } from 'lodash/fp';
18 import { WithStyles } from '@material-ui/core/styles';
19 interface ChipsProps<Value> {
21 getLabel?: (value: Value) => string;
22 filler?: React.ReactNode;
25 onChange: (value: Value[]) => void;
29 type CssRules = 'root';
31 const styles: StyleRulesCallback<CssRules> = ({ spacing }) => ({
33 margin: `0px -${spacing.unit / 2}px`,
36 export const Chips = withStyles(styles)(
37 class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
39 const { values, filler } = this.props;
40 return <Grid container spacing={8} className={this.props.classes.root}>
41 {values && values.map(this.renderChip)}
42 {filler && <Grid item xs>{filler}</Grid>}
46 renderChip = (value: Value, index: number) => {
47 const { deletable, getLabel } = this.props;
48 return <Grid item key={index}>
49 <Chip onDelete={deletable ? this.deleteValue(value) : undefined}
50 label={getLabel !== undefined ? getLabel(value) : value} />
56 dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
57 beginDrag: ({ value }) => ({ value }),
58 endDrag: ({ value: dragValue }, monitor) => {
59 const result = monitor.getDropResult();
61 const { value: dropValue } = monitor.getDropResult();
62 const dragIndex = this.props.values.indexOf(dragValue);
63 const dropIndex = this.props.values.indexOf(dropValue);
64 const newValues = this.props.values.slice(0);
65 if (dragIndex < dropIndex) {
66 newValues.splice(dragIndex, 1);
67 newValues.splice(dropIndex - 1 || 0, 0, dragValue);
68 } else if (dragIndex > dropIndex) {
69 newValues.splice(dragIndex, 1);
70 newValues.splice(dropIndex, 0, dragValue);
72 this.props.onChange(newValues);
77 dragCollector: DragSourceCollector<{}> = connect => ({
78 connectDragSource: connect.dragSource(),
81 dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
82 drop: ({ value }) => ({ value }),
85 dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
86 connectDropTarget: connect.dropTarget(),
87 isOver: monitor.isOver(),
90 DragSource(this.type, this.dragSpec, this.dragCollector),
91 DropTarget(this.type, this.dropSpec, this.dropCollector),
93 ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) => {
94 const connect = compose(
102 color={isOver ? 'primary' : 'default'}
103 onDelete={this.props.deletable
104 ? this.deleteValue(value)
106 clickable={this.props.clickable}
107 label={this.props.getLabel ?
108 this.props.getLabel(value)
109 : typeof value === 'object'
110 ? JSON.stringify(value)
114 return this.props.orderable
120 deleteValue = (value: Value) => () => {
121 const { values } = this.props;
122 const index = values.indexOf(value);
123 const newValues = values.slice(0);
124 newValues.splice(index, 1);
125 this.props.onChange(newValues);
129 interface CollectedProps {
130 connectDragSource: ConnectDragSource;
131 connectDropTarget: ConnectDropTarget;
136 interface DraggableChipProps<Value> {