forstok-ui-lib 8.8.2 → 8.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forstok-ui-lib",
3
- "version": "8.8.2",
3
+ "version": "8.9.0",
4
4
  "description": "Forstok UI Components Library",
5
5
  "path": "dist",
6
6
  "main": "dist/index.js",
@@ -0,0 +1,544 @@
1
+ import {
2
+ useCallback,
3
+ useEffect,
4
+ useRef,
5
+ useState,
6
+ ReactNode,
7
+ ReactElement,
8
+ } from "react";
9
+ import type { KeyboardEvent } from "react";
10
+ import { AsyncPaginate } from "react-select-async-paginate";
11
+ import Creatable from "react-select/creatable";
12
+ import type { CreatableProps } from "react-select/creatable";
13
+ import {
14
+ withAsyncPaginate,
15
+ type UseAsyncPaginateParams,
16
+ type ComponentProps,
17
+ } from "react-select-async-paginate";
18
+
19
+ import type { LoadOptions } from "react-select-async-paginate";
20
+ import {
21
+ SingleValueProps,
22
+ ActionMeta,
23
+ ControlProps,
24
+ CSSObjectWithLabel,
25
+ GroupBase,
26
+ OnChangeValue,
27
+ OptionProps,
28
+ StylesConfig,
29
+ } from "react-select";
30
+
31
+ import type { CSSObject } from "@emotion/serialize";
32
+ import type { TState } from "../../typeds/base.typed";
33
+ import type { TLoadOption, TOption } from "./typed";
34
+
35
+ type AsyncPaginateCreatableProps<
36
+ OptionType,
37
+ Group extends GroupBase<OptionType>,
38
+ Additional,
39
+ IsMulti extends boolean,
40
+ > = CreatableProps<OptionType, IsMulti, Group> &
41
+ UseAsyncPaginateParams<OptionType, Group, Additional> &
42
+ ComponentProps<OptionType, Group, IsMulti>;
43
+
44
+ type AsyncPaginateCreatableType = <
45
+ OptionType,
46
+ Group extends GroupBase<OptionType>,
47
+ Additional,
48
+ IsMulti extends boolean = false,
49
+ >(
50
+ props: AsyncPaginateCreatableProps<OptionType, Group, Additional, IsMulti>,
51
+ ) => ReactElement;
52
+
53
+ const AsyncPaginateCreatable = withAsyncPaginate(
54
+ Creatable,
55
+ ) as AsyncPaginateCreatableType;
56
+
57
+ type TSelect = {
58
+ type?: string;
59
+ mode?: string;
60
+ isError?: boolean;
61
+ customLabel?: (arg0: SingleValueProps) => void;
62
+ customOption?: (arg0: OptionProps) => void;
63
+ MenuList?: any;
64
+ reset?: boolean;
65
+ setReset?: TState<boolean>;
66
+ evChange?: (
67
+ newValue: OnChangeValue<TOption, boolean>,
68
+ actionMeta?: ActionMeta<TOption>,
69
+ ) => void;
70
+ defaultValue?: OnChangeValue<TOption, boolean>;
71
+ loadOptions: LoadOptions<unknown, GroupBase<unknown>, unknown> | TLoadOption;
72
+ loadOptionsOnMenuOpen?: boolean;
73
+ noOptionsMessage?: (obj: { inputValue: string }) => ReactNode;
74
+ isSearchable?: boolean;
75
+ isClearable?: boolean;
76
+ placeholder?: string;
77
+ isMenuOpen?: boolean;
78
+ setMenuIsOpen?: TState<boolean | undefined>;
79
+ isForceUpdate?: boolean;
80
+ setForceUpdate?: TState<boolean>;
81
+ isMulti?: boolean;
82
+ iconLeft?: boolean | string;
83
+ loadedOptions?: TOption[];
84
+ "data-qa-id"?: string;
85
+ evChangeOnMenuClose?: (
86
+ value: OnChangeValue<TOption, boolean> | undefined,
87
+ ) => void;
88
+ id?: string;
89
+ isDisabled?: boolean;
90
+ isCreateable?: boolean;
91
+ evCreate?: (value: string) => void;
92
+ };
93
+
94
+ const SelectAsyncCreateablePaginateComponent = ({
95
+ loadOptions,
96
+ ...props
97
+ }: TSelect) => {
98
+ const {
99
+ isError = false,
100
+ mode,
101
+ type,
102
+ customLabel,
103
+ customOption,
104
+ MenuList,
105
+ defaultValue,
106
+ // reset,
107
+ setReset,
108
+ evChange,
109
+ isForceUpdate,
110
+ setForceUpdate,
111
+ loadOptionsOnMenuOpen,
112
+ noOptionsMessage,
113
+ iconLeft,
114
+ isSearchable,
115
+ placeholder,
116
+ isMulti,
117
+ loadedOptions,
118
+ isMenuOpen,
119
+ setMenuIsOpen,
120
+ id,
121
+ evChangeOnMenuClose,
122
+ isCreateable = false,
123
+ evCreate,
124
+ ...rest
125
+ } = props;
126
+
127
+ const [valueSelect, setValueSelect] = useState<typeof defaultValue>(
128
+ defaultValue || null,
129
+ );
130
+ const [inputValue, setInputValue] = useState<string>("");
131
+ const [_time, setTime] = useState<Date>(new Date());
132
+
133
+ const selectRef = useRef<HTMLElement | null>(null);
134
+
135
+ useEffect(() => {
136
+ if (isForceUpdate) {
137
+ setValueSelect(defaultValue || null);
138
+ setForceUpdate?.(false);
139
+ }
140
+ }, [defaultValue, isForceUpdate, setForceUpdate, setValueSelect]);
141
+
142
+ const handleChange = (
143
+ value: OnChangeValue<TOption, boolean>,
144
+ actionMeta?: ActionMeta<TOption>,
145
+ ) => {
146
+ const newValue = value as OnChangeValue<TOption, false>;
147
+
148
+ if (actionMeta) {
149
+ const { action, option } = actionMeta;
150
+
151
+ if (action === "select-option" && option?.value === "*") {
152
+ const newValueSelect = valueSelect as OnChangeValue<TOption, true>;
153
+
154
+ if (newValueSelect?.length) {
155
+ let _newValue = loadedOptions
156
+ ? [...newValueSelect, ...loadedOptions]
157
+ : [...newValueSelect];
158
+
159
+ _newValue = _newValue.filter(
160
+ (_value, idx, self) =>
161
+ idx === self.findIndex((t) => t.sku === _value.sku),
162
+ );
163
+
164
+ setValueSelect(_newValue);
165
+ evChange && evChange(_newValue, actionMeta);
166
+ } else if (loadedOptions) {
167
+ setValueSelect(loadedOptions);
168
+ evChange && evChange(loadedOptions, actionMeta);
169
+ }
170
+
171
+ selectRef.current?.blur();
172
+ return false;
173
+ }
174
+ }
175
+
176
+ setValueSelect(newValue);
177
+ evChange && evChange(newValue, actionMeta);
178
+ };
179
+
180
+ const handleCreate = (inputValue: string) => {
181
+ setReset?.(false);
182
+ if (isMulti) {
183
+ !Array.isArray(valueSelect)
184
+ ? setValueSelect([{ value: inputValue, label: inputValue }])
185
+ : setValueSelect([
186
+ ...valueSelect,
187
+ { value: inputValue, label: inputValue },
188
+ ]);
189
+ } else {
190
+ setValueSelect({ value: inputValue, label: inputValue });
191
+ }
192
+ evCreate?.(inputValue);
193
+ setInputValue("");
194
+ };
195
+
196
+ const customStyles: StylesConfig<TOption, boolean> = {
197
+ container: (provided: CSSObject) =>
198
+ ({
199
+ ...provided,
200
+ display: "block",
201
+ width: "100%",
202
+ maxWidth: "100%",
203
+ minWidth: 0,
204
+ minHeight: "1px",
205
+ textAlign: "left",
206
+ border: "none",
207
+ }) as CSSObjectWithLabel,
208
+ control: (provided: CSSObject, state: ControlProps<TOption, boolean>) =>
209
+ ({
210
+ ...provided,
211
+ width: "100%",
212
+ minWidth: 0,
213
+ borderRadius: ".5rem",
214
+ minHeight: "1px",
215
+ height: isMulti ? "100%" : mode === "small" ? "30px" : "32px",
216
+ maxHeight: "300px",
217
+ cursor: "pointer",
218
+ borderWidth: isError ? "1px" : "0",
219
+ boxShadow: state.isFocused
220
+ ? "-.0625rem 0rem .0625rem 0rem rgba(26, 26, 26, .122) inset, .0625rem 0rem .0625rem 0rem rgba(26, 26, 26, .122) inset, 0rem .125rem .0625rem 0rem rgba(26, 26, 26, .2) inset"
221
+ : "0rem -.0625rem 0rem 0rem #b5b5b5 inset,0rem 0rem 0rem .0625rem rgba(0, 0, 0, .1) inset,0rem .03125rem 0rem .09375rem #FFF inset",
222
+ borderColor:
223
+ state.isFocused && mode !== "filter" && !isError
224
+ ? "var(--pri-clr-ln__fc)"
225
+ : isError
226
+ ? "var(--err-clr-ln)"
227
+ : mode === "filter"
228
+ ? "#ffffff"
229
+ : " var(--ck-clr-ln)",
230
+ background: state.isFocused ? "rgba(247, 247, 247, 1)" : "#ffffff",
231
+ overflow: mode === "picklist" ? "hidden" : "initial",
232
+ "&:hover": {
233
+ borderColor:
234
+ state.isFocused && mode !== "filter" && !isError
235
+ ? "var(--pri-clr-ln__fc)"
236
+ : isError
237
+ ? "var(--err-clr-ln)"
238
+ : mode === "filter"
239
+ ? "#ffffff"
240
+ : " var(--ck-clr-ln)",
241
+ background: "rgba(250, 250, 250, 1)",
242
+ color: "rgba(48, 48, 48, 1)",
243
+ },
244
+ }) as CSSObjectWithLabel,
245
+ placeholder: (provided: CSSObject) =>
246
+ ({
247
+ ...provided,
248
+ fontStyle: "italic",
249
+ }) as CSSObjectWithLabel,
250
+ input: (provided: CSSObject) =>
251
+ ({
252
+ ...provided,
253
+ minHeight: "1px",
254
+ margin: "0 2px",
255
+ padding: "0",
256
+ input: {
257
+ boxShadow: "none !important",
258
+ },
259
+ }) as CSSObjectWithLabel,
260
+ singleValue: (provided: CSSObject) =>
261
+ ({
262
+ ...provided,
263
+ minHeight: "1px",
264
+ minWidth: 0,
265
+ maxWidth: "100%",
266
+ overflow: "hidden",
267
+ textOverflow: "ellipsis",
268
+ whiteSpace: "nowrap",
269
+ paddingBottom: "2px",
270
+ color: "initial",
271
+ }) as CSSObjectWithLabel,
272
+ dropdownIndicator: (provided: CSSObject) =>
273
+ ({
274
+ ...provided,
275
+ minHeight: "1px",
276
+ paddingTop: "0",
277
+ paddingBottom: "0",
278
+ paddingLeft: "2px",
279
+ paddingRight: "2px",
280
+ color: "#757575",
281
+ }) as CSSObjectWithLabel,
282
+ indicatorSeparator: (provided: CSSObject) =>
283
+ ({
284
+ ...provided,
285
+ minHeight: "1px",
286
+ height: "24px",
287
+ display: type === "tag" ? "block" : "none",
288
+ }) as CSSObjectWithLabel,
289
+ clearIndicator: (provided: CSSObject) =>
290
+ ({
291
+ ...provided,
292
+ minHeight: "1px",
293
+ marginRight: "-6px",
294
+ padding: "0",
295
+ }) as CSSObjectWithLabel,
296
+ valueContainer: (provided: CSSObject) =>
297
+ ({
298
+ ...provided,
299
+ flex: 1,
300
+ minWidth: 0,
301
+ minHeight: "1px",
302
+ height: isMulti
303
+ ? "100%"
304
+ : mode === "multi-select"
305
+ ? "70px"
306
+ : mode === "small"
307
+ ? "30px"
308
+ : "32px",
309
+ paddingTop: isMulti ? "4px" : "0",
310
+ paddingBottom: isMulti ? "4px" : "0",
311
+ paddingLeft: iconLeft ? "28px" : "8px",
312
+ paddingRight: "2px",
313
+ maxHeight: isMulti ? "300px" : "unset",
314
+ overflowX: isMulti ? "hidden" : "initial",
315
+ overflowY: isMulti ? "auto" : "initial",
316
+ flexWrap: isMulti ? "wrap" : "nowrap",
317
+ alignContent: isMulti ? "flex-start" : "initial",
318
+ }) as CSSObjectWithLabel,
319
+ option: (provided: CSSObject, state: OptionProps<TOption, boolean>) =>
320
+ ({
321
+ ...provided,
322
+ width: "100%",
323
+ margin: "0 0 4px",
324
+ borderRadius: "var(--nav-rd)",
325
+ cursor: "pointer",
326
+ overflowWrap: "break-word",
327
+ wordBreak: "break-all",
328
+ position: "relative",
329
+ padding: "0.375rem 0.5rem",
330
+ backgroundColor:
331
+ state.isSelected || state.isFocused
332
+ ? "var(--nav-clr-bg__hvr)"
333
+ : "transparent",
334
+ "&:last-child": {
335
+ marginBottom: 0,
336
+ },
337
+ ...(!state.isDisabled && {
338
+ color: "var(--pri-clr)",
339
+ fontWeight: state.isSelected ? 500 : "normal",
340
+ "&:hover": {
341
+ backgroundColor: "var(--nav-clr-bg__hvr)",
342
+ },
343
+ }),
344
+ ...(state.isSelected && {
345
+ paddingRight: "1.5em",
346
+ "&:after": {
347
+ content: '"✔"',
348
+ position: "absolute",
349
+ top: "50%",
350
+ transform: "translateY(-50%)",
351
+ marginTop: "2px",
352
+ right: "6px",
353
+ bottom: "0",
354
+ width: "12px",
355
+ height: "20px",
356
+ },
357
+ }),
358
+ }) as CSSObjectWithLabel,
359
+ menu: (provided: CSSObject) =>
360
+ ({
361
+ ...provided,
362
+ padding: "6px 8px",
363
+ borderRadius: ".75rem",
364
+ boxShadow:
365
+ "0rem .5rem 1.5rem -.5rem rgba(0, 0, 0, .05), 0rem .5rem 1rem -.25rem rgba(0, 0, 0, .05), 0rem .1875rem .375rem 0rem rgba(0, 0, 0, .05), 0rem .125rem .25rem 0rem rgba(0, 0, 0, .05), 0rem .0625rem .125rem 0rem rgba(0, 0, 0, .05), 0rem 0rem 0rem .0625rem rgba(0, 0, 0, .06)",
366
+ border: "none",
367
+ }) as CSSObjectWithLabel,
368
+ menuPortal: (provided: CSSObject) =>
369
+ ({
370
+ ...provided,
371
+ zIndex: 9999,
372
+ letterSpacing: "normal",
373
+ lineHeight: "normal",
374
+ }) as CSSObjectWithLabel,
375
+ multiValue: (provided: CSSObject) =>
376
+ ({
377
+ ...provided,
378
+ maxWidth: "100%",
379
+ }) as CSSObjectWithLabel,
380
+ multiValueLabel: (provided: CSSObject) =>
381
+ ({
382
+ ...provided,
383
+ overflow: "hidden",
384
+ textOverflow: "ellipsis",
385
+ whiteSpace: "nowrap",
386
+ }) as CSSObjectWithLabel,
387
+ };
388
+
389
+ const onMenuOpen = useCallback(() => {
390
+ setMenuIsOpen && setMenuIsOpen(true);
391
+ }, [setMenuIsOpen]);
392
+
393
+ const onMenuClose = useCallback(() => {
394
+ setInputValue("");
395
+ setMenuIsOpen && setMenuIsOpen(false);
396
+ }, [setMenuIsOpen]);
397
+
398
+ const evToogleSelect = () => {
399
+ const containerRef = document.getElementsByClassName(
400
+ "_refSelectContainer is-shown",
401
+ ) as HTMLCollectionOf<HTMLElement>;
402
+ if (containerRef.length) {
403
+ for (let i = 0; i < containerRef.length; i++) {
404
+ containerRef[i].classList.remove("is-shown");
405
+ }
406
+ }
407
+ };
408
+
409
+ const evReToogleSelect = () => {
410
+ setTime(new Date());
411
+ evToogleSelect();
412
+ };
413
+
414
+ const handleKeyDown = (event: KeyboardEvent) => {
415
+ if (event.key !== "Enter") return;
416
+
417
+ const target = event.target as HTMLElement;
418
+ const menuId =
419
+ target.getAttribute("aria-controls") ||
420
+ target.closest("[aria-controls]")?.getAttribute("aria-controls");
421
+ const menuEl = menuId ? document.getElementById(menuId) : null;
422
+ const hasSelectableOption = Boolean(
423
+ menuEl?.querySelector('[role="option"]'),
424
+ );
425
+
426
+ if (!hasSelectableOption) {
427
+ event.preventDefault();
428
+ event.stopPropagation();
429
+ }
430
+ };
431
+
432
+ const SelectEl = isCreateable ? (
433
+ <AsyncPaginateCreatable
434
+ isSearchable={isSearchable}
435
+ placeholder={placeholder}
436
+ styles={customStyles}
437
+ loadOptions={loadOptions}
438
+ menuPortalTarget={document.body}
439
+ menuPosition="fixed"
440
+ menuPlacement="auto"
441
+ value={valueSelect}
442
+ onChange={handleChange}
443
+ onKeyDown={handleKeyDown}
444
+ debounceTimeout={300}
445
+ onCreateOption={handleCreate}
446
+ loadOptionsOnMenuOpen={loadOptionsOnMenuOpen}
447
+ noOptionsMessage={noOptionsMessage}
448
+ menuIsOpen={isMenuOpen}
449
+ {...(id && { id: id })}
450
+ {...(mode === "multi-select" && {
451
+ closeMenuOnSelect: false,
452
+ inputValue: inputValue,
453
+ onInputChange: (value, action) => {
454
+ if (action.action === "input-change") {
455
+ setInputValue(value);
456
+ setTime(new Date());
457
+ return value;
458
+ }
459
+
460
+ return value;
461
+ },
462
+ onMenuClose: () => {
463
+ setInputValue("");
464
+ evChangeOnMenuClose?.(valueSelect);
465
+ },
466
+ onMenuOpen: () => evReToogleSelect(),
467
+ cacheUniqs: [_time],
468
+ selectRef: selectRef,
469
+ })}
470
+ {...(mode !== "multi-select" && {
471
+ onMenuOpen: onMenuOpen,
472
+ onMenuClose: onMenuClose,
473
+ })}
474
+ {...(customLabel && { components: { SingleValue: customLabel } })}
475
+ {...(customOption && { components: { Option: customOption } })}
476
+ {...(customLabel &&
477
+ customOption && {
478
+ components: { SingleValue: customLabel, Option: customOption },
479
+ })}
480
+ {...(MenuList && { components: { MenuList } })}
481
+ {...(isMulti && { isMulti: true })}
482
+ {...rest}
483
+ />
484
+ ) : (
485
+ <AsyncPaginate
486
+ isSearchable={isSearchable}
487
+ placeholder={placeholder}
488
+ debounceTimeout={500}
489
+ styles={customStyles}
490
+ loadOptions={loadOptions}
491
+ menuPortalTarget={document.body}
492
+ menuPosition="fixed"
493
+ menuPlacement="auto"
494
+ value={valueSelect}
495
+ onChange={handleChange}
496
+ onKeyDown={handleKeyDown}
497
+ loadOptionsOnMenuOpen={loadOptionsOnMenuOpen}
498
+ noOptionsMessage={noOptionsMessage}
499
+ menuIsOpen={isMenuOpen}
500
+ {...(id && { id: id })}
501
+ {...(mode === "multi-select" && {
502
+ closeMenuOnSelect: false,
503
+ inputValue: inputValue,
504
+ onInputChange: (value, action) => {
505
+ if (action.action === "input-change") {
506
+ setInputValue(value);
507
+ setTime(new Date());
508
+ return value;
509
+ }
510
+
511
+ return value;
512
+ },
513
+ onMenuClose: () => {
514
+ setInputValue("");
515
+ evChangeOnMenuClose?.(valueSelect);
516
+ },
517
+ onMenuOpen: () => evReToogleSelect(),
518
+ cacheUniqs: [_time],
519
+ selectRef: selectRef,
520
+ })}
521
+ {...(mode !== "multi-select" && {
522
+ onMenuOpen: onMenuOpen,
523
+ onMenuClose: onMenuClose,
524
+ })}
525
+ {...(customLabel && { components: { SingleValue: customLabel } })}
526
+ {...(customOption && { components: { Option: customOption } })}
527
+ {...(customLabel &&
528
+ customOption && {
529
+ components: { SingleValue: customLabel, Option: customOption },
530
+ })}
531
+ {...(MenuList && { components: { MenuList } })}
532
+ {...(isMulti && { isMulti: true })}
533
+ {...rest}
534
+ />
535
+ );
536
+
537
+ return (
538
+ <div className="_refContainer" style={{ width: "100%", minWidth: 0 }}>
539
+ {SelectEl}
540
+ </div>
541
+ );
542
+ };
543
+
544
+ export default SelectAsyncCreateablePaginateComponent;
@@ -1,5 +1,7 @@
1
1
  import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { AsyncPaginate } from "react-select-async-paginate";
3
+ import AsyncCreatableSelect from "react-select/async-creatable";
4
+
3
5
  import type { LoadOptions } from "react-select-async-paginate";
4
6
  import {
5
7
  SingleValueProps,
@@ -370,7 +372,7 @@ const SelectAsyncPaginateComponent = ({ loadOptions, ...props }: TSelect) => {
370
372
  };
371
373
 
372
374
  let SelectEl = isCreateable ? (
373
- <AsyncPaginate
375
+ <AsyncCreatableSelect
374
376
  isSearchable={isSearchable}
375
377
  placeholder={placeholder}
376
378
  debounceTimeout={500}
@@ -1,6 +1,5 @@
1
1
  import { useEffect, useState } from "react";
2
2
  import ReactSelect from "react-select";
3
- import AsyncCreatableSelect from "react-select/async-creatable";
4
3
  import type {
5
4
  SingleValueProps,
6
5
  OptionProps,
@@ -546,7 +545,7 @@ const SelectComponent = ({
546
545
  };
547
546
 
548
547
  let SelectEl = isCreateable ? (
549
- <AsyncCreatableSelect
548
+ <CreatableSelect
550
549
  options={options}
551
550
  styles={customStyles}
552
551
  onMenuOpen={evToogleSelect}