@loadsmart/loadsmart-ui 5.7.0 → 5.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/README.md +13 -7
- package/dist/components/Dropdown/Dropdown.types.d.ts +6 -0
- package/dist/components/Dropdown/useDropdown.d.ts +1 -1
- package/dist/components/Select/Select.stories.d.ts +1 -0
- package/dist/components/Select/Select.types.d.ts +4 -0
- package/dist/index.js +177 -177
- package/dist/index.js.map +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/package.json +3 -5
- package/src/components/Dropdown/Dropdown.context.ts +1 -0
- package/src/components/Dropdown/Dropdown.tsx +12 -4
- package/src/components/Dropdown/Dropdown.types.ts +6 -0
- package/src/components/Dropdown/DropdownTrigger.tsx +5 -4
- package/src/components/Dropdown/useDropdown.ts +9 -8
- package/src/components/Select/Select.fixtures.ts +1 -1
- package/src/components/Select/Select.stories.tsx +98 -15
- package/src/components/Select/Select.test.tsx +127 -9
- package/src/components/Select/Select.tsx +14 -8
- package/src/components/Select/Select.types.ts +5 -0
- package/src/components/Select/useSelect.ts +59 -15
- package/src/stories/startPage.stories.mdx +20 -8
- package/src/testing/SelectEvent/SelectEvent.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loadsmart/loadsmart-ui",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "Miranda UI, a React UI library",
|
|
5
5
|
"main": "dist",
|
|
6
6
|
"files": [
|
|
@@ -62,14 +62,12 @@
|
|
|
62
62
|
"lint-staged": {
|
|
63
63
|
"src/**/*.{js,ts,jsx,tsx,json}": [
|
|
64
64
|
"eslint --fix",
|
|
65
|
-
"prettier --write"
|
|
66
|
-
"git add"
|
|
65
|
+
"prettier --write"
|
|
67
66
|
],
|
|
68
67
|
"src/{common,components,styles}/**/*.{js,ts,jsx,tsx}": [
|
|
69
68
|
"eslint --fix",
|
|
70
69
|
"prettier --write",
|
|
71
|
-
"stylelint --fix"
|
|
72
|
-
"git add"
|
|
70
|
+
"stylelint --fix"
|
|
73
71
|
]
|
|
74
72
|
},
|
|
75
73
|
"devDependencies": {
|
|
@@ -39,8 +39,16 @@ const HiddenCloseButton = styled.button.attrs({
|
|
|
39
39
|
* @returns
|
|
40
40
|
*/
|
|
41
41
|
export function GenericDropdown(props: GenericDropdownProps): JSX.Element {
|
|
42
|
-
const {
|
|
43
|
-
|
|
42
|
+
const {
|
|
43
|
+
children,
|
|
44
|
+
expanded,
|
|
45
|
+
toggle,
|
|
46
|
+
disabled = false,
|
|
47
|
+
expandDisabled = false,
|
|
48
|
+
onBlur,
|
|
49
|
+
...others
|
|
50
|
+
} = props
|
|
51
|
+
const [contextValue, setContextValue] = useState({ expanded, toggle, disabled, expandDisabled })
|
|
44
52
|
const ref = useRef(null)
|
|
45
53
|
|
|
46
54
|
useClickOutside(
|
|
@@ -58,9 +66,9 @@ export function GenericDropdown(props: GenericDropdownProps): JSX.Element {
|
|
|
58
66
|
|
|
59
67
|
useEffect(
|
|
60
68
|
function updateContextValue() {
|
|
61
|
-
setContextValue({ expanded, toggle, disabled })
|
|
69
|
+
setContextValue({ expanded, toggle, disabled, expandDisabled })
|
|
62
70
|
},
|
|
63
|
-
[
|
|
71
|
+
[expanded, toggle, disabled, expandDisabled]
|
|
64
72
|
)
|
|
65
73
|
|
|
66
74
|
return (
|
|
@@ -15,6 +15,11 @@ export interface useDropdownReturn {
|
|
|
15
15
|
|
|
16
16
|
export interface DropdownProps extends Omit<HTMLAttributes<HTMLDivElement>, 'onChange' | 'onBlur'> {
|
|
17
17
|
disabled?: boolean
|
|
18
|
+
/**
|
|
19
|
+
* Use this prop to not allow the dropdown to be expanded.
|
|
20
|
+
* While the `disabled` prop applies to the whole dropdown trigger element, this one only disables the TriggerHandle.
|
|
21
|
+
*/
|
|
22
|
+
expandDisabled?: boolean
|
|
18
23
|
onBlur?: (event?: MouseEvent | TouchEvent | KeyboardEvent) => void
|
|
19
24
|
}
|
|
20
25
|
|
|
@@ -42,6 +47,7 @@ export interface DropdownMenuSectionProps extends HTMLAttributes<HTMLDivElement>
|
|
|
42
47
|
|
|
43
48
|
export interface DropdownContextReturn {
|
|
44
49
|
disabled: boolean
|
|
50
|
+
expandDisabled: boolean
|
|
45
51
|
expanded: boolean
|
|
46
52
|
toggle: () => void
|
|
47
53
|
}
|
|
@@ -237,7 +237,8 @@ function Caret(props: Omit<IconProps, 'name'> & { $rotate: boolean }) {
|
|
|
237
237
|
}
|
|
238
238
|
|
|
239
239
|
function DropdownTriggerButton(props: DropdownTriggerButtonProps): JSX.Element {
|
|
240
|
-
const
|
|
240
|
+
const contextValue = useContext(DropdownContext)
|
|
241
|
+
const { toggle, disabled, expanded } = contextValue
|
|
241
242
|
const { children, onClick, ...others } = props
|
|
242
243
|
|
|
243
244
|
function handleClick(e: MouseEvent<HTMLButtonElement>) {
|
|
@@ -249,7 +250,7 @@ function DropdownTriggerButton(props: DropdownTriggerButtonProps): JSX.Element {
|
|
|
249
250
|
|
|
250
251
|
function renderChildren() {
|
|
251
252
|
if (isFunction(children)) {
|
|
252
|
-
return children(
|
|
253
|
+
return children(contextValue)
|
|
253
254
|
}
|
|
254
255
|
|
|
255
256
|
return children
|
|
@@ -273,7 +274,7 @@ function DropdownTriggerButton(props: DropdownTriggerButtonProps): JSX.Element {
|
|
|
273
274
|
}
|
|
274
275
|
|
|
275
276
|
function DropdownTriggerHandle(props: ButtonHTMLAttributes<HTMLButtonElement>): JSX.Element {
|
|
276
|
-
const { toggle, expanded, disabled } = useContext(DropdownContext)
|
|
277
|
+
const { toggle, expanded, disabled, expandDisabled } = useContext(DropdownContext)
|
|
277
278
|
const { onClick, ...others } = props
|
|
278
279
|
|
|
279
280
|
function handleClick(e: MouseEvent<HTMLButtonElement>) {
|
|
@@ -288,9 +289,9 @@ function DropdownTriggerHandle(props: ButtonHTMLAttributes<HTMLButtonElement>):
|
|
|
288
289
|
onClick={handleClick}
|
|
289
290
|
data-testid="dropdown-trigger-handle"
|
|
290
291
|
{...others}
|
|
292
|
+
disabled={disabled || expandDisabled}
|
|
291
293
|
type="button"
|
|
292
294
|
tabIndex={-1}
|
|
293
|
-
disabled={disabled}
|
|
294
295
|
>
|
|
295
296
|
<Caret $rotate={expanded} />
|
|
296
297
|
</TriggerHandle>
|
|
@@ -11,6 +11,7 @@ export interface GenericDropdownProps extends DropdownProps, useDropdownProps {}
|
|
|
11
11
|
|
|
12
12
|
function useDropdown({
|
|
13
13
|
disabled,
|
|
14
|
+
expandDisabled,
|
|
14
15
|
}: DropdownProps): {
|
|
15
16
|
expanded: boolean
|
|
16
17
|
toggle: () => void
|
|
@@ -21,44 +22,44 @@ function useDropdown({
|
|
|
21
22
|
|
|
22
23
|
const toggle = useCallback(
|
|
23
24
|
function toggle() {
|
|
24
|
-
if (disabled) {
|
|
25
|
+
if (disabled || expandDisabled) {
|
|
25
26
|
return
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
setExpanded((isExpanded) => !isExpanded)
|
|
29
30
|
},
|
|
30
|
-
[disabled]
|
|
31
|
+
[disabled, expandDisabled]
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
const expand = useCallback(
|
|
34
35
|
function toggle() {
|
|
35
|
-
if (disabled) {
|
|
36
|
+
if (disabled || expandDisabled) {
|
|
36
37
|
return
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
setExpanded(true)
|
|
40
41
|
},
|
|
41
|
-
[disabled]
|
|
42
|
+
[disabled, expandDisabled]
|
|
42
43
|
)
|
|
43
44
|
|
|
44
45
|
const collapse = useCallback(
|
|
45
46
|
function toggle() {
|
|
46
|
-
if (disabled) {
|
|
47
|
+
if (disabled || expandDisabled) {
|
|
47
48
|
return
|
|
48
49
|
}
|
|
49
50
|
|
|
50
51
|
setExpanded(false)
|
|
51
52
|
},
|
|
52
|
-
[disabled]
|
|
53
|
+
[disabled, expandDisabled]
|
|
53
54
|
)
|
|
54
55
|
|
|
55
56
|
useEffect(
|
|
56
57
|
function closeWhenDisabled() {
|
|
57
|
-
if (disabled && expanded) {
|
|
58
|
+
if ((disabled || expandDisabled) && expanded) {
|
|
58
59
|
setExpanded(false)
|
|
59
60
|
}
|
|
60
61
|
},
|
|
61
|
-
[disabled, expanded]
|
|
62
|
+
[disabled, expandDisabled, expanded]
|
|
62
63
|
)
|
|
63
64
|
|
|
64
65
|
return { expanded, toggle, expand, collapse }
|
|
@@ -33,10 +33,10 @@ export const Playground: Story<SelectProps> = (args: SelectProps) => {
|
|
|
33
33
|
<Label htmlFor="select-playground">Select your favorite fruit</Label>
|
|
34
34
|
<Select
|
|
35
35
|
options={FRUITS}
|
|
36
|
+
placeholder="Select sync"
|
|
36
37
|
{...omit(args, OMITTED_PROPS)}
|
|
37
38
|
id="select-playground"
|
|
38
39
|
name="select-playground"
|
|
39
|
-
placeholder="Select sync"
|
|
40
40
|
/>
|
|
41
41
|
</div>
|
|
42
42
|
</div>
|
|
@@ -61,10 +61,10 @@ export const SingleSyncDatasource: Story<SelectProps> = (args: SelectProps) => {
|
|
|
61
61
|
<div className="flex-1">
|
|
62
62
|
<Label htmlFor="select-single-sync">Select your favorite fruit</Label>
|
|
63
63
|
<Select
|
|
64
|
+
placeholder="Select sync"
|
|
64
65
|
{...omit(args, OMITTED_PROPS)}
|
|
65
66
|
id="select-single-sync"
|
|
66
67
|
name="select-single-sync"
|
|
67
|
-
placeholder="Select sync"
|
|
68
68
|
datasources={SINGLE_SYNC_DATASOURCES}
|
|
69
69
|
/>
|
|
70
70
|
</div>
|
|
@@ -106,10 +106,10 @@ export const SingleCustomOptionRendering: Story<SelectProps> = (args: SelectProp
|
|
|
106
106
|
<div className="flex-1">
|
|
107
107
|
<Label htmlFor="select-custom-option">Select your favorite fruit</Label>
|
|
108
108
|
<Select
|
|
109
|
+
placeholder="Select with custom option"
|
|
109
110
|
{...args}
|
|
110
111
|
id="select-custom-option"
|
|
111
112
|
name="select-custom-option"
|
|
112
|
-
placeholder="Select with custom option"
|
|
113
113
|
datasources={SINGLE_SYNC_DATASOURCES}
|
|
114
114
|
/>
|
|
115
115
|
</div>
|
|
@@ -166,10 +166,10 @@ export const SingleCustomEmptyRendering: Story<SelectProps> = (args: SelectProps
|
|
|
166
166
|
<div className="flex-1">
|
|
167
167
|
<Label htmlFor="select-custom-empty">Select your favorite fruit</Label>
|
|
168
168
|
<Select
|
|
169
|
+
placeholder="Select with custom empty"
|
|
169
170
|
{...omit(args, OMITTED_PROPS)}
|
|
170
171
|
id="select-custom-empty"
|
|
171
172
|
name="select-custom-empyy"
|
|
172
|
-
placeholder="Select with custom empty"
|
|
173
173
|
datasources={SINGLE_SYNC_DATASOURCES}
|
|
174
174
|
components={{ Empty }}
|
|
175
175
|
/>
|
|
@@ -209,10 +209,10 @@ export const SingleAsyncDatasource: Story<SelectProps> = (args: SelectProps) =>
|
|
|
209
209
|
<div className="flex-1">
|
|
210
210
|
<Label htmlFor="select-single-async">Select the project manager</Label>
|
|
211
211
|
<Select
|
|
212
|
+
placeholder="Select async"
|
|
212
213
|
{...omit(args, OMITTED_PROPS)}
|
|
213
214
|
id="select-single-async"
|
|
214
215
|
name="select-single-async"
|
|
215
|
-
placeholder="Select async"
|
|
216
216
|
datasources={SINGLE_ASYNC_DATASOURCES}
|
|
217
217
|
/>
|
|
218
218
|
</div>
|
|
@@ -237,10 +237,10 @@ export const SingleMixedDatasources: Story<SelectProps> = (args: SelectProps) =>
|
|
|
237
237
|
<div className="flex-1">
|
|
238
238
|
<Label htmlFor="select-mixed">Select your favorite fruit or the project manager</Label>
|
|
239
239
|
<Select
|
|
240
|
+
placeholder="Select mixed"
|
|
240
241
|
{...omit(args, OMITTED_PROPS)}
|
|
241
242
|
id="select-mixed"
|
|
242
243
|
name="select-mixed"
|
|
243
|
-
placeholder="Select mixed"
|
|
244
244
|
datasources={SINGLE_MIXED_DATASOURCES}
|
|
245
245
|
/>
|
|
246
246
|
</div>
|
|
@@ -266,10 +266,10 @@ export const MultiSyncDatasource: Story<SelectProps> = (args: SelectProps) => {
|
|
|
266
266
|
<div className="flex-1">
|
|
267
267
|
<Label htmlFor="select-multi-sync">Select your favorite fruit</Label>
|
|
268
268
|
<Select
|
|
269
|
+
placeholder="Select multiple sync"
|
|
269
270
|
{...omit(args, OMITTED_PROPS)}
|
|
270
271
|
id="select-multi-sync"
|
|
271
272
|
name="select-multi-sync"
|
|
272
|
-
placeholder="Select multiple sync"
|
|
273
273
|
datasources={MULTI_SYNC_DATASOURCES}
|
|
274
274
|
/>
|
|
275
275
|
</div>
|
|
@@ -294,10 +294,10 @@ export const MultiAsyncDatasource: Story<SelectProps> = (args: SelectProps) => {
|
|
|
294
294
|
<div className="flex-1">
|
|
295
295
|
<Label htmlFor="select-multi-async">Select the project manager</Label>
|
|
296
296
|
<Select
|
|
297
|
+
placeholder="Select multiple async"
|
|
297
298
|
{...omit(args, OMITTED_PROPS)}
|
|
298
299
|
id="select-multi-async"
|
|
299
300
|
name="select-multi-async"
|
|
300
|
-
placeholder="Select multiple async"
|
|
301
301
|
datasources={MULTI_ASYNC_DATASOURCES}
|
|
302
302
|
/>
|
|
303
303
|
</div>
|
|
@@ -324,10 +324,10 @@ export const MultiMixedDatasource: Story<SelectProps> = (args: SelectProps) => {
|
|
|
324
324
|
Select your favorite fruit or the project manager
|
|
325
325
|
</Label>
|
|
326
326
|
<Select
|
|
327
|
+
placeholder="Select multiple mixed"
|
|
327
328
|
{...omit(args, OMITTED_PROPS)}
|
|
328
329
|
id="select-multi-mixed"
|
|
329
330
|
name="select-multi-mixed"
|
|
330
|
-
placeholder="Select multiple mixed"
|
|
331
331
|
datasources={MULTI_MIXED_DATASOURCES}
|
|
332
332
|
/>
|
|
333
333
|
</div>
|
|
@@ -383,10 +383,10 @@ export const MultiCustomOptionRendering: Story<SelectProps> = (args: SelectProps
|
|
|
383
383
|
Select your favorite fruit or the project manager
|
|
384
384
|
</Label>
|
|
385
385
|
<Select
|
|
386
|
+
placeholder="Select multiple mixed"
|
|
386
387
|
{...omit(args, OMITTED_PROPS)}
|
|
387
388
|
id="select-multi-mixed"
|
|
388
389
|
name="select-multi-mixed"
|
|
389
|
-
placeholder="Select multiple mixed"
|
|
390
390
|
datasources={MULTI_MIXED_DATASOURCES}
|
|
391
391
|
components={{ Option: MixedCustomOption }}
|
|
392
392
|
value={value}
|
|
@@ -457,10 +457,10 @@ export const CreatableSync: Story<SelectProps> = (args: SelectProps) => {
|
|
|
457
457
|
<Select
|
|
458
458
|
onCreate={handleCreate}
|
|
459
459
|
datasources={SINGLE_SYNC_DATASOURCES}
|
|
460
|
+
placeholder="Select creatable sync"
|
|
460
461
|
{...args}
|
|
461
462
|
id="select-creatable-sync"
|
|
462
463
|
name="select-creatable-sync"
|
|
463
|
-
placeholder="Select creatable sync"
|
|
464
464
|
/>
|
|
465
465
|
</div>
|
|
466
466
|
</div>
|
|
@@ -488,7 +488,7 @@ function handleCreate(query: string): Promise<void | Option> | void | Option {
|
|
|
488
488
|
|
|
489
489
|
// Update options
|
|
490
490
|
FRUITS = [...FRUITS, item]
|
|
491
|
-
|
|
491
|
+
|
|
492
492
|
// When the option is returned it will be selected. onChange will be fired with the new value
|
|
493
493
|
return item
|
|
494
494
|
}
|
|
@@ -528,10 +528,10 @@ export const CreatableAsync: Story<SelectProps> = (args: SelectProps) => {
|
|
|
528
528
|
datasources={SINGLE_ASYNC_DATASOURCES}
|
|
529
529
|
onCreate={handleCreate}
|
|
530
530
|
multiple
|
|
531
|
+
placeholder="Select creatable async"
|
|
531
532
|
{...args}
|
|
532
533
|
id="select-creatable-async"
|
|
533
534
|
name="select-creatable-async"
|
|
534
|
-
placeholder="Select creatable async"
|
|
535
535
|
disabled={disabled}
|
|
536
536
|
/>
|
|
537
537
|
</div>
|
|
@@ -573,7 +573,7 @@ function handleCreate(query: string): Promise<void | Option> | void | Option {
|
|
|
573
573
|
setTimeout(() => {
|
|
574
574
|
// Update options
|
|
575
575
|
USERS = [...USERS, item]
|
|
576
|
-
|
|
576
|
+
|
|
577
577
|
// Enable the component
|
|
578
578
|
setDisabled(false)
|
|
579
579
|
|
|
@@ -615,10 +615,10 @@ export const CustomCreatableOption: Story<SelectProps> = ({ onCreate, ...args }:
|
|
|
615
615
|
<div className="flex-1">
|
|
616
616
|
<Label htmlFor="select-custom-creatable-option">Select your favorite fruit</Label>
|
|
617
617
|
<Select
|
|
618
|
+
placeholder="Select with custom creatable option"
|
|
618
619
|
{...args}
|
|
619
620
|
id="select-custom-creatable-option"
|
|
620
621
|
name="select-custom-creatable-option"
|
|
621
|
-
placeholder="Select with custom creatable option"
|
|
622
622
|
datasources={SINGLE_SYNC_DATASOURCES}
|
|
623
623
|
onChange={(event) => setValue(event.target.value as Option)}
|
|
624
624
|
onCreate={handleCreate}
|
|
@@ -656,3 +656,86 @@ const CreatableOption = () => {
|
|
|
656
656
|
},
|
|
657
657
|
},
|
|
658
658
|
}
|
|
659
|
+
|
|
660
|
+
const FixedCreatableOption = () => {
|
|
661
|
+
return (
|
|
662
|
+
<Select.CreatableOption>
|
|
663
|
+
<Text variant="body-bold" color="color-accent">
|
|
664
|
+
Add new...
|
|
665
|
+
</Text>
|
|
666
|
+
</Select.CreatableOption>
|
|
667
|
+
)
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
export const SelectWithFixedCreatableOption: Story<SelectProps> = ({
|
|
671
|
+
onCreate,
|
|
672
|
+
...args
|
|
673
|
+
}: SelectProps) => {
|
|
674
|
+
const [value, setValue] = useState<Option | null>(null)
|
|
675
|
+
|
|
676
|
+
const handleCreate = useCallback(
|
|
677
|
+
function handleCreate(query: string) {
|
|
678
|
+
onCreate?.(query)
|
|
679
|
+
},
|
|
680
|
+
[onCreate]
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
return (
|
|
684
|
+
<div className="flex flex-col space-y-4">
|
|
685
|
+
<div className="flex flex-row space-x-4" style={{ width: 720 }}>
|
|
686
|
+
<div className="flex-1">
|
|
687
|
+
<Label htmlFor="select-custom-creatable-option">Select your favorite fruit</Label>
|
|
688
|
+
<Select
|
|
689
|
+
{...args}
|
|
690
|
+
id="select-custom-creatable-option"
|
|
691
|
+
name="select-custom-creatable-option"
|
|
692
|
+
placeholder="Select with custom creatable option"
|
|
693
|
+
datasources={SINGLE_SYNC_DATASOURCES}
|
|
694
|
+
onChange={(event) => setValue(event.target.value as Option)}
|
|
695
|
+
onCreate={handleCreate}
|
|
696
|
+
value={value as Option}
|
|
697
|
+
components={{
|
|
698
|
+
CreatableOption: FixedCreatableOption,
|
|
699
|
+
}}
|
|
700
|
+
/>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
<div className="text-sm" style={{ width: 720 }}>
|
|
704
|
+
<p>Available options:</p>
|
|
705
|
+
<code>{FRUITS.map(({ label }) => label).join(', ')}</code>
|
|
706
|
+
</div>
|
|
707
|
+
</div>
|
|
708
|
+
)
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
SelectWithFixedCreatableOption.args = {
|
|
712
|
+
createOptionPosition: 'first',
|
|
713
|
+
isValidNewOption: true,
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
SelectWithFixedCreatableOption.parameters = {
|
|
717
|
+
docs: {
|
|
718
|
+
description: {
|
|
719
|
+
story: `
|
|
720
|
+
\`\`\`jsx
|
|
721
|
+
const CreatableOption = () => {
|
|
722
|
+
return (
|
|
723
|
+
<Select.CreatableOption>
|
|
724
|
+
Add new...
|
|
725
|
+
</Select.CreatableOption>
|
|
726
|
+
)
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
<Select
|
|
730
|
+
{...args}
|
|
731
|
+
createOptionPosition="first"
|
|
732
|
+
isValidNewOption={true}
|
|
733
|
+
components={{
|
|
734
|
+
CreatableOption,
|
|
735
|
+
}}
|
|
736
|
+
/>
|
|
737
|
+
\`\`\`
|
|
738
|
+
`,
|
|
739
|
+
},
|
|
740
|
+
},
|
|
741
|
+
}
|
|
@@ -12,6 +12,7 @@ import selectEvent from '../../testing/SelectEvent'
|
|
|
12
12
|
import type { SelectProps, Option, GenericOption } from './Select.types'
|
|
13
13
|
import Select from './Select'
|
|
14
14
|
import userEvent from '@testing-library/user-event'
|
|
15
|
+
import type { Selectable } from 'hooks/useSelectable'
|
|
15
16
|
|
|
16
17
|
const {
|
|
17
18
|
Playground,
|
|
@@ -22,6 +23,10 @@ const {
|
|
|
22
23
|
} = composeStories(stories)
|
|
23
24
|
|
|
24
25
|
async function expandSelect(waitForOptionsToBeAvailable = false) {
|
|
26
|
+
await waitFor(() => {
|
|
27
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
28
|
+
})
|
|
29
|
+
|
|
25
30
|
fire.click(screen.getByTestId('select-trigger-handle'))
|
|
26
31
|
|
|
27
32
|
await waitFor(() => {
|
|
@@ -47,24 +52,24 @@ describe('Select', () => {
|
|
|
47
52
|
})
|
|
48
53
|
|
|
49
54
|
describe('when disabled', () => {
|
|
50
|
-
it('does not expand with the available options when search input is clicked',
|
|
55
|
+
it('does not expand with the available options when search input is clicked', () => {
|
|
51
56
|
setup({ disabled: true })
|
|
52
57
|
|
|
53
58
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
54
59
|
expect(screen.getByTestId('select-trigger-search-field')).toBeDisabled()
|
|
55
60
|
|
|
56
|
-
|
|
61
|
+
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
57
62
|
|
|
58
63
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
59
64
|
})
|
|
60
65
|
|
|
61
|
-
it('does not expand with the available options when trigger handle is clicked',
|
|
66
|
+
it('does not expand with the available options when trigger handle is clicked', () => {
|
|
62
67
|
setup({ disabled: true })
|
|
63
68
|
|
|
64
69
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
65
70
|
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
fire.click(screen.getByTestId('select-trigger-handle'))
|
|
68
73
|
|
|
69
74
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
70
75
|
})
|
|
@@ -76,12 +81,14 @@ describe('Select', () => {
|
|
|
76
81
|
|
|
77
82
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
78
83
|
|
|
79
|
-
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
80
|
-
|
|
81
84
|
await waitFor(() => {
|
|
82
|
-
screen.
|
|
85
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
83
86
|
})
|
|
84
87
|
|
|
88
|
+
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
89
|
+
|
|
90
|
+
expect(await screen.findByRole('listbox')).toBeInTheDocument()
|
|
91
|
+
|
|
85
92
|
await waitFor(() => {
|
|
86
93
|
screen.getAllByRole('option')
|
|
87
94
|
})
|
|
@@ -109,13 +116,31 @@ describe('Select', () => {
|
|
|
109
116
|
}
|
|
110
117
|
})
|
|
111
118
|
|
|
119
|
+
it('expands only if a query is typed if the options list is empty', async () => {
|
|
120
|
+
render(<Playground options={undefined} />)
|
|
121
|
+
|
|
122
|
+
const searchInput = screen.getByLabelText('Select your favorite fruit')
|
|
123
|
+
|
|
124
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
125
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
126
|
+
|
|
127
|
+
userEvent.type(searchInput, generator.word())
|
|
128
|
+
|
|
129
|
+
await selectEvent.expand(searchInput)
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
133
|
+
expect(screen.queryByRole('listbox')).toBeInTheDocument()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
112
137
|
it('shows correct options when options prop change', async () => {
|
|
113
138
|
const { rerender } = render(<Playground options={undefined} />)
|
|
114
139
|
|
|
115
140
|
const searchInput = screen.getByLabelText('Select your favorite fruit')
|
|
116
|
-
await selectEvent.expand(searchInput)
|
|
117
141
|
|
|
118
|
-
expect(screen.
|
|
142
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
143
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
119
144
|
|
|
120
145
|
rerender(<Playground options={FRUITS as GenericOption[]} />)
|
|
121
146
|
|
|
@@ -785,6 +810,99 @@ describe('Select', () => {
|
|
|
785
810
|
})
|
|
786
811
|
}
|
|
787
812
|
)
|
|
813
|
+
|
|
814
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
815
|
+
'renders fixed creatable option when isValidNewOption is passed - %s',
|
|
816
|
+
async (args) => {
|
|
817
|
+
setup({ ...args, isValidNewOption: true })
|
|
818
|
+
|
|
819
|
+
await expandSelect(false)
|
|
820
|
+
|
|
821
|
+
await waitFor(() => {
|
|
822
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
823
|
+
})
|
|
824
|
+
}
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
828
|
+
'renders creatable option as first item - %s',
|
|
829
|
+
async (args) => {
|
|
830
|
+
setup({ ...args, isValidNewOption: true, createOptionPosition: 'first' })
|
|
831
|
+
|
|
832
|
+
await expandSelect(true)
|
|
833
|
+
|
|
834
|
+
await waitFor(() => {
|
|
835
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
836
|
+
})
|
|
837
|
+
|
|
838
|
+
expect(screen.getAllByTestId('dropdown-menu-item')[0]).toHaveTextContent(/add ""/i)
|
|
839
|
+
}
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
843
|
+
'renders creatable option as last item - %s',
|
|
844
|
+
async (args) => {
|
|
845
|
+
setup({ ...args, isValidNewOption: true, createOptionPosition: 'last' })
|
|
846
|
+
|
|
847
|
+
await expandSelect(true)
|
|
848
|
+
|
|
849
|
+
await waitFor(() => {
|
|
850
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
const options = screen.getAllByTestId('dropdown-menu-item')
|
|
854
|
+
|
|
855
|
+
expect(options[options.length - 1]).toHaveTextContent(/add ""/i)
|
|
856
|
+
}
|
|
857
|
+
)
|
|
858
|
+
|
|
859
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
860
|
+
'keep creatable option on the list even when any value is selected - %s',
|
|
861
|
+
async (args) => {
|
|
862
|
+
const CreatableOption = () => {
|
|
863
|
+
return (
|
|
864
|
+
<Select.CreatableOption data-testid="custom-creatable">Add item</Select.CreatableOption>
|
|
865
|
+
)
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const value = generator.pickone(FRUITS) as Selectable
|
|
869
|
+
|
|
870
|
+
setup({
|
|
871
|
+
...args,
|
|
872
|
+
isValidNewOption: true,
|
|
873
|
+
createOptionPosition: 'first',
|
|
874
|
+
value,
|
|
875
|
+
components: {
|
|
876
|
+
CreatableOption,
|
|
877
|
+
},
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
await expandSelect(true)
|
|
881
|
+
expect(await screen.findByText(/add item/i)).toBeVisible()
|
|
882
|
+
}
|
|
883
|
+
)
|
|
884
|
+
|
|
885
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
886
|
+
'show creatable option on the list even when option list is empty - %s',
|
|
887
|
+
async (args) => {
|
|
888
|
+
const CreatableOption = () => {
|
|
889
|
+
return <Select.CreatableOption>Add item</Select.CreatableOption>
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
setup({
|
|
893
|
+
...args,
|
|
894
|
+
isValidNewOption: true,
|
|
895
|
+
createOptionPosition: 'first',
|
|
896
|
+
options: [],
|
|
897
|
+
components: {
|
|
898
|
+
CreatableOption,
|
|
899
|
+
},
|
|
900
|
+
})
|
|
901
|
+
|
|
902
|
+
await expandSelect(true)
|
|
903
|
+
expect(await screen.findByText(/add item/i)).toBeVisible()
|
|
904
|
+
}
|
|
905
|
+
)
|
|
788
906
|
})
|
|
789
907
|
|
|
790
908
|
describe('Creatable Async', () => {
|