1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React from 'react';
6 import { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { Chip, Grid } from '@mui/material';
8 import withStyles from '@mui/styles/withStyles';
19 import { compose } from 'lodash/fp';
20 import { WithStyles } from '@mui/styles';
21 interface ChipsProps<Value> {
23 getLabel?: (value: Value) => string;
24 filler?: React.ReactNode;
27 onChange: (value: Value[]) => void;
31 type CssRules = 'root';
33 const styles: CustomStyleRulesCallback<CssRules> = ({ spacing }) => ({
35 margin: `0px ${spacing(0.5)}`,
38 export const Chips = withStyles(styles)(
39 class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
41 const { values, filler } = this.props;
42 return <Grid container spacing={1} className={this.props.classes.root}>
43 {values && values.map(this.renderChip)}
44 {filler && <Grid item xs>{filler}</Grid>}
48 renderChip = (value: Value, index: number) => {
49 const { deletable, getLabel } = this.props;
50 return <Grid item key={index}>
51 <Chip onDelete={deletable ? this.deleteValue(value) : undefined}
52 label={getLabel !== undefined ? getLabel(value) : value} />
58 dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
59 beginDrag: ({ value }) => ({ value }),
60 endDrag: ({ value: dragValue }, monitor) => {
61 const result = monitor.getDropResult();
63 const { value: dropValue } = monitor.getDropResult();
64 const dragIndex = this.props.values.indexOf(dragValue);
65 const dropIndex = this.props.values.indexOf(dropValue);
66 const newValues = this.props.values.slice(0);
67 if (dragIndex < dropIndex) {
68 newValues.splice(dragIndex, 1);
69 newValues.splice(dropIndex - 1 || 0, 0, dragValue);
70 } else if (dragIndex > dropIndex) {
71 newValues.splice(dragIndex, 1);
72 newValues.splice(dropIndex, 0, dragValue);
74 this.props.onChange(newValues);
79 dragCollector: DragSourceCollector<{}> = connect => ({
80 connectDragSource: connect.dragSource(),
83 dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
84 drop: ({ value }) => ({ value }),
87 dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
88 connectDropTarget: connect.dropTarget(),
89 isOver: monitor.isOver(),
92 DragSource(this.type, this.dragSpec, this.dragCollector),
93 DropTarget(this.type, this.dropSpec, this.dropCollector),
95 ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) => {
96 const connect = compose(
104 color={isOver ? 'primary' : 'default'}
105 onDelete={this.props.deletable
106 ? this.deleteValue(value)
108 clickable={this.props.clickable}
109 label={this.props.getLabel ?
110 this.props.getLabel(value)
111 : typeof value === 'object'
112 ? JSON.stringify(value)
116 return this.props.orderable
122 deleteValue = (value: Value) => () => {
123 const { values } = this.props;
124 const index = values.indexOf(value);
125 const newValues = values.slice(0);
126 newValues.splice(index, 1);
127 this.props.onChange(newValues);
131 interface CollectedProps {
132 connectDragSource: ConnectDragSource;
133 connectDropTarget: ConnectDropTarget;
138 interface DraggableChipProps<Value> {