on 39bc817 help sign in
131 lines 3.5 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';
8import { useField } from 'react-final-form';
10// NOTE: if we really care about accessibility, we should pull in a dependency
11export default function SelectInput({
12 name,
13 label,
14 placeholder,
15 className,
16 mono = false,
17 options = [],
18 disabled = false,
19}) {
20 const {
21 input,
22 meta: { active, error, submitting, touched, valid },
23 } = useField(name, {
24 type: 'select',
25 });
27 disabled = disabled || submitting;
29 const [isOpen, setIsOpen] = useState(false);
30 const ref = useRef();
32 // close select on outside clicks
33 useOnClickOutside(ref, useCallback(() => setIsOpen(false), [setIsOpen]));
35 const toggleOpen = useCallback(() => {
36 input.onFocus();
37 setIsOpen(isOpen => !isOpen);
38 }, [input]);
40 const onChange = value => {
41 // construct a pseudo event that sets the value correctly
42 input.onChange({ target: { value } });
43 input.onBlur();
44 setIsOpen(false);
45 };
47 const text = options.find(o => o.value === input.value).text;
49 return (
50 <Flex
51 ref={ref}
52 className={cn(className, 'mb1 us-none')}
53 col
54 style={{
55 ...(disabled && {
56 pointerEvents: 'none',
57 cursor: 'not-allowed',
58 }),
59 }}>
60 <Flex.Item as="label" className="f6 lh-tall" htmlFor={name}>
61 {label}
62 </Flex.Item>
63 <Flex.Item as={Flex} row className="rel clickable" onClick={toggleOpen}>
64 <Flex.Item
65 flex
66 className={cn(
67 'b b1 p3 outline-none',
68 { mono },
69 {
70 'bg-white': !disabled,
71 'bg-gray1': disabled,
72 },
73 {
74 gray4: isOpen,
75 black: !isOpen,
76 },
77 {
78 'b-green3': valid,
79 'b-black': !valid && active,
80 'b-yellow3': !valid && !active && touched && error,
81 'b-gray2': !valid && !active && !touched && !error,
82 }
83 )}
84 id={name}
85 name={name}>
86 {isOpen ? placeholder : text}
87 </Flex.Item>
88 <div
89 className="abs"
90 style={{
91 top: 0,
92 right: 0,
93 height: '100%',
94 width: '44px',
95 overflow: 'hidden',
96 }}>
97 <AccessoryIcon className="gray4">{isOpen ? '▲' : '▼'}</AccessoryIcon>
98 </div>
99 {isOpen && (
100 <Flex
101 col
102 className="abs bg-white b-black b1 z10"
103 style={{
104 top: 'calc(100% + 4px)',
105 left: 0,
106 right: 0,
107 overflow: 'auto',
108 maxHeight: '30vh',
109 }}>
110 {options.map(option => (
111 <Flex.Item
112 key={option.value}
113 className="pv2 ph3 hover-bg-grey3"
114 onClick={e => {
115 e.stopPropagation();
116 onChange(option.value);
117 }}>
118 {option.text}
119 </Flex.Item>
120 ))}
121 </Flex>
122 )}
123 </Flex.Item>
124 {touched && !active && error && (
125 <Flex.Item as={ErrorText} className="mv1">
126 {error}
127 </Flex.Item>
128 )}
129 </Flex>
130 );