summaryrefslogtreecommitdiffhomepage
path: root/frontend/src/components/inputs/Chips.tsx
blob: fc9231f7a739ff48daae4387d9f548c2e83f1776 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import React, {
  FocusEvent,
  FunctionComponent,
  KeyboardEvent,
  useCallback,
  useMemo,
  useRef,
  useState,
} from "react";
import "./chip.scss";

const SplitKeys = ["Tab", "Enter", " ", ",", ";"];

export interface ChipsProps {
  disabled?: boolean;
  defaultValue?: readonly string[];
  onChange?: (v: string[]) => void;
}

export const Chips: FunctionComponent<ChipsProps> = ({
  defaultValue,
  disabled,
  onChange,
}) => {
  const [chips, setChips] = useState(defaultValue ?? []);

  const input = useRef<HTMLInputElement>(null);

  const addChip = useCallback(
    (value: string) => {
      const newChips = [...chips];
      newChips.push(value);
      setChips(newChips);
      onChange && onChange(newChips);
    },
    [chips, onChange]
  );

  const removeChip = useCallback(
    (idx?: number) => {
      idx = idx ?? chips.length - 1;
      if (idx !== -1) {
        const newChips = [...chips];
        newChips.splice(idx, 1);
        setChips(newChips);
        onChange && onChange(newChips);
      }
    },
    [chips, onChange]
  );

  const clearInput = useCallback(() => {
    if (input.current) {
      input.current.value = "";
    }
  }, [input]);

  const onKeyUp = useCallback(
    (event: KeyboardEvent<HTMLInputElement>) => {
      const pressed = event.key;
      const value = event.currentTarget.value;
      if (SplitKeys.includes(pressed) && value.length !== 0) {
        event.preventDefault();
        addChip(value);
        clearInput();
      } else if (pressed === "Backspace" && value.length === 0) {
        event.preventDefault();
        removeChip();
      }
    },
    [addChip, removeChip, clearInput]
  );

  const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) => {
    const pressed = event.key;
    const value = event.currentTarget.value;
    if (SplitKeys.includes(pressed) && value.length !== 0) {
      event.preventDefault();
    }
  }, []);

  const onBlur = useCallback(
    (event: FocusEvent<HTMLInputElement>) => {
      const value = event.currentTarget.value;
      if (value.length !== 0) {
        event.preventDefault();
        addChip(value);
        clearInput();
      }
    },
    [addChip, clearInput]
  );

  const chipElements = useMemo(
    () =>
      chips.map((v, idx) => (
        <span
          key={idx}
          title={v}
          className={`custom-chip ${disabled ? "" : "active"}`}
          onClick={() => {
            if (!disabled) {
              removeChip(idx);
            }
          }}
        >
          {v}
        </span>
      )),
    [chips, removeChip, disabled]
  );

  return (
    <div className="form-control custom-chip-input d-flex">
      <div className="chip-container">{chipElements}</div>
      <input
        disabled={disabled}
        className="main-input p-0"
        ref={input}
        onKeyUp={onKeyUp}
        onKeyDown={onKeyDown}
        onBlur={onBlur}
      ></input>
    </div>
  );
};