on e471cdb help sign in
149 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 = <AccessoryIcon>{isOpen ? '⬆' : '⬇'}</AccessoryIcon>;
63 const text = options.find(o => o.value === data).text;
65 return (
66 <Flex
67 ref={ref}
68 className={cn(className, 'mb1 us-none')}
69 col
70 style={{
71 ...(disabled && {
72 pointerEvents: 'none',
73 cursor: 'not-allowed',
74 }),
75 }}>
76 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
77 {label}
78 </Flex.Item>
79 <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
80 <Flex.Item
81 // as="select"
82 className={cn(
83 'b b1 p3 outline-none',
84 { mono },
85 {
86 'bg-white': !disabled,
87 'bg-gray1': disabled,
88 },
89 {
90 gray4: isOpen,
91 black: !isOpen,
92 },
93 {
94 'b-green3': visiblyPassed,
95 'b-black': focused && !visiblyPassed,
96 'b-yellow3': !focused && hintError,
97 'b-gray3': !focused && !hintError && !visiblyPassed,
98 }
99 )}
100 id={name}
101 name={name}
102 {...bind}
103 flex>
104 {isOpen ? placeholder : text}
105 </Flex.Item>
106 <div
107 className="abs"
108 style={{
109 top: 0,
110 right: 0,
111 height: '100%',
112 width: '44px',
113 overflow: 'hidden',
114 }}>
115 {accessory}
116 </div>
117 {isOpen && (
118 <Flex
119 col
120 className="abs bg-white b-black b1 z10"
121 style={{
122 top: 'calc(100% + 4px)',
123 left: 0,
124 right: 0,
125 overflow: 'auto',
126 maxHeight: '30vh',
127 }}>
128 {options.map(option => (
129 <Flex.Item
130 key={option.value}
131 className="pv2 ph3 hover-bg-grey3"
132 onClick={e => {
133 e.stopPropagation();
134 onChange(option.value);
135 }}>
136 {option.text}
137 </Flex.Item>
138 ))}
139 </Flex>
140 )}
141 </Flex.Item>
142 {error && (
143 <Flex.Item as={ErrorText} className="mv1">
144 {error}
145 </Flex.Item>
146 )}
147 </Flex>
148 );