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