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