on 0872827 help sign in
142 lines 3.7 kB history raw
1import React, { useCallback, useRef, useState } from 'react';
2import cn from 'classnames';
3import { useField } from 'react-final-form';
4import useOnClickOutside from 'indigo-react/lib/useOnClickOutside';
6import Flex from './Flex';
7import { ErrorText } from './Typography';
8import AccessoryIcon from './AccessoryIcon';
10// NOTE: if we really care about accessibility, we should pull in a dependency
11export default function SelectInput({
12 name,
13 label,
14 placeholder,
15 className,
16 mono = false,
17 options = [],
18 disabled = false,
19 warning,
20}) {
21 const {
22 input,
23 meta: { active, error, submitting, submitSucceeded, touched, valid },
24 } = useField(name, {
25 type: 'select',
26 });
28 disabled = disabled || submitting || submitSucceeded;
30 const [isOpen, setIsOpen] = useState(false);
31 const ref = useRef();
33 // close select on outside clicks
34 useOnClickOutside(
35 ref,
36 useCallback(() => setIsOpen(false), [setIsOpen])
37 );
39 const toggleOpen = useCallback(() => {
40 input.onFocus();
41 setIsOpen(isOpen => !isOpen);
42 }, [input]);
44 const onChange = value => {
45 // construct a pseudo event that sets the value correctly
46 input.onChange({ target: { value } });
47 input.onBlur();
48 setIsOpen(false);
49 };
51 const text = options.find(o => o.value === input.value).text;
53 return (
54 <Flex
55 ref={ref}
56 className={cn(className, 'mb1 us-none')}
57 col
58 style={{
59 ...(disabled && {
60 pointerEvents: 'none',
61 cursor: 'not-allowed',
62 }),
63 }}>
64 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
65 {label}
66 </Flex.Item>
67 <Flex.Item as={Flex} row className="rel pointer" onClick={toggleOpen}>
68 <Flex.Item
69 flex
70 className={cn(
71 'b b1 p3 outline-none',
72 { mono },
73 {
74 'bg-white': !disabled,
75 'bg-gray1': disabled,
76 },
77 {
78 gray4: isOpen,
79 black: !isOpen,
80 },
81 {
82 'b-green3': valid,
83 'b-black': !valid && active,
84 'b-yellow3': !valid && !active && touched && error,
85 'b-gray2': !valid && !active && !touched && !error,
86 }
87 )}
88 id={name}
89 name={name}>
90 {isOpen ? placeholder : text}
91 </Flex.Item>
92 <div
93 className="abs"
94 style={{
95 top: 0,
96 right: 0,
97 height: '100%',
98 width: '44px',
99 overflow: 'hidden',
100 }}>
101 <AccessoryIcon className="gray4">{isOpen ? '▲' : '▼'}</AccessoryIcon>
102 </div>
103 {isOpen && (
104 <Flex
105 col
106 className="abs bg-white b-black b1 z10"
107 style={{
108 top: 'calc(100% + 4px)',
109 left: 0,
110 right: 0,
111 overflow: 'auto',
112 maxHeight: '30vh',
113 }}>
114 {options.map(option => (
115 <Flex.Item
116 key={option.value}
117 className="pv2 ph3 hover-bg-grey3"
118 onClick={e => {
119 e.stopPropagation();
120 onChange(option.value);
121 }}>
122 {option.text}
123 </Flex.Item>
124 ))}
125 </Flex>
126 )}
127 </Flex.Item>
129 {warning && (
130 <Flex.Item as={ErrorText} className="mv1">
131 {warning}
132 </Flex.Item>
133 )}
135 {touched && !active && error && (
136 <Flex.Item as={ErrorText} className="mv1">
137 {JSON.stringify(error)}
138 </Flex.Item>
139 )}
140 </Flex>
141 );