Update styles to match mui form
[arvados-workbench2.git] / src / components / chips / chips.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
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> {
11     values: Value[];
12     getLabel?: (value: Value) => string;
13     filler?: React.ReactNode;
14     onChange: (value: Value[]) => void;
15 }
16
17 type CssRules = 'root';
18
19 const styles: StyleRulesCallback<CssRules> = ({ spacing }) => ({
20     root: {
21         margin: `0px -${spacing.unit / 2}px`,
22     },
23 });
24 export const Chips = withStyles(styles)(
25     class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
26         render() {
27             const { values, filler } = this.props;
28             return <Grid container spacing={8} className={this.props.classes.root}>
29                 {values.map(this.renderChip)}
30                 {filler && <Grid item xs>{filler}</Grid>}
31             </Grid>;
32         }
33
34         renderChip = (value: Value, index: number) =>
35             <Grid item key={index}>
36                 <this.chip {...{ value }} />
37             </Grid>
38
39         type = 'chip';
40
41         dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
42             beginDrag: ({ value }) => ({ value }),
43             endDrag: ({ value: dragValue }, monitor) => {
44                 const result = monitor.getDropResult();
45                 if (result) {
46                     const { value: dropValue } = monitor.getDropResult();
47                     const dragIndex = this.props.values.indexOf(dragValue);
48                     const dropIndex = this.props.values.indexOf(dropValue);
49                     const newValues = this.props.values.slice(0);
50                     if (dragIndex < dropIndex) {
51                         newValues.splice(dragIndex, 1);
52                         newValues.splice(dropIndex - 1 || 0, 0, dragValue);
53                     } else if (dragIndex > dropIndex) {
54                         newValues.splice(dragIndex, 1);
55                         newValues.splice(dropIndex, 0, dragValue);
56                     }
57                     this.props.onChange(newValues);
58                 }
59             }
60         };
61
62         dragCollector: DragSourceCollector<{}> = connect => ({
63             connectDragSource: connect.dragSource(),
64         })
65
66         dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
67             drop: ({ value }) => ({ value }),
68         };
69
70         dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
71             connectDropTarget: connect.dropTarget(),
72             isOver: monitor.isOver(),
73         })
74         chip = compose(
75             DragSource(this.type, this.dragSpec, this.dragCollector),
76             DropTarget(this.type, this.dropSpec, this.dropCollector),
77         )(
78             ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) =>
79                 compose(
80                     connectDragSource,
81                     connectDropTarget,
82                 )(
83                     <span>
84                         <Chip
85                             color={isOver ? 'primary' : 'default'}
86                             onDelete={this.deleteValue(value)}
87                             label={this.props.getLabel ?
88                                 this.props.getLabel(value)
89                                 : typeof value === 'object'
90                                     ? JSON.stringify(value)
91                                     : value} />
92                     </span>
93                 )
94         );
95
96         deleteValue = (value: Value) => () => {
97             const { values } = this.props;
98             const index = values.indexOf(value);
99             const newValues = values.slice(0);
100             newValues.splice(index, 1);
101             this.props.onChange(newValues);
102         }
103     });
104
105 interface CollectedProps {
106     connectDragSource: ConnectDragSource;
107     connectDropTarget: ConnectDropTarget;
108
109     isOver: boolean;
110 }
111
112 interface DraggableChipProps<Value> {
113     value: Value;
114 }