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