PluginProbe
HubSpot All-In-One Marketing – Forms, Popups, Live Chat / 11.3.69
HubSpot All-In-One Marketing – Forms, Popups, Live Chat v11.3.69
11.3.73 11.3.71 11.3.70 11.3.69 11.3.64 11.3.65 11.3.62 11.3.61 11.3.56 11.3.58 11.0.31 11.0.52 11.0.54 11.0.56 11.0.58 11.0.7 11.1.10 11.1.11 11.1.13 11.1.14 11.1.15 11.1.2 11.1.20 11.1.21 11.1.22 All 72 releases
leadin / scripts / shared / Common / AsyncSelect.tsx

AsyncSelect.tsx in HubSpot All-In-One Marketing – Forms, Popups, Live Chat 11.3.69, at scripts/shared/Common/AsyncSelect.tsx

312 lines 7.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import React, { useRef, useState, useEffect } from 'react';
2 import { styled } from '@linaria/react';
3 import {
4 CALYPSO,
5 CALYPSO_LIGHT,
6 CALYPSO_MEDIUM,
7 OBSIDIAN,
8 } from '../UIComponents/colors';
9 import UISpinner from '../UIComponents/UISpinner';
10 import LoadState, { LoadStateType } from '../enums/loadState';
11
12 const Container = styled.div`
13 color: ${OBSIDIAN};
14 font-family: 'Lexend Deca', Helvetica, Arial, sans-serif;
15 font-size: 14px;
16 position: relative;
17 `;
18
19 interface IControlContainerProps {
20 focused: boolean;
21 }
22
23 const ControlContainer = styled.div<IControlContainerProps>`
24 align-items: center;
25 background-color: hsl(0, 0%, 100%);
26 border-color: hsl(0, 0%, 80%);
27 border-radius: 4px;
28 border-style: solid;
29 border-width: ${props => (props.focused ? '0' : '1px')};
30 cursor: default;
31 display: flex;
32 flex-wrap: wrap;
33 justify-content: space-between;
34 min-height: 38px;
35 outline: 0 !important;
36 position: relative;
37 transition: all 100ms;
38 box-sizing: border-box;
39 box-shadow: ${props =>
40 props.focused ? `0 0 0 2px ${CALYPSO_MEDIUM}` : 'none'};
41 &:hover {
42 border-color: hsl(0, 0%, 70%);
43 }
44 `;
45
46 const ValueContainer = styled.div`
47 align-items: center;
48 display: flex;
49 flex: 1;
50 flex-wrap: wrap;
51 padding: 2px 8px;
52 position: relative;
53 overflow: hidden;
54 box-sizing: border-box;
55 `;
56
57 const Placeholder = styled.div`
58 color: hsl(0, 0%, 50%);
59 margin-left: 2px;
60 margin-right: 2px;
61 position: absolute;
62 top: 50%;
63 transform: translateY(-50%);
64 box-sizing: border-box;
65 font-size: 16px;
66 `;
67
68 const SingleValue = styled.div`
69 color: hsl(0, 0%, 20%);
70 margin-left: 2px;
71 margin-right: 2px;
72 max-width: calc(100% - 8px);
73 overflow: hidden;
74 position: absolute;
75 text-overflow: ellipsis;
76 white-space: nowrap;
77 top: 50%;
78 transform: translateY(-50%);
79 box-sizing: border-box;
80 `;
81
82 const IndicatorContainer = styled.div`
83 align-items: center;
84 align-self: stretch;
85 display: flex;
86 flex-shrink: 0;
87 box-sizing: border-box;
88 `;
89
90 const DropdownIndicator = styled.div`
91 border-top: 8px solid ${CALYPSO};
92 border-left: 6px solid transparent;
93 border-right: 6px solid transparent;
94 width: 0px;
95 height: 0px;
96 margin: 10px;
97 `;
98
99 const InputContainer = styled.div`
100 margin: 2px;
101 padding-bottom: 2px;
102 padding-top: 2px;
103 visibility: visible;
104 color: hsl(0, 0%, 20%);
105 box-sizing: border-box;
106 `;
107
108 const Input = styled.input`
109 box-sizing: content-box;
110 background: rgba(0, 0, 0, 0) none repeat scroll 0px center;
111 border: 0px none;
112 font-size: inherit;
113 opacity: 1;
114 outline: currentcolor none 0px;
115 padding: 0px;
116 color: inherit;
117 font-family: inherit;
118 `;
119
120 const InputShadow = styled.div`
121 position: absolute;
122 opacity: 0;
123 font-size: inherit;
124 `;
125
126 const MenuContainer = styled.div`
127 position: absolute;
128 top: 100%;
129 background-color: #fff;
130 border-radius: 4px;
131 margin-bottom: 8px;
132 margin-top: 8px;
133 z-index: 9999;
134 box-shadow: 0 0 0 1px hsla(0, 0%, 0%, 0.1), 0 4px 11px hsla(0, 0%, 0%, 0.1);
135 width: 100%;
136 `;
137
138 const MenuList = styled.div`
139 max-height: 300px;
140 overflow-y: auto;
141 padding-bottom: 4px;
142 padding-top: 4px;
143 position: relative;
144 `;
145
146 const MenuGroup = styled.div`
147 padding-bottom: 8px;
148 padding-top: 8px;
149 `;
150
151 const MenuGroupHeader = styled.div`
152 color: #999;
153 cursor: default;
154 font-size: 75%;
155 font-weight: 500;
156 margin-bottom: 0.25em;
157 text-transform: uppercase;
158 padding-left: 12px;
159 padding-left: 12px;
160 `;
161
162 interface IMenuItemProps {
163 selected: boolean;
164 }
165
166 const MenuItem = styled.div<IMenuItemProps>`
167 display: block;
168 background-color: ${props =>
169 props.selected ? CALYPSO_MEDIUM : 'transparent'};
170 color: ${props => (props.selected ? '#fff' : 'inherit')};
171 cursor: default;
172 font-size: inherit;
173 width: 100%;
174 padding: 8px 12px;
175 &:hover {
176 background-color: ${props =>
177 props.selected ? CALYPSO_MEDIUM : CALYPSO_LIGHT};
178 }
179 `;
180
181 interface IAsyncSelectProps {
182 placeholder: string;
183 value: any;
184 loadOptions?: Function;
185 defaultOptions?: any[];
186 onChange: Function;
187 }
188
189 export default function AsyncSelect({
190 placeholder,
191 value,
192 loadOptions,
193 onChange,
194 defaultOptions,
195 }: IAsyncSelectProps) {
196 const inputEl = useRef<HTMLInputElement>(null);
197 const inputShadowEl = useRef<HTMLDivElement>(null);
198 const [isFocused, setFocus] = useState(false);
199 const [loadState, setLoadState] = useState<LoadStateType>(
200 LoadState.NotLoaded
201 );
202 const [localValue, setLocalValue] = useState('');
203 const [options, setOptions] = useState(defaultOptions);
204
205 const inputSize = `${
206 inputShadowEl.current ? inputShadowEl.current.clientWidth + 10 : 2
207 }px`;
208
209 useEffect(() => {
210 if (loadOptions && loadState === LoadState.NotLoaded) {
211 loadOptions('', (result: any) => {
212 setOptions(result);
213 setLoadState(LoadState.Idle);
214 });
215 }
216 }, [loadOptions, loadState]);
217
218 const renderItems = (items: any[] = [], parentKey?: number) => {
219 return items.map((item, index) => {
220 if (item.options) {
221 return (
222 <MenuGroup key={`async-select-item-${index}`}>
223 <MenuGroupHeader id={`${index}-heading`}>
224 {item.label}
225 </MenuGroupHeader>
226 <div>{renderItems(item.options, index)}</div>
227 </MenuGroup>
228 );
229 } else {
230 const key = `async-select-item-${
231 parentKey !== undefined ? `${parentKey}-${index}` : index
232 }`;
233 return (
234 <MenuItem
235 key={key}
236 id={key}
237 selected={value && item.value === value.value}
238 onClick={() => {
239 onChange(item);
240 setFocus(false);
241 }}
242 >
243 {item.label}
244 </MenuItem>
245 );
246 }
247 });
248 };
249
250 return (
251 <Container>
252 <ControlContainer
253 id="leadin-async-selector"
254 focused={isFocused}
255 onClick={() => {
256 if (isFocused) {
257 if (inputEl.current) {
258 inputEl.current.blur();
259 }
260 setFocus(false);
261 setLocalValue('');
262 } else {
263 if (inputEl.current) {
264 inputEl.current.focus();
265 }
266 setFocus(true);
267 }
268 }}
269 >
270 <ValueContainer>
271 {localValue === '' &&
272 (!value ? (
273 <Placeholder>{placeholder}</Placeholder>
274 ) : (
275 <SingleValue>{value.label}</SingleValue>
276 ))}
277 <InputContainer>
278 <Input
279 ref={inputEl}
280 onFocus={() => {
281 setFocus(true);
282 }}
283 onChange={e => {
284 setLocalValue(e.target.value);
285 setLoadState(LoadState.Loading);
286 loadOptions &&
287 loadOptions(e.target.value, (result: any) => {
288 setOptions(result);
289 setLoadState(LoadState.Idle);
290 });
291 }}
292 value={localValue}
293 width={inputSize}
294 id="asycn-select-input"
295 />
296 <InputShadow ref={inputShadowEl}>{localValue}</InputShadow>
297 </InputContainer>
298 </ValueContainer>
299 <IndicatorContainer>
300 {loadState === LoadState.Loading && <UISpinner />}
301 <DropdownIndicator />
302 </IndicatorContainer>
303 </ControlContainer>
304 {isFocused && (
305 <MenuContainer>
306 <MenuList>{renderItems(options)}</MenuList>
307 </MenuContainer>
308 )}
309 </Container>
310 );
311 }
312