@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadsmart/loadsmart-ui",
3
- "version": "5.7.0",
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": {
@@ -3,6 +3,7 @@ import type { DropdownContextReturn } from './Dropdown.types'
3
3
 
4
4
  const DropdownContext = createContext<DropdownContextReturn>({
5
5
  disabled: false,
6
+ expandDisabled: false,
6
7
  expanded: false,
7
8
  toggle: function () {
8
9
  // nothing
@@ -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 { children, expanded, toggle, disabled = false, onBlur, ...others } = props
43
- const [contextValue, setContextValue] = useState({ expanded, toggle, disabled })
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
- [disabled, expanded, toggle]
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 { toggle, disabled, expanded } = useContext(DropdownContext)
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({ toggle, disabled, expanded })
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 }
@@ -136,7 +136,7 @@ export function useAsyncUsers(): SelectDatasource<{ uuid: string; name: string }
136
136
  return new Promise((resolve) => {
137
137
  setTimeout(() => {
138
138
  resolve(USERS.filter(({ name }) => regex.test(name)))
139
- }, 2500)
139
+ }, 1500)
140
140
  })
141
141
  },
142
142
  }
@@ -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', async () => {
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
- await expandSelect()
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', async () => {
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
- await expandSelect()
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.queryByRole('listbox')
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.queryByRole('option')).not.toBeInTheDocument()
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 !== 'querying') {
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 === 'querying' ? 'Loading...' : `${after.length} option${pluralize(after.length)}`}
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 === 'querying' && isEmpty(select.options)) {
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
- {isCreatable && <CreatableOption />}
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
- {isCreatable && <CreatableOption />}
186
+ {select.createOptionPosition === 'last' && isCreatable ? <CreatableOption /> : null}
181
187
  </Fragment>
182
188
  )
183
189
 
184
- if (select.status !== 'querying' && isEmpty(remainingOptions)) {
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 !== 'querying' && isEmpty(select.options)) {
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 === 'querying') {
223
+ if (isQuerying(select.status)) {
218
224
  return <Loading data-testid="select-trigger-loading">&middot;&middot;&middot;</Loading>
219
225
  }
220
226