on d955c27 help sign in
145 lines 3.6 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}) {
20 const {
21 input,
22 meta: {
23 active,
24 error,
25 submitting,
26 touched,
27 valid,
28 data: { warning },
29 },
30 } = useField(name, {
31 type: 'select',
32 });
34 disabled = disabled || submitting;
36 const [isOpen, setIsOpen] = useState(false);
37 const ref = useRef();
39 // close select on outside clicks
40 useOnClickOutside(ref, useCallback(() => setIsOpen(false), [setIsOpen]));
42 const toggleOpen = useCallback(() => {
43 input.onFocus();
44 setIsOpen(isOpen => !isOpen);
45 }, [input]);
47 const onChange = value => {
48 // construct a pseudo event that sets the value correctly
49 input.onChange({ target: { value } });
50 input.onBlur();
51 setIsOpen(false);
52 };
54 const text = options.find(o => o.value === input.value).text;
56 return (
57 <Flex
58 ref={ref}
59 className={cn(className, 'mb1 us-none')}
60 col
61 style={{
62 ...(disabled && {
63 pointerEvents: 'none',
64 cursor: 'not-allowed',
65 }),
66 }}>
67 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
68 {label}
69 </Flex.Item>
70 <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
71 <Flex.Item
72 flex
73 className={cn(
74 'b b1 p3 outline-none',
75 { mono },
76 {
77 'bg-white': !disabled,
78 'bg-gray1': disabled,
79 },
80 {
81 gray4: isOpen,
82 black: !isOpen,
83 },
84 {
85 'b-green3': valid,
86 'b-black': !valid && active,
87 'b-yellow3': !valid && !active && touched && error,
88 'b-gray2': !valid && !active && !touched && !error,
89 }
90 )}
91 id={name}
92 name={name}>
93 {isOpen ? placeholder : text}
94 </Flex.Item>
95 <div
96 className="abs"
97 style={{
98 top: 0,
99 right: 0,
100 height: '100%',
101 width: '44px',
102 overflow: 'hidden',
103 }}>
104 <AccessoryIcon className="gray4">{isOpen ? '▲' : '▼'}</AccessoryIcon>
105 </div>
106 {isOpen && (
107 <Flex
108 col
109 className="abs bg-white b-black b1 z10"
110 style={{
111 top: 'calc(100% + 4px)',
112 left: 0,
113 right: 0,
114 overflow: 'auto',
115 maxHeight: '30vh',
116 }}>
117 {options.map(option => (
118 <Flex.Item
119 key={option.value}
120 className="pv2 ph3 hover-bg-grey3"
121 onClick={e => {
122 e.stopPropagation();
123 onChange(option.value);
124 }}>
125 {option.text}
126 </Flex.Item>
127 ))}
128 </Flex>
129 )}
130 </Flex.Item>
132 {warning && (
133 <Flex.Item as={ErrorText} className="mv1">
134 {warning}
135 </Flex.Item>
136 )}
138 {touched && !active && error && (
139 <Flex.Item as={ErrorText} className="mv1">
140 {error}
141 </Flex.Item>
142 )}
143 </Flex>
144 );