on 633e7b8 help sign in
150 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 visiblyPassed,
26 error,
27 hintError,
28 data,
29 bind,
30 autoFocus,
31 disabled,
32 options,
34 // ignored
35 initialValue,
36 validators,
37 transformers,
39 // extra
40 ...rest
41}) {
42 const [isOpen, setIsOpen] = useState(false);
43 const ref = useRef();
45 // close select on outside clicks
46 useOnClickOutside(ref, useCallback(() => setIsOpen(false), [setIsOpen]));
48 const toggleOpen = useCallback(() => setIsOpen(isOpen => !isOpen), [
49 setIsOpen,
50 ]);
52 const onChange = value => {
53 // TODO: provide setValue here?
54 // construct a pseudo event that sets the value correctly
55 bind.onChange({ target: { value } });
56 setIsOpen(false);
57 };
59 // redefine accessory because we still want to ignore it from the ..rest above
60 accessory = (
61 <AccessoryIcon className="gray4">{isOpen ? '▲' : '▼'}</AccessoryIcon>
62 );
64 const text = options.find(o => o.value === data).text;
66 return (
67 <Flex
68 ref={ref}
69 className={cn(className, 'mb1 us-none')}
70 col
71 style={{
72 ...(disabled && {
73 pointerEvents: 'none',
74 cursor: 'not-allowed',
75 }),
76 }}>
77 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
78 {label}
79 </Flex.Item>
80 <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
81 <Flex.Item
82 // as="select"
83 className={cn(
84 'b b1 p3 outline-none',
85 { mono },
86 {
87 'bg-white': !disabled,
88 'bg-gray1': disabled,
89 },
90 {
91 gray4: isOpen,
92 black: !isOpen,
93 },
94 {
95 'b-green3': visiblyPassed,
96 'b-black': focused && !visiblyPassed,
97 'b-yellow3': !focused && hintError,
98 'b-gray3': !focused && !hintError && !visiblyPassed,
99 }
100 )}
101 id={name}
102 name={name}
103 {...bind}
104 flex>
105 {isOpen ? placeholder : text}
106 </Flex.Item>
107 <div
108 className="abs"
109 style={{
110 top: 0,
111 right: 0,
112 height: '100%',
113 width: '44px',
114 overflow: 'hidden',
115 }}>
116 {accessory}
117 </div>
118 {isOpen && (
119 <Flex
120 col
121 className="abs bg-white b-black b1 z10"
122 style={{
123 top: 'calc(100% + 4px)',
124 left: 0,
125 right: 0,
126 overflow: 'auto',
127 maxHeight: '30vh',
128 }}>
129 {options.map(option => (
130 <Flex.Item
131 key={option.value}
132 className="pv2 ph3 hover-bg-grey3"
133 onClick={e => {
134 e.stopPropagation();
135 onChange(option.value);
136 }}>
137 {option.text}
138 </Flex.Item>
139 ))}
140 </Flex>
141 )}
142 </Flex.Item>
143 {error && (
144 <Flex.Item as={ErrorText} className="mv1">
145 {error}
146 </Flex.Item>
147 )}
148 </Flex>
149 );