// SPDX-License-Identifier: AGPL-3.0
import * as React from 'react';
-import { Chip, Grid } from '@material-ui/core';
-import { DragSource, DragSourceSpec, DragSourceCollector, ConnectDragSource, DragDropContextProvider, DropTarget, DropTargetSpec, DropTargetCollector, ConnectDropTarget } from 'react-dnd';
-import HTML5Backend from 'react-dnd-html5-backend';
+import { Chip, Grid, StyleRulesCallback, withStyles } from '@material-ui/core';
+import { DragSource, DragSourceSpec, DragSourceCollector, ConnectDragSource, DropTarget, DropTargetSpec, DropTargetCollector, ConnectDropTarget } from 'react-dnd';
import { compose } from 'lodash/fp';
-interface ChipsFieldProps<Value> {
+import { WithStyles } from '@material-ui/core/styles';
+interface ChipsProps<Value> {
values: Value[];
getLabel?: (value: Value) => string;
+ filler?: React.ReactNode;
+ deletable?: boolean;
+ orderable?: boolean;
onChange: (value: Value[]) => void;
+ clickable?: boolean;
}
-export class Chips<Value> extends React.Component<ChipsFieldProps<Value>> {
- render() {
- const { values } = this.props;
- return <DragDropContextProvider backend={HTML5Backend}>
- <Grid container spacing={8}>
+
+type CssRules = 'root';
+
+const styles: StyleRulesCallback<CssRules> = ({ spacing }) => ({
+ root: {
+ margin: `0px -${spacing.unit / 2}px`,
+ },
+});
+export const Chips = withStyles(styles)(
+ class Chips<Value> extends React.Component<ChipsProps<Value> & WithStyles<CssRules>> {
+ render() {
+ const { values, filler } = this.props;
+ return <Grid container spacing={8} className={this.props.classes.root}>
{values.map(this.renderChip)}
+ {filler && <Grid item xs>{filler}</Grid>}
+ </Grid>;
+ }
+
+ renderChip = (value: Value, index: number) =>
+ <Grid item key={index}>
+ <this.chip {...{ value }} />
</Grid>
- </DragDropContextProvider>;
- }
- renderChip = (value: Value, index: number) =>
- <Grid item key={index}>
- <this.chip {...{ value }} />
- </Grid>
+ type = 'chip';
- type = 'chip';
+ dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
+ beginDrag: ({ value }) => ({ value }),
+ endDrag: ({ value: dragValue }, monitor) => {
+ const result = monitor.getDropResult();
+ if (result) {
+ const { value: dropValue } = monitor.getDropResult();
+ const dragIndex = this.props.values.indexOf(dragValue);
+ const dropIndex = this.props.values.indexOf(dropValue);
+ const newValues = this.props.values.slice(0);
+ if (dragIndex < dropIndex) {
+ newValues.splice(dragIndex, 1);
+ newValues.splice(dropIndex - 1 || 0, 0, dragValue);
+ } else if (dragIndex > dropIndex) {
+ newValues.splice(dragIndex, 1);
+ newValues.splice(dropIndex, 0, dragValue);
+ }
+ this.props.onChange(newValues);
+ }
+ }
+ };
- dragSpec: DragSourceSpec<DraggableChipProps<Value>, { value: Value }> = {
- beginDrag: ({ value }) => ({ value }),
- endDrag: ({ value: dragValue }, monitor) => {
- const { value: dropValue } = monitor.getDropResult();
- const dragIndex = this.props.values.indexOf(dragValue);
- const dropIndex = this.props.values.indexOf(dropValue);
- const newValues = this.props.values.slice(0);
- newValues.splice(dragIndex, 1, dropValue);
- newValues.splice(dropIndex, 1, dragValue);
- this.props.onChange(newValues);
- }
- };
+ dragCollector: DragSourceCollector<{}> = connect => ({
+ connectDragSource: connect.dragSource(),
+ })
- dragCollector: DragSourceCollector<{}> = connect => ({
- connectDragSource: connect.dragSource(),
- })
+ dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
+ drop: ({ value }) => ({ value }),
+ };
- dropSpec: DropTargetSpec<DraggableChipProps<Value>> = {
- drop: ({ value }) => ({ value }),
- };
+ dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
+ connectDropTarget: connect.dropTarget(),
+ isOver: monitor.isOver(),
+ })
+ chip = compose(
+ DragSource(this.type, this.dragSpec, this.dragCollector),
+ DropTarget(this.type, this.dropSpec, this.dropCollector),
+ )(
+ ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) => {
+ const connect = compose(
+ connectDragSource,
+ connectDropTarget,
+ );
- dropCollector: DropTargetCollector<{}> = (connect, monitor) => ({
- connectDropTarget: connect.dropTarget(),
- isOver: monitor.isOver(),
- })
- chip = compose(
- DragSource(this.type, this.dragSpec, this.dragCollector),
- DropTarget(this.type, this.dropSpec, this.dropCollector),
- )(
- ({ connectDragSource, connectDropTarget, isOver, value }: DraggableChipProps<Value> & CollectedProps) =>
- compose(
- connectDragSource,
- connectDropTarget,
- )(
- <span>
- <Chip
- color={isOver ? 'primary' : 'default'}
- onDelete={this.deleteValue(value)}
- label={this.props.getLabel ? this.props.getLabel(value) : JSON.stringify(value)} />
- </span>
- )
- );
+ const chip =
+ <span>
+ <Chip
+ color={isOver ? 'primary' : 'default'}
+ onDelete={this.props.deletable
+ ? this.deleteValue(value)
+ : undefined}
+ clickable={this.props.clickable}
+ label={this.props.getLabel ?
+ this.props.getLabel(value)
+ : typeof value === 'object'
+ ? JSON.stringify(value)
+ : value} />
+ </span>;
- deleteValue = (value: Value) => () => {
- const { values } = this.props;
- const index = values.indexOf(value);
- const newValues = values.slice(0);
- newValues.splice(index, 1);
- this.props.onChange(newValues);
- }
-}
+ return this.props.orderable
+ ? connect(chip)
+ : chip;
+ }
+ );
+
+ deleteValue = (value: Value) => () => {
+ const { values } = this.props;
+ const index = values.indexOf(value);
+ const newValues = values.slice(0);
+ newValues.splice(index, 1);
+ this.props.onChange(newValues);
+ }
+ });
interface CollectedProps {
connectDragSource: ConnectDragSource;