@@ -0,0 +1,120 @@
1+import React, { useState, useCallback } from 'react';
2+import { Flex, ErrorText } from 'indigo-react';
3+import cn from 'classnames';
4+import { useField } from 'react-final-form';
5+
6+import { hasErrors } from 'form/validators';
7+
8+const Chip = ({ value, onDelete }) => {
9+ return (
10+ <Flex className="r32 ph2 m1 flex-center h7 bg-gray2">
11+ <Flex.Item>{value}</Flex.Item>
12+ <Flex.Item onClick={onDelete} className="ml1 f6">
13+ ✗
14+ </Flex.Item>
15+ </Flex>
16+ );
17+};
18+
19+const EmailChipInput = ({ className, name, label }) => {
20+ const [chips, _setChips] = useState([]);
21+
22+ const [value, setValue] = useState('');
23+
24+ const handleChange = useCallback(
25+ event => {
26+ setValue(event.target.value);
27+ },
28+ [setValue]
29+ );
30+
31+ const {
32+ input,
33+ meta: { active, error },
34+ } = useField(name, {
35+ subscription: { active: true, error: true },
36+ });
37+
38+ const setChips = useCallback(
39+ chips => {
40+ _setChips(chips);
41+ input.onChange(chips);
42+ },
43+ [input, _setChips]
44+ );
45+
46+ const addToChips = useCallback(() => {
47+ setChips([...chips, value]);
48+ setValue('');
49+ }, [chips, value, setChips]);
50+
51+ const handleKeyDown = useCallback(
52+ event => {
53+ if (['Enter', 'Tab', ','].includes(event.key) && value !== '') {
54+ event.preventDefault();
55+ addToChips();
56+ } else if ('Backspace' === event.key && value === '') {
57+ event.preventDefault();
58+ setChips(chips.slice(0, -1));
59+ }
60+ },
61+ [setChips, chips, value]
62+ );
63+
64+ const handleDelete = useCallback(
65+ idx => () => {
66+ setChips([...chips.slice(0, idx), ...chips.slice(idx + 1)]);
67+ },
68+ [setChips, chips]
69+ );
70+
71+ const handleBlur = useCallback(
72+ e => {
73+ if (value !== '') {
74+ addToChips();
75+ input.handleBlur();
76+ }
77+ },
78+ [addToChips, value]
79+ );
80+
81+ const hasError = hasErrors(error);
82+
83+ return (
84+ <Flex col className={className}>
85+ {label && (
86+ <Flex.Item as="label" className={cn('f6 lh-tall')} htmlFor={name}>
87+ {label}
88+ </Flex.Item>
89+ )}
90+ <Flex.Item
91+ className={cn('flex b1 flex-wrap', {
92+ 'b-green2': active && !hasError,
93+ 'b-red3': active && hasError,
94+ })}>
95+ {chips.map((chip, idx) => (
96+ <Chip key={idx} value={chip} onDelete={handleDelete(idx)} />
97+ ))}
98+
99+ <input
100+ type="textarea"
101+ className="mv1 mh2 h7 b-none flex-grow"
102+ placeholder="Enter an email address"
103+ value={value}
104+ name={name}
105+ onChange={handleChange}
106+ onKeyDown={handleKeyDown}
107+ onFocus={input.onFocus}
108+ onBlur={handleBlur}
109+ />
110+ </Flex.Item>
111+ {error && (
112+ <Flex.Item as={ErrorText} className="mv1">
113+ {error}
114+ </Flex.Item>
115+ )}
116+ </Flex>
117+ );
118+};
119+
120+export default EmailChipInput;