@loadsmart/loadsmart-ui 5.6.3 → 5.8.1

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.
@@ -11,5 +11,6 @@ declare type TokenLike<P extends ThemedProps = ThemedProps> = ThemeToken | ((pro
11
11
  * @param {[ThemedStyledProps]} props - Component props.
12
12
  * @returns {ThemeTokenValue} Token value or `undefined` if the token was not found for the current theme.
13
13
  */
14
- export declare function getToken<P extends ThemedProps>(token: TokenLike<P>, props?: P): ThemeTokenValue | ((props: P) => ThemeTokenValue);
14
+ export declare function getToken<TProps extends ThemedProps>(token: TokenLike<TProps>): (props: TProps) => ThemeTokenValue;
15
+ export declare function getToken<TProps extends ThemedProps>(token: TokenLike<TProps>, props: TProps): ThemeTokenValue;
15
16
  export * as Themes from './themes';
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../../src/theming/index.ts"],"sourcesContent":["import { ThemeProps } from 'styled-components'\nimport { isFunction } from '@loadsmart/utils-function'\nimport ThemeAlice from './themes/alice.theme'\n\nexport type ThemeToken = keyof typeof ThemeAlice\nexport type ThemeTokenValue = string | number\n\nexport type CustomTheme = Record<ThemeToken, ThemeTokenValue>\nexport type ThemedProps = ThemeProps<CustomTheme>\n\ntype TokenLike<P extends ThemedProps = ThemedProps> = ThemeToken | ((props: P) => ThemeToken)\n\nfunction getTokenFromTheme<P extends ThemedProps>(token: TokenLike<P>, props: P): ThemeTokenValue {\n const tokenName = isFunction(token) ? token(props) : token\n return props.theme[tokenName]\n}\n\n/**\n * Return a design token value for the current theme.\n * @param {string} token - Token whose value should be returned.\n * @param {[ThemedStyledProps]} props - Component props.\n * @returns {ThemeTokenValue} Token value or `undefined` if the token was not found for the current theme.\n */\nexport function getToken<P extends ThemedProps>(\n token: TokenLike<P>,\n props?: P\n): ThemeTokenValue | ((props: P) => ThemeTokenValue) {\n if (props === undefined) {\n return (props) => getTokenFromTheme(token, props)\n }\n\n return getTokenFromTheme(token, props)\n}\n\nexport * as Themes from './themes'\n"],"names":["getTokenFromTheme","token","props","tokenName","isFunction","theme","undefined"],"mappings":"0LAYA,SAASA,EAAyCC,EAAqBC,GACrE,MAAMC,EAAYC,aAAWH,GAASA,EAAMC,GAASD,EACrD,OAAOC,EAAMG,MAAMF,8CAUnBF,EACAC,GAEA,YAAcI,IAAVJ,EACMA,GAAUF,EAAkBC,EAAOC,GAGtCF,EAAkBC,EAAOC"}
1
+ {"version":3,"file":"index.js","sources":["../../src/theming/index.ts"],"sourcesContent":["import { ThemeProps } from 'styled-components'\nimport { isFunction } from '@loadsmart/utils-function'\nimport ThemeAlice from './themes/alice.theme'\n\nexport type ThemeToken = keyof typeof ThemeAlice\nexport type ThemeTokenValue = string | number\n\nexport type CustomTheme = Record<ThemeToken, ThemeTokenValue>\nexport type ThemedProps = ThemeProps<CustomTheme>\n\ntype TokenLike<P extends ThemedProps = ThemedProps> = ThemeToken | ((props: P) => ThemeToken)\n\nfunction getTokenFromTheme<P extends ThemedProps>(token: TokenLike<P>, props: P): ThemeTokenValue {\n const tokenName = isFunction(token) ? token(props) : token\n return props.theme[tokenName]\n}\n\n/**\n * Return a design token value for the current theme.\n * @param {string} token - Token whose value should be returned.\n * @param {[ThemedStyledProps]} props - Component props.\n * @returns {ThemeTokenValue} Token value or `undefined` if the token was not found for the current theme.\n */\nexport function getToken<TProps extends ThemedProps>(\n token: TokenLike<TProps>\n): (props: TProps) => ThemeTokenValue\n\nexport function getToken<TProps extends ThemedProps>(\n token: TokenLike<TProps>,\n props: TProps\n): ThemeTokenValue\n\nexport function getToken<TProps extends ThemedProps>(token: any, props?: any): any {\n if (props === undefined) {\n return (props: TProps) => getTokenFromTheme(token, props)\n }\n\n return getTokenFromTheme(token, props)\n}\n\nexport * as Themes from './themes'\n"],"names":["getTokenFromTheme","token","props","tokenName","isFunction","theme","undefined"],"mappings":"0LAYA,SAASA,EAAyCC,EAAqBC,GACrE,MAAMC,EAAYC,aAAWH,GAASA,EAAMC,GAASD,EACrD,OAAOC,EAAMG,MAAMF,8CAkBgCF,EAAYC,GAC/D,YAAcI,IAAVJ,EACMA,GAAkBF,EAAkBC,EAAOC,GAG9CF,EAAkBC,EAAOC"}
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("../prop-0c635ee9.js");require("../toArray-b56541b4.js"),require("../loadsmart.theme-63c13988.js"),require("../theming/index.js"),exports.conditional=e.conditional,exports.prop=e.prop,exports.whenProps=e.whenProps;
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});var e=require("../prop-0f94ff83.js");require("../toArray-b56541b4.js"),require("../loadsmart.theme-63c13988.js"),require("../theming/index.js"),exports.conditional=e.conditional,exports.prop=e.prop,exports.whenProps=e.whenProps;
2
2
  //# sourceMappingURL=index.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loadsmart/loadsmart-ui",
3
- "version": "5.6.3",
3
+ "version": "5.8.1",
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
+ }
@@ -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', async () => {
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
- await expandSelect()
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', async () => {
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
- await expandSelect()
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.queryByRole('listbox')
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.queryByRole('option')).not.toBeInTheDocument()
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,77 @@ 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
+ )
788
884
  })
789
885
 
790
886
  describe('Creatable Async', () => {