@loadsmart/loadsmart-ui 5.7.0 → 5.8.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 +172 -172
- 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 +78 -9
- package/src/components/Select/Select.tsx +14 -8
- package/src/components/Select/Select.types.ts +5 -0
- package/src/components/Select/useSelect.ts +53 -10
- 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.8.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
|
+
}
|
|
@@ -22,6 +22,10 @@ const {
|
|
|
22
22
|
} = composeStories(stories)
|
|
23
23
|
|
|
24
24
|
async function expandSelect(waitForOptionsToBeAvailable = false) {
|
|
25
|
+
await waitFor(() => {
|
|
26
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
27
|
+
})
|
|
28
|
+
|
|
25
29
|
fire.click(screen.getByTestId('select-trigger-handle'))
|
|
26
30
|
|
|
27
31
|
await waitFor(() => {
|
|
@@ -47,24 +51,24 @@ describe('Select', () => {
|
|
|
47
51
|
})
|
|
48
52
|
|
|
49
53
|
describe('when disabled', () => {
|
|
50
|
-
it('does not expand with the available options when search input is clicked',
|
|
54
|
+
it('does not expand with the available options when search input is clicked', () => {
|
|
51
55
|
setup({ disabled: true })
|
|
52
56
|
|
|
53
57
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
54
58
|
expect(screen.getByTestId('select-trigger-search-field')).toBeDisabled()
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
57
61
|
|
|
58
62
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
59
63
|
})
|
|
60
64
|
|
|
61
|
-
it('does not expand with the available options when trigger handle is clicked',
|
|
65
|
+
it('does not expand with the available options when trigger handle is clicked', () => {
|
|
62
66
|
setup({ disabled: true })
|
|
63
67
|
|
|
64
68
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
65
69
|
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
66
70
|
|
|
67
|
-
|
|
71
|
+
fire.click(screen.getByTestId('select-trigger-handle'))
|
|
68
72
|
|
|
69
73
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
70
74
|
})
|
|
@@ -76,12 +80,14 @@ describe('Select', () => {
|
|
|
76
80
|
|
|
77
81
|
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
78
82
|
|
|
79
|
-
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
80
|
-
|
|
81
83
|
await waitFor(() => {
|
|
82
|
-
screen.
|
|
84
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
83
85
|
})
|
|
84
86
|
|
|
87
|
+
fire.click(screen.getByTestId('select-trigger-search-field'))
|
|
88
|
+
|
|
89
|
+
expect(await screen.findByRole('listbox')).toBeInTheDocument()
|
|
90
|
+
|
|
85
91
|
await waitFor(() => {
|
|
86
92
|
screen.getAllByRole('option')
|
|
87
93
|
})
|
|
@@ -109,13 +115,31 @@ describe('Select', () => {
|
|
|
109
115
|
}
|
|
110
116
|
})
|
|
111
117
|
|
|
118
|
+
it('expands only if a query is typed if the options list is empty', async () => {
|
|
119
|
+
render(<Playground options={undefined} />)
|
|
120
|
+
|
|
121
|
+
const searchInput = screen.getByLabelText('Select your favorite fruit')
|
|
122
|
+
|
|
123
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
124
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
125
|
+
|
|
126
|
+
userEvent.type(searchInput, generator.word())
|
|
127
|
+
|
|
128
|
+
await selectEvent.expand(searchInput)
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeEnabled()
|
|
132
|
+
expect(screen.queryByRole('listbox')).toBeInTheDocument()
|
|
133
|
+
})
|
|
134
|
+
})
|
|
135
|
+
|
|
112
136
|
it('shows correct options when options prop change', async () => {
|
|
113
137
|
const { rerender } = render(<Playground options={undefined} />)
|
|
114
138
|
|
|
115
139
|
const searchInput = screen.getByLabelText('Select your favorite fruit')
|
|
116
|
-
await selectEvent.expand(searchInput)
|
|
117
140
|
|
|
118
|
-
expect(screen.
|
|
141
|
+
expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
|
|
142
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
|
|
119
143
|
|
|
120
144
|
rerender(<Playground options={FRUITS as GenericOption[]} />)
|
|
121
145
|
|
|
@@ -785,6 +809,51 @@ describe('Select', () => {
|
|
|
785
809
|
})
|
|
786
810
|
}
|
|
787
811
|
)
|
|
812
|
+
|
|
813
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
814
|
+
'renders fixed creatable option when isValidNewOption is passed - %s',
|
|
815
|
+
async (args) => {
|
|
816
|
+
setup({ ...args, isValidNewOption: true })
|
|
817
|
+
|
|
818
|
+
await expandSelect(false)
|
|
819
|
+
|
|
820
|
+
await waitFor(() => {
|
|
821
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
822
|
+
})
|
|
823
|
+
}
|
|
824
|
+
)
|
|
825
|
+
|
|
826
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
827
|
+
'renders creatable option as first item - %s',
|
|
828
|
+
async (args) => {
|
|
829
|
+
setup({ ...args, isValidNewOption: true, createOptionPosition: 'first' })
|
|
830
|
+
|
|
831
|
+
await expandSelect(true)
|
|
832
|
+
|
|
833
|
+
await waitFor(() => {
|
|
834
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
835
|
+
})
|
|
836
|
+
|
|
837
|
+
expect(screen.getAllByTestId('dropdown-menu-item')[0]).toHaveTextContent(/add ""/i)
|
|
838
|
+
}
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
it.each([[{ multiple: false }], [{ multiple: true }]])(
|
|
842
|
+
'renders creatable option as last item - %s',
|
|
843
|
+
async (args) => {
|
|
844
|
+
setup({ ...args, isValidNewOption: true, createOptionPosition: 'last' })
|
|
845
|
+
|
|
846
|
+
await expandSelect(true)
|
|
847
|
+
|
|
848
|
+
await waitFor(() => {
|
|
849
|
+
expect(screen.getByText(/add ""/i)).toBeInTheDocument()
|
|
850
|
+
})
|
|
851
|
+
|
|
852
|
+
const options = screen.getAllByTestId('dropdown-menu-item')
|
|
853
|
+
|
|
854
|
+
expect(options[options.length - 1]).toHaveTextContent(/add ""/i)
|
|
855
|
+
}
|
|
856
|
+
)
|
|
788
857
|
})
|
|
789
858
|
|
|
790
859
|
describe('Creatable Async', () => {
|
|
@@ -105,6 +105,10 @@ const DividerText = styled(Text)`
|
|
|
105
105
|
background-color: ${token('color-neutral-white')};
|
|
106
106
|
`
|
|
107
107
|
|
|
108
|
+
function isQuerying(status: SelectStatus) {
|
|
109
|
+
return ['pending-query', 'querying'].includes(status)
|
|
110
|
+
}
|
|
111
|
+
|
|
108
112
|
type SeparatorProps = {
|
|
109
113
|
status: SelectStatus
|
|
110
114
|
after?: unknown[]
|
|
@@ -112,7 +116,7 @@ type SeparatorProps = {
|
|
|
112
116
|
}
|
|
113
117
|
|
|
114
118
|
function Separator({ status, after = [], before = [] }: SeparatorProps): JSX.Element {
|
|
115
|
-
if (status
|
|
119
|
+
if (!isQuerying(status)) {
|
|
116
120
|
if (isEmpty(after) && !isEmpty(before)) return <Divider />
|
|
117
121
|
if (isEmpty(after)) return <Fragment />
|
|
118
122
|
}
|
|
@@ -121,7 +125,7 @@ function Separator({ status, after = [], before = [] }: SeparatorProps): JSX.Ele
|
|
|
121
125
|
<div style={{ position: 'relative' }}>
|
|
122
126
|
<Divider />
|
|
123
127
|
<DividerText variant="caption-bold" color="color-neutral-light">
|
|
124
|
-
{status
|
|
128
|
+
{isQuerying(status) ? 'Loading...' : `${after.length} option${pluralize(after.length)}`}
|
|
125
129
|
</DividerText>
|
|
126
130
|
</div>
|
|
127
131
|
)
|
|
@@ -131,7 +135,7 @@ function renderOptionsSingle(select: useSelectReturn, components?: Components):
|
|
|
131
135
|
const { Option, Empty, CreatableOption } = getComponents(components)
|
|
132
136
|
const isCreatable = select.isCreatable()
|
|
133
137
|
|
|
134
|
-
if (select.status
|
|
138
|
+
if (isQuerying(select.status) && isEmpty(select.options)) {
|
|
135
139
|
return <SelectEmpty>Loading...</SelectEmpty>
|
|
136
140
|
}
|
|
137
141
|
|
|
@@ -145,11 +149,12 @@ function renderOptionsSingle(select: useSelectReturn, components?: Components):
|
|
|
145
149
|
|
|
146
150
|
return (
|
|
147
151
|
<>
|
|
152
|
+
{select.createOptionPosition === 'first' && isCreatable ? <CreatableOption /> : null}
|
|
148
153
|
{select.options.map((option) => {
|
|
149
154
|
const { value } = select.getSelectableOption(option)
|
|
150
155
|
return <Option key={String(value)} value={value} />
|
|
151
156
|
})}
|
|
152
|
-
{
|
|
157
|
+
{select.createOptionPosition === 'last' && isCreatable ? <CreatableOption /> : null}
|
|
153
158
|
</>
|
|
154
159
|
)
|
|
155
160
|
}
|
|
@@ -174,18 +179,19 @@ function renderOptionsMultiple(select: useSelectReturn, components?: Components)
|
|
|
174
179
|
|
|
175
180
|
let remaining = (
|
|
176
181
|
<Fragment>
|
|
182
|
+
{select.createOptionPosition === 'first' && isCreatable ? <CreatableOption /> : null}
|
|
177
183
|
{remainingOptions.map((option) => (
|
|
178
184
|
<Option key={String(option.value)} value={option.value} />
|
|
179
185
|
))}
|
|
180
|
-
{
|
|
186
|
+
{select.createOptionPosition === 'last' && isCreatable ? <CreatableOption /> : null}
|
|
181
187
|
</Fragment>
|
|
182
188
|
)
|
|
183
189
|
|
|
184
|
-
if (select.status
|
|
190
|
+
if (!isQuerying(select.status) && isEmpty(remainingOptions)) {
|
|
185
191
|
remaining = isCreatable ? <CreatableOption /> : <Empty>No more options.</Empty>
|
|
186
192
|
}
|
|
187
193
|
|
|
188
|
-
if (select.status
|
|
194
|
+
if (!isQuerying(select.status) && isEmpty(select.options)) {
|
|
189
195
|
remaining = isCreatable ? <CreatableOption /> : <Empty>No results found.</Empty>
|
|
190
196
|
}
|
|
191
197
|
|
|
@@ -214,7 +220,7 @@ function Select(props: SelectProps): JSX.Element {
|
|
|
214
220
|
}
|
|
215
221
|
|
|
216
222
|
function getTrailing() {
|
|
217
|
-
if (select.status
|
|
223
|
+
if (isQuerying(select.status)) {
|
|
218
224
|
return <Loading data-testid="select-trigger-loading">···</Loading>
|
|
219
225
|
}
|
|
220
226
|
|