on 261ea40 help sign in
152 lines 3.6 kB history raw
1import React, { useCallback, useRef, useState } from 'react';
2import cn from 'classnames';
4import Flex from './Flex';
5import { ErrorText } from './Typography';
6import useOnClickOutside from 'indigo-react/lib/useOnClickOutside';
7import AccessoryIcon from './AccessoryIcon';
9// NOTE: if we really care about accessibility, we should pull in a dependency
10export default function SelectInput({
11 // visuals
12 name,
13 label,
14 className,
15 accessory,
16 mono = false,
17 placeholder,
19 // callbacks
20 onEnter,
22 // state from hook
23 focused,
24 pass,
25 syncPass,
26 visiblyPassed,
27 error,
28 hintError,
29 data,
30 bind,
31 autoFocus,
32 disabled,
33 options,
34 touched,
36 // ignored
37 initialValue,
38 validators,
39 transformers,
41 // extra
42 ...rest
43}) {
44 const [isOpen, setIsOpen] = useState(false);
45 const ref = useRef();
47 // close select on outside clicks
48 useOnClickOutside(ref, useCallback(() => setIsOpen(false), [setIsOpen]));
50 const toggleOpen = useCallback(() => setIsOpen(isOpen => !isOpen), [
51 setIsOpen,
52 ]);
54 const onChange = value => {
55 // TODO: provide setValue here?
56 // construct a pseudo event that sets the value correctly
57 bind.onChange({ target: { value } });
58 setIsOpen(false);
59 };
61 // redefine accessory because we still want to ignore it from the ..rest above
62 accessory = (
63 <AccessoryIcon className="gray4">{isOpen ? '▲' : '▼'}</AccessoryIcon>
64 );
66 const text = options.find(o => o.value === data).text;
68 return (
69 <Flex
70 ref={ref}
71 className={cn(className, 'mb1 us-none')}
72 col
73 style={{
74 ...(disabled && {
75 pointerEvents: 'none',
76 cursor: 'not-allowed',
77 }),
78 }}>
79 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
80 {label}
81 </Flex.Item>
82 <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
83 <Flex.Item
84 // as="select"
85 className={cn(
86 'b b1 p3 outline-none',
87 { mono },
88 {
89 'bg-white': !disabled,
90 'bg-gray1': disabled,
91 },
92 {
93 gray4: isOpen,
94 black: !isOpen,
95 },
96 {
97 'b-green3': visiblyPassed,
98 'b-black': focused && !visiblyPassed,
99 'b-yellow3': !focused && hintError,
100 'b-gray3': !focused && !hintError && !visiblyPassed,
101 }
102 )}
103 id={name}
104 name={name}
105 {...bind}
106 flex>
107 {isOpen ? placeholder : text}
108 </Flex.Item>
109 <div
110 className="abs"
111 style={{
112 top: 0,
113 right: 0,
114 height: '100%',
115 width: '44px',
116 overflow: 'hidden',
117 }}>
118 {accessory}
119 </div>
120 {isOpen && (
121 <Flex
122 col
123 className="abs bg-white b-black b1 z10"
124 style={{
125 top: 'calc(100% + 4px)',
126 left: 0,
127 right: 0,
128 overflow: 'auto',
129 maxHeight: '30vh',
130 }}>
131 {options.map(option => (
132 <Flex.Item
133 key={option.value}
134 className="pv2 ph3 hover-bg-grey3"
135 onClick={e => {
136 e.stopPropagation();
137 onChange(option.value);
138 }}>
139 {option.text}
140 </Flex.Item>
141 ))}
142 </Flex>
143 )}
144 </Flex.Item>
145 {error && (
146 <Flex.Item as={ErrorText} className="mv1">
147 {error}
148 </Flex.Item>
149 )}
150 </Flex>
151 );