@loadsmart/loadsmart-ui 5.8.1 → 5.10.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": "@loadsmart/loadsmart-ui",
3
- "version": "5.8.1",
3
+ "version": "5.10.0",
4
4
  "description": "Miranda UI, a React UI library",
5
5
  "main": "dist",
6
6
  "files": [
@@ -3,11 +3,16 @@ import { Meta } from '@storybook/react/types-6-0'
3
3
  import BackButton from '../../common/BackButton'
4
4
  import Button, { SelectorButton, IconButton, Caret, BaseButton } from './Button'
5
5
  import CloseButton from '../../common/CloseButton'
6
+ import type { ReactNode } from 'react'
6
7
 
7
8
  import type { BackButtonProps } from '../../common/BackButton'
8
9
  import type { ButtonProps } from './Button'
9
10
  import type { CloseButtonProps } from '../../common/CloseButton'
10
11
 
12
+ const Container = ({ children }: { children?: ReactNode }) => {
13
+ return <div className="flex flex-col space-y-2 items-center">{children}</div>
14
+ }
15
+
11
16
  export default {
12
17
  title: 'Components/Button',
13
18
  component: Button,
@@ -34,6 +39,11 @@ export default {
34
39
  type: 'boolean',
35
40
  },
36
41
  },
42
+ loading: {
43
+ control: {
44
+ type: 'boolean',
45
+ },
46
+ },
37
47
  className: {
38
48
  table: {
39
49
  disable: true,
@@ -78,9 +88,9 @@ export default {
78
88
 
79
89
  export function Playground(args: ButtonProps): JSX.Element {
80
90
  return (
81
- <div className="flex flex-col space-y-2">
91
+ <Container>
82
92
  <Button {...args}>Button</Button>
83
- </div>
93
+ </Container>
84
94
  )
85
95
  }
86
96
 
@@ -93,7 +103,7 @@ Playground.args = {
93
103
 
94
104
  export function Base(args: ButtonProps): JSX.Element {
95
105
  return (
96
- <div className="flex flex-col space-y-2">
106
+ <Container>
97
107
  <BaseButton {...args}>Click me</BaseButton>
98
108
  <BaseButton {...args} leading={<span>&clubs;</span>}>
99
109
  Click me
@@ -104,17 +114,17 @@ export function Base(args: ButtonProps): JSX.Element {
104
114
  <BaseButton {...args} leading={<span>&clubs;</span>} trailing={<span>&spades;</span>}>
105
115
  Click me
106
116
  </BaseButton>
107
- </div>
117
+ </Container>
108
118
  )
109
119
  }
110
120
 
111
121
  export function Selector(args: ButtonProps): JSX.Element {
112
122
  return (
113
- <div className="flex flex-col space-y-2">
123
+ <Container>
114
124
  <SelectorButton {...args} trailing={<Caret />}>
115
125
  Button
116
126
  </SelectorButton>
117
- </div>
127
+ </Container>
118
128
  )
119
129
  }
120
130
 
@@ -125,11 +135,11 @@ Selector.args = {
125
135
 
126
136
  export function Icon(args: ButtonProps): JSX.Element {
127
137
  return (
128
- <div className="flex flex-col items-center">
138
+ <Container>
129
139
  <IconButton {...args} variant="icon">
130
140
  <span>&clubs;</span>
131
141
  </IconButton>
132
- </div>
142
+ </Container>
133
143
  )
134
144
  }
135
145
 
@@ -139,16 +149,26 @@ Icon.args = {
139
149
 
140
150
  export function Back(args: BackButtonProps): JSX.Element {
141
151
  return (
142
- <div className="flex flex-col items-center">
152
+ <Container>
143
153
  <BackButton {...args} />
144
- </div>
154
+ </Container>
145
155
  )
146
156
  }
147
157
 
148
158
  export function Close(args: CloseButtonProps): JSX.Element {
149
159
  return (
150
- <div className="flex flex-col items-center">
160
+ <Container>
151
161
  <CloseButton {...args} />
152
- </div>
162
+ </Container>
163
+ )
164
+ }
165
+
166
+ export function Loading(args: ButtonProps): JSX.Element {
167
+ return (
168
+ <Container>
169
+ <Button loading {...args}>
170
+ Accessible Text
171
+ </Button>
172
+ </Container>
153
173
  )
154
174
  }
@@ -1,4 +1,5 @@
1
1
  import React from 'react'
2
+ import { screen } from '@testing-library/react'
2
3
 
3
4
  import generator from '../../../tests/generator'
4
5
  import renderer from '../../../tests/renderer'
@@ -13,8 +14,9 @@ describe('<Button />', () => {
13
14
  children: generator.word(),
14
15
  }
15
16
 
16
- const { getByText } = setup(props)
17
- getByText(props.children)
17
+ setup(props)
18
+
19
+ screen.getByRole('button', { name: props.children })
18
20
  })
19
21
 
20
22
  it('renders trailing correctly', () => {
@@ -23,9 +25,9 @@ describe('<Button />', () => {
23
25
  trailing: generator.word(),
24
26
  }
25
27
 
26
- const { getByText } = setup(props)
27
- getByText(props.children)
28
- getByText(props.trailing)
28
+ setup(props)
29
+
30
+ screen.getByRole('button', { name: `${props.children} ${props.trailing}` })
29
31
  })
30
32
 
31
33
  it('renders leading correctly', () => {
@@ -34,8 +36,22 @@ describe('<Button />', () => {
34
36
  leading: generator.word(),
35
37
  }
36
38
 
37
- const { getByText } = setup(props)
38
- getByText(props.children)
39
- getByText(props.leading)
39
+ setup(props)
40
+
41
+ screen.getByRole('button', { name: `${props.leading} ${props.children}` })
42
+ })
43
+
44
+ it('renders loading state', () => {
45
+ const props = {
46
+ children: generator.word(),
47
+ loading: true,
48
+ }
49
+
50
+ setup(props)
51
+
52
+ const button = screen.getByRole('button')
53
+
54
+ expect(button).toHaveAttribute('aria-disabled', 'true')
55
+ expect(button).toHaveAccessibleName(props.children)
40
56
  })
41
57
  })
@@ -14,17 +14,23 @@ import rem from 'utils/toolset/rem'
14
14
  import transition from 'styles/transition'
15
15
  import typography from 'styles/typography'
16
16
 
17
+ import { LoadingDots } from 'components/Loaders'
18
+
17
19
  import type { ButtonHTMLAttributes, ForwardedRef, ReactNode } from 'react'
18
20
  import type { IconProps } from 'components/Icon'
19
21
  import type ColorScheme from 'utils/types/ColorScheme'
22
+ import type { LoadingDotsProps } from 'components/Loaders'
23
+
24
+ export type ButtonVariants = 'primary' | 'secondary' | 'warning' | 'icon' | 'tertiary'
20
25
 
21
26
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
22
27
  className?: string
23
28
  leading?: ReactNode
24
29
  scheme?: ColorScheme
25
30
  trailing?: ReactNode
26
- variant?: 'primary' | 'secondary' | 'warning' | 'icon' | 'tertiary'
31
+ variant?: ButtonVariants
27
32
  scale?: 'small' | 'default' | 'large'
33
+ loading?: boolean
28
34
  }
29
35
 
30
36
  const StyledSpan = styled.span`
@@ -44,23 +50,24 @@ const Trailing = styled(StyledSpan)`
44
50
  /* placeholder */
45
51
  `
46
52
 
47
- export const Children = styled.span`
48
- ${({ children }: ButtonProps) =>
53
+ export const Children = styled.span<ButtonProps>`
54
+ ${({ children }) =>
49
55
  typeof children === 'string'
50
- ? `
51
- ${ellipsizable()}
52
- `
56
+ ? ellipsizable()
53
57
  : `
54
- display: inline-flex;
55
- flex-flow: row nowrap;
56
- align-items: center;
57
- justify-content: center;
58
- `}
58
+ display: inline-flex;
59
+ flex-flow: row nowrap;
60
+ align-items: center;
61
+ justify-content: center;
62
+ `}
59
63
 
60
64
  padding: ${rem('6px')} 0;
61
65
  `
62
66
 
63
- const BaseStyledButton = styled.button<{ $scale: ButtonProps['scale'] }>`
67
+ const BaseStyledButton = styled.button<{
68
+ $scale: ButtonProps['scale']
69
+ $loading?: ButtonProps['loading']
70
+ }>`
64
71
  ${transition()}
65
72
 
66
73
  ${typography(
@@ -98,6 +105,8 @@ const BaseStyledButton = styled.button<{ $scale: ButtonProps['scale'] }>`
98
105
 
99
106
  ${disableable()}
100
107
 
108
+ ${({ $loading }) => $loading && 'pointer-events: none;'}
109
+
101
110
  ${Leading} {
102
111
  margin: 0 ${token('space-xs')} 0 0;
103
112
  }
@@ -359,15 +368,86 @@ const StyledSelector = styled(StyledButton)`
359
368
  }
360
369
  `
361
370
 
362
- export const BaseButton = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
363
- { scale = 'default', children, leading, trailing, ...others }: ButtonProps,
371
+ const TextHidden = styled.span`
372
+ transform: scale(0);
373
+ `
374
+
375
+ /**
376
+ * Accessible attributes in `LoadingDots` aren't necessary
377
+ * because the `Button` will keep its content, but invisible
378
+ */
379
+ const StyledLoadingDots = styled(LoadingDots).attrs({
380
+ 'aria-label': undefined,
381
+ role: 'presentation',
382
+ })`
383
+ position: absolute;
384
+
385
+ display: flex;
386
+ align-items: center;
387
+ justify-content: center;
388
+
389
+ width: 100%;
390
+ `
391
+
392
+ const ButtonLoadingDots = ({
393
+ buttonVariant = 'secondary',
394
+ ...remainingProps
395
+ }: Omit<LoadingDotsProps, 'variant'> & { buttonVariant?: ButtonVariants }) => {
396
+ const buttonLightVariants = new Set(['secondary', 'warning', 'icon', 'tertiary'])
397
+ /**
398
+ * Change LoadingDots variant between `light` and `dark` to have contrast with Button's variant
399
+ */
400
+ const loadingDotsVariant: 'dark' | 'light' = buttonLightVariants.has(buttonVariant)
401
+ ? 'dark'
402
+ : 'light'
403
+
404
+ return <StyledLoadingDots {...remainingProps} variant={loadingDotsVariant} />
405
+ }
406
+
407
+ function ButtonChildrenWrapper({
408
+ loading,
409
+ children,
410
+ variant,
411
+ }: Pick<ButtonProps, 'loading' | 'children' | 'variant'>) {
412
+ if (loading) {
413
+ return (
414
+ <>
415
+ <ButtonLoadingDots buttonVariant={variant} />
416
+ <TextHidden>{children}</TextHidden>
417
+ </>
418
+ )
419
+ }
420
+
421
+ return <>{children}</>
422
+ }
423
+
424
+ export const BaseButton = forwardRef<HTMLButtonElement, ButtonProps>(function BaseButton(
425
+ {
426
+ scale = 'default',
427
+ children,
428
+ leading,
429
+ trailing,
430
+ loading,
431
+ disabled,
432
+ variant,
433
+ ...others
434
+ }: ButtonProps,
364
435
  ref: ForwardedRef<HTMLButtonElement>
365
436
  ) {
366
437
  return (
367
- <BaseStyledButton ref={ref} {...others} $scale={scale}>
368
- {leading && <Leading aria-hidden="true">{leading}</Leading>}
369
- <Children>{children}</Children>
370
- {trailing && <Trailing aria-hidden="true">{trailing}</Trailing>}
438
+ <BaseStyledButton
439
+ ref={ref}
440
+ {...others}
441
+ aria-disabled={loading ? 'true' : undefined}
442
+ disabled={disabled}
443
+ $scale={scale}
444
+ $loading={loading}
445
+ >
446
+ <ButtonChildrenWrapper loading={loading} variant={variant}>
447
+ {leading && <Leading aria-hidden="true">{leading}</Leading>}
448
+ <Children loading={loading}>{children}</Children>
449
+ {trailing && <Trailing aria-hidden="true">{trailing}</Trailing>}
450
+ </ButtonChildrenWrapper>
371
451
  </BaseStyledButton>
372
452
  )
373
453
  })
@@ -381,6 +461,8 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
381
461
  children,
382
462
  leading,
383
463
  trailing,
464
+ disabled,
465
+ loading,
384
466
  ...others
385
467
  }: ButtonProps,
386
468
  ref: ForwardedRef<HTMLButtonElement>
@@ -390,13 +472,18 @@ const Button = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
390
472
  ref={ref}
391
473
  {...others}
392
474
  type={type}
475
+ aria-disabled={loading ? 'true' : undefined}
476
+ disabled={disabled}
393
477
  $scheme={scheme}
394
478
  $scale={scale}
395
479
  $variant={variant}
480
+ $loading={loading}
396
481
  >
397
- {leading && <Leading>{leading}</Leading>}
398
- <Children>{children}</Children>
399
- {trailing && <Trailing>{trailing}</Trailing>}
482
+ <ButtonChildrenWrapper loading={loading} variant={variant}>
483
+ {leading && <Leading>{leading}</Leading>}
484
+ <Children>{children}</Children>
485
+ {trailing && <Trailing>{trailing}</Trailing>}
486
+ </ButtonChildrenWrapper>
400
487
  </StyledButton>
401
488
  )
402
489
  })
@@ -405,7 +492,7 @@ export function Caret(props: Omit<IconProps, 'name'>): JSX.Element {
405
492
  return <Icon size={20} {...props} name="caret-down" />
406
493
  }
407
494
 
408
- export const SelectorButton = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
495
+ export const SelectorButton = forwardRef<HTMLButtonElement, ButtonProps>(function SelectorButton(
409
496
  {
410
497
  scheme = 'light',
411
498
  scale = 'default',
@@ -413,6 +500,8 @@ export const SelectorButton = forwardRef<HTMLButtonElement, ButtonProps>(functio
413
500
  variant = 'secondary',
414
501
  children,
415
502
  trailing,
503
+ disabled,
504
+ loading,
416
505
  ...others
417
506
  }: ButtonProps,
418
507
  ref: ForwardedRef<HTMLButtonElement>
@@ -424,18 +513,25 @@ export const SelectorButton = forwardRef<HTMLButtonElement, ButtonProps>(functio
424
513
  ref={ref}
425
514
  {...others}
426
515
  type={type}
516
+ aria-disabled={loading ? 'true' : undefined}
517
+ disabled={disabled}
518
+ $loading={loading}
427
519
  $scheme={scheme}
428
520
  $scale={scale}
429
521
  $variant={variant}
430
522
  >
431
- <Children>{children}</Children>
432
- {trailing && <Trailing>{trailing}</Trailing>}
523
+ <ButtonChildrenWrapper loading={loading} variant={variant}>
524
+ <Children>{children}</Children>
525
+ {trailing && <Trailing>{trailing}</Trailing>}
526
+ </ButtonChildrenWrapper>
433
527
  </StyledSelector>
434
528
  )
435
529
  })
436
530
 
437
- export const IconButton = forwardRef<HTMLButtonElement, ButtonProps>(function Button(
438
- { scheme = 'light', scale = 'default', type = 'button', children, ...others }: ButtonProps,
531
+ export type IconButtonProps = Omit<ButtonProps, 'leading' | 'trailing' | 'variant' | 'loading'>
532
+
533
+ export const IconButton = forwardRef<HTMLButtonElement, ButtonProps>(function IconButton(
534
+ { scheme = 'light', scale = 'default', type = 'button', children, ...others }: IconButtonProps,
439
535
  ref: ForwardedRef<HTMLButtonElement>
440
536
  ) {
441
537
  others = omit<ButtonProps>(others, ['leading', 'trailing', 'variant'])
@@ -22,7 +22,7 @@ export const SelectableKeyTypeOptions: { label: string; value: SelectableKeyType
22
22
  { label: 'String', value: 'string' },
23
23
  ]
24
24
 
25
- export const FRUITS: Fruit[] = [
25
+ export const FRUITS: Readonly<Fruit[]> = Object.freeze([
26
26
  { label: 'Acerola', value: 'acerola', family: 'Malpighiaceae' },
27
27
  { label: 'Apple', value: 'apple', family: 'Rosaceae' },
28
28
  { label: 'Apricots', value: 'apricots', family: 'Rosaceae' },
@@ -93,7 +93,7 @@ export const FRUITS: Fruit[] = [
93
93
  { label: 'Tamarind', value: 'tamarind', family: 'Fabaceae' },
94
94
  { label: 'Tangerine', value: 'tangerine', family: 'Rutaceae' },
95
95
  { label: 'Watermelon', value: 'watermelon', family: 'Cucurbitaceae' },
96
- ]
96
+ ])
97
97
 
98
98
  function generateSelectOptions() {
99
99
  return generator.array(() => {
@@ -441,11 +441,13 @@ const MixedCustomOption = ({ value }: SelectOptionProps) => {
441
441
  },
442
442
  }
443
443
 
444
+ const fruits = [...FRUITS]
445
+
444
446
  export const CreatableSync: Story<SelectProps> = (args: SelectProps) => {
445
447
  const handleCreate = useCallback(function handleCreate(query: string) {
446
448
  const item: Option = { label: query, value: query } as Option
447
449
 
448
- FRUITS.push(item as Fruit)
450
+ fruits.push(item as Fruit)
449
451
  return item
450
452
  }, [])
451
453
 
@@ -466,7 +468,7 @@ export const CreatableSync: Story<SelectProps> = (args: SelectProps) => {
466
468
  </div>
467
469
  <div className="text-sm" style={{ width: 720 }}>
468
470
  <p>Available options:</p>
469
- <code>{FRUITS.map(({ label }) => label).join(', ')}</code>
471
+ <code>{fruits.map(({ label }) => label).join(', ')}</code>
470
472
  </div>
471
473
  </div>
472
474
  )
@@ -604,7 +606,7 @@ export const CustomCreatableOption: Story<SelectProps> = ({ onCreate, ...args }:
604
606
  const item: Option = { label: query, value: query } as Option
605
607
 
606
608
  setValue(item)
607
- FRUITS.push(item as Fruit)
609
+ fruits.push(item as Fruit)
608
610
  },
609
611
  [onCreate]
610
612
  )
@@ -631,7 +633,7 @@ export const CustomCreatableOption: Story<SelectProps> = ({ onCreate, ...args }:
631
633
  </div>
632
634
  <div className="text-sm" style={{ width: 720 }}>
633
635
  <p>Available options:</p>
634
- <code>{FRUITS.map(({ label }) => label).join(', ')}</code>
636
+ <code>{fruits.map(({ label }) => label).join(', ')}</code>
635
637
  </div>
636
638
  </div>
637
639
  )
@@ -9,7 +9,7 @@ import generator from '../../../tests/generator'
9
9
  import renderer, { screen, fire, waitFor } from '../../../tests/renderer'
10
10
  import selectEvent from '../../testing/SelectEvent'
11
11
 
12
- import type { SelectProps, Option, GenericOption } from './Select.types'
12
+ import type { SelectProps, Option } from './Select.types'
13
13
  import Select from './Select'
14
14
  import userEvent from '@testing-library/user-event'
15
15
  import type { Selectable } from 'hooks/useSelectable'
@@ -142,7 +142,7 @@ describe('Select', () => {
142
142
  expect(screen.getByTestId('select-trigger-handle')).toBeDisabled()
143
143
  expect(screen.queryByRole('listbox')).not.toBeInTheDocument()
144
144
 
145
- rerender(<Playground options={FRUITS as GenericOption[]} />)
145
+ rerender(<Playground options={[...FRUITS]} />)
146
146
 
147
147
  await selectEvent.expand(searchInput)
148
148
 
@@ -213,10 +213,7 @@ describe('Select', () => {
213
213
  setup({})
214
214
 
215
215
  const searchInput = screen.getByLabelText('Select your favorite fruit')
216
- const selectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
217
- label: string
218
- value: string
219
- }
216
+ const selectedFruit = generator.pickone([...FRUITS])
220
217
 
221
218
  await selectEvent.select(selectedFruit.label, searchInput)
222
219
 
@@ -229,10 +226,7 @@ describe('Select', () => {
229
226
  })
230
227
 
231
228
  it('initializes with a selected item', async () => {
232
- const selectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
233
- label: string
234
- value: string
235
- }
229
+ const selectedFruit = generator.pickone([...FRUITS])
236
230
 
237
231
  setup({
238
232
  value: selectedFruit as Option,
@@ -248,10 +242,7 @@ describe('Select', () => {
248
242
  })
249
243
 
250
244
  it('updates selected item after the initially selected item changes', async () => {
251
- const selectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
252
- label: string
253
- value: string
254
- }
245
+ const selectedFruit = generator.pickone([...FRUITS])
255
246
  const props = {
256
247
  value: selectedFruit as Option,
257
248
  }
@@ -265,10 +256,7 @@ describe('Select', () => {
265
256
 
266
257
  expect(selectedOptions[0]).toHaveTextContent(selectedFruit.label)
267
258
 
268
- const newSelectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
269
- label: string
270
- value: string
271
- }
259
+ const newSelectedFruit = generator.pickone([...FRUITS])
272
260
  const newProps = {
273
261
  value: newSelectedFruit as Option,
274
262
  }
@@ -284,10 +272,7 @@ describe('Select', () => {
284
272
  })
285
273
 
286
274
  it('unselects the clicked item', async () => {
287
- const selectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
288
- label: string
289
- value: string
290
- }
275
+ const selectedFruit = generator.pickone([...FRUITS])
291
276
 
292
277
  setup({
293
278
  value: selectedFruit as Option,
@@ -308,10 +293,7 @@ describe('Select', () => {
308
293
  })
309
294
 
310
295
  it('clears selection', async () => {
311
- const selectedFruit = generator.pick(FRUITS, { amount: 1 }) as {
312
- label: string
313
- value: string
314
- }
296
+ const selectedFruit = generator.pickone([...FRUITS])
315
297
 
316
298
  setup({
317
299
  value: selectedFruit as Option,
@@ -506,7 +488,7 @@ describe('Select', () => {
506
488
  setup({})
507
489
 
508
490
  const searchInput = screen.getByLabelText('Select your favorite fruit')
509
- const selectedFruit = generator.pick<Fruit>(FRUITS, { amount: 1 }) as Fruit
491
+ const selectedFruit = generator.pickone<Fruit>([...FRUITS])
510
492
  const optionText = getOptionText(selectedFruit)
511
493
 
512
494
  await selectEvent.select(optionText, searchInput)
@@ -520,23 +502,21 @@ describe('Select', () => {
520
502
  it('selects multiple clicked custom items', async () => {
521
503
  setup({ multiple: true })
522
504
 
523
- const amount = 2
505
+ const SELECTED_OPTIONS_AMOUNT = 2
524
506
  const searchInput = screen.getByLabelText('Select your favorite fruit')
525
- const options = generator.pick<Fruit>(FRUITS, { amount }) as Fruit[]
507
+ const options = generator.pickset([...FRUITS], SELECTED_OPTIONS_AMOUNT)
526
508
 
527
- for (let i = 0; i < options.length; i++) {
528
- const option = options[i]
509
+ for await (const option of options) {
529
510
  const optionText = getOptionText(option)
530
511
  await selectEvent.select(optionText, searchInput)
531
512
  }
532
513
 
533
514
  const selectedOptions = await selectEvent.getSelectedOptions(searchInput)
534
- expect(selectedOptions).toHaveLength(amount)
515
+ expect(selectedOptions).toHaveLength(SELECTED_OPTIONS_AMOUNT)
535
516
 
536
- for (let i = 0; i < options.length; i++) {
537
- const option = options[i]
517
+ for await (const [index, option] of options.entries()) {
538
518
  const optionText = getOptionText(option)
539
- expect(selectedOptions[i]).toHaveTextContent(optionText)
519
+ expect(selectedOptions[index]).toHaveTextContent(optionText)
540
520
  }
541
521
  })
542
522
 
@@ -642,7 +622,7 @@ describe('Select', () => {
642
622
  'renders creatable option at the end of the list when the entered query is not equal one of the available options - %s',
643
623
  async (args) => {
644
624
  setup({ ...args })
645
- const { label: availableOption } = generator.pick(FRUITS, { amount: 1 }) as Fruit
625
+ const { label: availableOption } = generator.pickone([...FRUITS])
646
626
  const query = availableOption.slice(0, availableOption.length - 1)
647
627
 
648
628
  const searchInput = screen.getByLabelText('Select your favorite fruit')
@@ -664,7 +644,7 @@ describe('Select', () => {
664
644
  onCreate: (null as unknown) as undefined,
665
645
  ...args,
666
646
  })
667
- const { label: availableOption } = generator.pick(FRUITS, { amount: 1 }) as Fruit
647
+ const { label: availableOption } = generator.pickone([...FRUITS])
668
648
  const query = availableOption.slice(0, availableOption.length - 1)
669
649
 
670
650
  const searchInput = screen.getByLabelText('Select your favorite fruit')
@@ -685,7 +665,7 @@ describe('Select', () => {
685
665
  setup({
686
666
  ...args,
687
667
  })
688
- const { label: availableOption } = generator.pick(FRUITS, { amount: 1 }) as Fruit
668
+ const { label: availableOption } = generator.pickone([...FRUITS])
689
669
 
690
670
  const searchInput = screen.getByLabelText('Select your favorite fruit')
691
671
  fire.change(searchInput, {
@@ -705,7 +685,7 @@ describe('Select', () => {
705
685
  setup({
706
686
  ...args,
707
687
  })
708
- const { label: availableOption } = generator.pick(FRUITS, { amount: 1 }) as Fruit
688
+ const { label: availableOption } = generator.pickone([...FRUITS])
709
689
 
710
690
  await expandSelect()
711
691
 
@@ -865,7 +845,7 @@ describe('Select', () => {
865
845
  )
866
846
  }
867
847
 
868
- const value = generator.pickone(FRUITS) as Selectable
848
+ const value = generator.pickone([...FRUITS]) as Selectable
869
849
 
870
850
  setup({
871
851
  ...args,
@@ -881,6 +861,28 @@ describe('Select', () => {
881
861
  expect(await screen.findByText(/add item/i)).toBeVisible()
882
862
  }
883
863
  )
864
+
865
+ it.each([[{ multiple: false }], [{ multiple: true }]])(
866
+ 'show creatable option on the list even when option list is empty - %s',
867
+ async (args) => {
868
+ const CreatableOption = () => {
869
+ return <Select.CreatableOption>Add item</Select.CreatableOption>
870
+ }
871
+
872
+ setup({
873
+ ...args,
874
+ isValidNewOption: true,
875
+ createOptionPosition: 'first',
876
+ options: [],
877
+ components: {
878
+ CreatableOption,
879
+ },
880
+ })
881
+
882
+ await expandSelect(true)
883
+ expect(await screen.findByText(/add item/i)).toBeVisible()
884
+ }
885
+ )
884
886
  })
885
887
 
886
888
  describe('Creatable Async', () => {