@availity/mui-autocomplete 0.5.2 → 0.6.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.
- package/CHANGELOG.md +19 -0
- package/README.md +28 -6
- package/dist/index.d.mts +31 -7
- package/dist/index.d.ts +31 -7
- package/dist/index.js +84 -46
- package/dist/index.mjs +82 -45
- package/package.json +3 -1
- package/src/index.ts +1 -0
- package/src/lib/AsyncAutocomplete.stories.tsx +29 -23
- package/src/lib/AsyncAutocomplete.test.tsx +22 -7
- package/src/lib/AsyncAutocomplete.tsx +20 -29
- package/src/lib/OrganizationAutocomplete.stories.tsx +14 -1
- package/src/lib/OrganizationAutocomplete.test.tsx +9 -1
- package/src/lib/OrganizationAutocomplete.tsx +27 -20
- package/src/lib/ProviderAutocomplete.stories.tsx +51 -0
- package/src/lib/ProviderAutocomplete.test.tsx +55 -0
- package/src/lib/ProviderAutocomplete.tsx +64 -0
- package/src/lib/util.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
4
|
|
|
5
|
+
## [0.6.1](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.6.0...@availity/mui-autocomplete@0.6.1) (2024-07-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **mui-autocomplete:** and provider export ([a5e6703](https://github.com/Availity/element/commit/a5e6703fb5544f4e2058c2128b3c8bca2a1b01b9))
|
|
11
|
+
|
|
12
|
+
## [0.6.0](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.5.2...@availity/mui-autocomplete@0.6.0) (2024-07-01)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
* **mui-autocomplete:** add ProviderAutocomplete component ([5ed1450](https://github.com/Availity/element/commit/5ed1450a6daca47231a030b516b5bf950c190f08))
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **mui-autocomplete:** fix unit test ([50e2b52](https://github.com/Availity/element/commit/50e2b52f2a0b7799fe63d4d140259255d905a96f))
|
|
23
|
+
|
|
5
24
|
## [0.5.2](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.5.1...@availity/mui-autocomplete@0.5.2) (2024-06-27)
|
|
6
25
|
|
|
7
26
|
### Dependency Updates
|
package/README.md
CHANGED
|
@@ -48,8 +48,6 @@ yarn add @availity/mui-autocomplete
|
|
|
48
48
|
|
|
49
49
|
### Usage
|
|
50
50
|
|
|
51
|
-
#### Import through @availity/element
|
|
52
|
-
|
|
53
51
|
The `Autcomplete` component can be used standalone or with a form state library like [react-hook-form](https://react-hook-form.com/).
|
|
54
52
|
|
|
55
53
|
`Autocomplete` uses the `TextField` component to render the input. You must pass your field related props: `label`, `helperText`, `error`, etc. to the the `FieldProps` prop.
|
|
@@ -121,18 +119,24 @@ const Form = () => {
|
|
|
121
119
|
|
|
122
120
|
#### `AsyncAutocomplete` Usage
|
|
123
121
|
|
|
124
|
-
An `AsyncAutocomplete` component is exported for use cases that require fetching paginated results from an api.
|
|
122
|
+
An `AsyncAutocomplete` component is exported for use cases that require fetching paginated results from an api. It uses the `useInfiniteQuery` from [@tanstack/react-query](https://tanstack.com/query/v4/docs/framework/react/guides/infinite-queries). The component requires two props to work properly: `loadOptions` and `queryKey`. The `loadOptions` prop is the function that is called to fetch the options. The `queryKey` is used as the key to cache the results. You can use this key to interact with the data in the query client.
|
|
123
|
+
|
|
124
|
+
The `loadOptions` function will be called when the user scrolls to the bottom of the dropdown. It will be passed the current offset and limit. The `limit` prop controls what is passed to `loadOptions` and is defaulted to `50`. The `loadOptions` function must return an object that has an array of `options`, a boolean `hasMore` property, and the `offset`. The returned `options` will be concatenated to the existing options array. `hasMore` tells the `AsyncAutocomplete` component whether or not it should call `loadOptions` again. Finally, the returned `offset` will be passed in the subsequent call to get the next set of options.
|
|
125
|
+
|
|
126
|
+
The `queryOptions` prop is available for passing in options to the `useInfiniteQuery` hook. One example of how this can be used is by using the `enabled` property. This can be used in cases where you would like to render the autocomplete, but are waiting on fetching the options. For example, if you need the user to fill out a section of the form before fetching the options for the autocomplete.
|
|
125
127
|
|
|
126
128
|
```jsx
|
|
127
129
|
import { Autocomplete } from '@availity/element';
|
|
130
|
+
import { callApi } from '../api';
|
|
128
131
|
|
|
129
132
|
const Example = () => {
|
|
130
|
-
const loadOptions = async (
|
|
131
|
-
const response = await callApi(
|
|
133
|
+
const loadOptions = async (offset: number, limit: number) => {
|
|
134
|
+
const response = await callApi(offset, limit);
|
|
132
135
|
|
|
133
136
|
return {
|
|
134
137
|
options: repsonse.data,
|
|
135
|
-
hasMore: response.totalCount > response.count,
|
|
138
|
+
hasMore: response.data.totalCount > response.data.count,
|
|
139
|
+
offset,
|
|
136
140
|
};
|
|
137
141
|
};
|
|
138
142
|
|
|
@@ -146,6 +150,8 @@ The `OrganizationAutocomplete` component is an extension of the `AsyncAutocomple
|
|
|
146
150
|
|
|
147
151
|
If you need to add params, headers, or other data to the api call then the `apiConfig` prop is available. This allows for passing in the same options you would to the `getOrganizations`. For example, `permissionIds` or `resourceIds`.
|
|
148
152
|
|
|
153
|
+
The `queryKey` by default is `org-autocomplete`.
|
|
154
|
+
|
|
149
155
|
```jsx
|
|
150
156
|
import { OrganizationAutocomplete } from '@availity/element';
|
|
151
157
|
|
|
@@ -153,3 +159,19 @@ const Example = () => {
|
|
|
153
159
|
return <OrganizationAutocomplete FieldProps={{ label: 'Organization Select', placeholder: 'Select...' }} />;
|
|
154
160
|
};
|
|
155
161
|
```
|
|
162
|
+
|
|
163
|
+
#### `ProviderAutocomplete` Usage
|
|
164
|
+
|
|
165
|
+
The `ProviderAutocomplete` component is an extension of the `AsyncAutocomplete` component which calls our Providers endpoint. The props are the same except you do not need to pass a function to `loadOptions`. This has already been done for you. The component uses the `uiDisplayName` as the default label for the options. This can be changed through the `getOptionLabel` function.
|
|
166
|
+
|
|
167
|
+
`ProviderAutocomplete` requires a `customerId` to call the api. You can pass it in as prop that the component will then use in the api call. There is also an `apiConfig` prop available for further customizing the call.
|
|
168
|
+
|
|
169
|
+
The `queryKey` by default is `prov-autocomplete`.
|
|
170
|
+
|
|
171
|
+
```jsx
|
|
172
|
+
import { ProviderAutocomplete } from '@availity/element';
|
|
173
|
+
|
|
174
|
+
const Example = () => {
|
|
175
|
+
return <ProviderAutocomplete customerId="1234" FieldProps={{ label: 'Provider Select', placeholder: 'Select...' }} />;
|
|
176
|
+
};
|
|
177
|
+
```
|
package/dist/index.d.mts
CHANGED
|
@@ -3,6 +3,7 @@ import { AutocompleteProps as AutocompleteProps$1 } from '@mui/material/Autocomp
|
|
|
3
3
|
import { ChipTypeMap } from '@mui/material/Chip';
|
|
4
4
|
import { TextFieldProps } from '@availity/mui-textfield';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
|
+
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
|
|
6
7
|
import { ApiConfig } from '@availity/api-axios';
|
|
7
8
|
|
|
8
9
|
interface AutocompleteProps<T, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AutocompleteProps$1<T, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'clearIcon' | 'clearText' | 'closeText' | 'componentsProps' | 'disabledItemsFocusable' | 'forcePopupIcon' | 'fullWidth' | 'handleHomeEndKeys' | 'includeInputInList' | 'openOnFocus' | 'openText' | 'PaperComponent' | 'PopperComponent' | 'popupIcon' | 'selectOnFocus' | 'size' | 'renderInput' | 'slotProps'> {
|
|
@@ -10,19 +11,30 @@ interface AutocompleteProps<T, Multiple extends boolean | undefined, DisableClea
|
|
|
10
11
|
FieldProps?: TextFieldProps;
|
|
11
12
|
name?: string;
|
|
12
13
|
}
|
|
13
|
-
declare const Autocomplete: <T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any> = "div">({ FieldProps, ...props }: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>) => JSX.Element;
|
|
14
|
+
declare const Autocomplete: <T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any, keyof react.JSX.IntrinsicElements> = "div">({ FieldProps, ...props }: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>) => JSX.Element;
|
|
14
15
|
|
|
15
16
|
interface AsyncAutocompleteProps<Option, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'options' | 'disableListWrap' | 'loading'> {
|
|
16
|
-
/** Function that
|
|
17
|
-
loadOptions: (
|
|
17
|
+
/** Function that is called to fetch the options for the list. Returns a promise with options, hasMore, and offset */
|
|
18
|
+
loadOptions: (offset: number, limit: number) => Promise<{
|
|
18
19
|
options: Option[];
|
|
19
20
|
hasMore: boolean;
|
|
21
|
+
offset: number;
|
|
20
22
|
}>;
|
|
23
|
+
/** The key used by @tanstack/react-query to cache the response */
|
|
24
|
+
queryKey: string;
|
|
21
25
|
/** The number of options to request from the api
|
|
22
26
|
* @default 50 */
|
|
23
27
|
limit?: number;
|
|
28
|
+
/** Config options for the useInfiniteQuery hook */
|
|
29
|
+
queryOptions?: UseInfiniteQueryOptions<{
|
|
30
|
+
options: Option[];
|
|
31
|
+
hasMore: boolean;
|
|
32
|
+
offset: number;
|
|
33
|
+
}>;
|
|
24
34
|
}
|
|
25
|
-
declare const AsyncAutocomplete: <Option, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any> = "div">({ loadOptions, limit, ListboxProps, ...rest }: AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>) => react_jsx_runtime.JSX.Element;
|
|
35
|
+
declare const AsyncAutocomplete: <Option, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any, keyof react.JSX.IntrinsicElements> = "div">({ loadOptions, limit, queryKey, ListboxProps, queryOptions, ...rest }: AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>) => react_jsx_runtime.JSX.Element;
|
|
36
|
+
|
|
37
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
26
38
|
|
|
27
39
|
type Organization = {
|
|
28
40
|
customerId: string;
|
|
@@ -31,9 +43,21 @@ type Organization = {
|
|
|
31
43
|
createDate: string;
|
|
32
44
|
links: Record<string, Record<string, string>>;
|
|
33
45
|
};
|
|
34
|
-
interface OrgAutocompleteProps<Option = Organization, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'loadOptions'> {
|
|
46
|
+
interface OrgAutocompleteProps<Option = Organization, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>, 'loadOptions'> {
|
|
47
|
+
apiConfig?: ApiConfig;
|
|
48
|
+
}
|
|
49
|
+
declare const OrganizationAutocomplete: ({ apiConfig, queryKey, ...rest }: OrgAutocompleteProps) => react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
type Provider = {
|
|
52
|
+
id: string;
|
|
53
|
+
businessName: string;
|
|
54
|
+
uiDisplayName: string;
|
|
55
|
+
aytypical: boolean;
|
|
56
|
+
};
|
|
57
|
+
interface ProviderAutocompleteProps<Option = Provider, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>, 'loadOptions'> {
|
|
58
|
+
customerId: string;
|
|
35
59
|
apiConfig?: ApiConfig;
|
|
36
60
|
}
|
|
37
|
-
declare const
|
|
61
|
+
declare const ProviderAutocomplete: ({ apiConfig, customerId, queryKey, ...rest }: ProviderAutocompleteProps) => react_jsx_runtime.JSX.Element;
|
|
38
62
|
|
|
39
|
-
export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete };
|
|
63
|
+
export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete, type Provider, ProviderAutocomplete, type ProviderAutocompleteProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { AutocompleteProps as AutocompleteProps$1 } from '@mui/material/Autocomp
|
|
|
3
3
|
import { ChipTypeMap } from '@mui/material/Chip';
|
|
4
4
|
import { TextFieldProps } from '@availity/mui-textfield';
|
|
5
5
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
6
|
+
import { UseInfiniteQueryOptions } from '@tanstack/react-query';
|
|
6
7
|
import { ApiConfig } from '@availity/api-axios';
|
|
7
8
|
|
|
8
9
|
interface AutocompleteProps<T, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AutocompleteProps$1<T, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'clearIcon' | 'clearText' | 'closeText' | 'componentsProps' | 'disabledItemsFocusable' | 'forcePopupIcon' | 'fullWidth' | 'handleHomeEndKeys' | 'includeInputInList' | 'openOnFocus' | 'openText' | 'PaperComponent' | 'PopperComponent' | 'popupIcon' | 'selectOnFocus' | 'size' | 'renderInput' | 'slotProps'> {
|
|
@@ -10,19 +11,30 @@ interface AutocompleteProps<T, Multiple extends boolean | undefined, DisableClea
|
|
|
10
11
|
FieldProps?: TextFieldProps;
|
|
11
12
|
name?: string;
|
|
12
13
|
}
|
|
13
|
-
declare const Autocomplete: <T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any> = "div">({ FieldProps, ...props }: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>) => JSX.Element;
|
|
14
|
+
declare const Autocomplete: <T, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any, keyof react.JSX.IntrinsicElements> = "div">({ FieldProps, ...props }: AutocompleteProps<T, Multiple, DisableClearable, FreeSolo, ChipComponent>) => JSX.Element;
|
|
14
15
|
|
|
15
16
|
interface AsyncAutocompleteProps<Option, Multiple extends boolean | undefined, DisableClearable extends boolean | undefined, FreeSolo extends boolean | undefined, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'options' | 'disableListWrap' | 'loading'> {
|
|
16
|
-
/** Function that
|
|
17
|
-
loadOptions: (
|
|
17
|
+
/** Function that is called to fetch the options for the list. Returns a promise with options, hasMore, and offset */
|
|
18
|
+
loadOptions: (offset: number, limit: number) => Promise<{
|
|
18
19
|
options: Option[];
|
|
19
20
|
hasMore: boolean;
|
|
21
|
+
offset: number;
|
|
20
22
|
}>;
|
|
23
|
+
/** The key used by @tanstack/react-query to cache the response */
|
|
24
|
+
queryKey: string;
|
|
21
25
|
/** The number of options to request from the api
|
|
22
26
|
* @default 50 */
|
|
23
27
|
limit?: number;
|
|
28
|
+
/** Config options for the useInfiniteQuery hook */
|
|
29
|
+
queryOptions?: UseInfiniteQueryOptions<{
|
|
30
|
+
options: Option[];
|
|
31
|
+
hasMore: boolean;
|
|
32
|
+
offset: number;
|
|
33
|
+
}>;
|
|
24
34
|
}
|
|
25
|
-
declare const AsyncAutocomplete: <Option, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any> = "div">({ loadOptions, limit, ListboxProps, ...rest }: AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>) => react_jsx_runtime.JSX.Element;
|
|
35
|
+
declare const AsyncAutocomplete: <Option, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends react.ElementType<any, keyof react.JSX.IntrinsicElements> = "div">({ loadOptions, limit, queryKey, ListboxProps, queryOptions, ...rest }: AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>) => react_jsx_runtime.JSX.Element;
|
|
36
|
+
|
|
37
|
+
type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
|
|
26
38
|
|
|
27
39
|
type Organization = {
|
|
28
40
|
customerId: string;
|
|
@@ -31,9 +43,21 @@ type Organization = {
|
|
|
31
43
|
createDate: string;
|
|
32
44
|
links: Record<string, Record<string, string>>;
|
|
33
45
|
};
|
|
34
|
-
interface OrgAutocompleteProps<Option = Organization, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'loadOptions'> {
|
|
46
|
+
interface OrgAutocompleteProps<Option = Organization, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>, 'loadOptions'> {
|
|
47
|
+
apiConfig?: ApiConfig;
|
|
48
|
+
}
|
|
49
|
+
declare const OrganizationAutocomplete: ({ apiConfig, queryKey, ...rest }: OrgAutocompleteProps) => react_jsx_runtime.JSX.Element;
|
|
50
|
+
|
|
51
|
+
type Provider = {
|
|
52
|
+
id: string;
|
|
53
|
+
businessName: string;
|
|
54
|
+
uiDisplayName: string;
|
|
55
|
+
aytypical: boolean;
|
|
56
|
+
};
|
|
57
|
+
interface ProviderAutocompleteProps<Option = Provider, Multiple extends boolean | undefined = false, DisableClearable extends boolean | undefined = false, FreeSolo extends boolean | undefined = false, ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent']> extends Omit<Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>, 'loadOptions'> {
|
|
58
|
+
customerId: string;
|
|
35
59
|
apiConfig?: ApiConfig;
|
|
36
60
|
}
|
|
37
|
-
declare const
|
|
61
|
+
declare const ProviderAutocomplete: ({ apiConfig, customerId, queryKey, ...rest }: ProviderAutocompleteProps) => react_jsx_runtime.JSX.Element;
|
|
38
62
|
|
|
39
|
-
export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete };
|
|
63
|
+
export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete, type Provider, ProviderAutocomplete, type ProviderAutocompleteProps };
|
package/dist/index.js
CHANGED
|
@@ -81,7 +81,8 @@ var src_exports = {};
|
|
|
81
81
|
__export(src_exports, {
|
|
82
82
|
AsyncAutocomplete: () => AsyncAutocomplete,
|
|
83
83
|
Autocomplete: () => Autocomplete,
|
|
84
|
-
OrganizationAutocomplete: () => OrganizationAutocomplete
|
|
84
|
+
OrganizationAutocomplete: () => OrganizationAutocomplete,
|
|
85
|
+
ProviderAutocomplete: () => ProviderAutocomplete
|
|
85
86
|
});
|
|
86
87
|
module.exports = __toCommonJS(src_exports);
|
|
87
88
|
|
|
@@ -129,51 +130,42 @@ var Autocomplete = (_a) => {
|
|
|
129
130
|
};
|
|
130
131
|
|
|
131
132
|
// src/lib/AsyncAutocomplete.tsx
|
|
132
|
-
var
|
|
133
|
+
var import_react_query = require("@tanstack/react-query");
|
|
133
134
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
134
135
|
var AsyncAutocomplete = (_a) => {
|
|
135
136
|
var _b = _a, {
|
|
136
137
|
loadOptions,
|
|
137
138
|
limit = 50,
|
|
138
|
-
|
|
139
|
+
queryKey,
|
|
140
|
+
ListboxProps,
|
|
141
|
+
queryOptions
|
|
139
142
|
} = _b, rest = __objRest(_b, [
|
|
140
143
|
"loadOptions",
|
|
141
144
|
"limit",
|
|
142
|
-
"
|
|
145
|
+
"queryKey",
|
|
146
|
+
"ListboxProps",
|
|
147
|
+
"queryOptions"
|
|
143
148
|
]);
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
setHasMore(result.hasMore);
|
|
154
|
-
setPage((prev) => prev + 1);
|
|
155
|
-
setLoading(false);
|
|
156
|
-
});
|
|
157
|
-
if (!loading && hasMore && page === 0) {
|
|
158
|
-
getInitialOptions();
|
|
159
|
-
}
|
|
160
|
-
}, [page, loading, loadOptions]);
|
|
149
|
+
const { isLoading, isFetching, data, hasNextPage, fetchNextPage } = (0, import_react_query.useInfiniteQuery)(__spreadValues({
|
|
150
|
+
queryKey: [queryKey, limit],
|
|
151
|
+
queryFn: (_0) => __async(void 0, [_0], function* ({ pageParam = 0 }) {
|
|
152
|
+
return loadOptions(pageParam, limit);
|
|
153
|
+
}),
|
|
154
|
+
staleTime: 1e4,
|
|
155
|
+
getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.offset + limit : false
|
|
156
|
+
}, queryOptions));
|
|
157
|
+
const options = (data == null ? void 0 : data.pages) ? data.pages.map((page) => page.options).flat() : [];
|
|
161
158
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
162
159
|
Autocomplete,
|
|
163
160
|
__spreadProps(__spreadValues({}, rest), {
|
|
164
|
-
loading,
|
|
161
|
+
loading: isFetching,
|
|
165
162
|
options,
|
|
166
163
|
ListboxProps: __spreadProps(__spreadValues({}, ListboxProps), {
|
|
167
164
|
onScroll: (event) => __async(void 0, null, function* () {
|
|
168
165
|
const listboxNode = event.currentTarget;
|
|
169
166
|
const difference = listboxNode.scrollHeight - (listboxNode.scrollTop + listboxNode.clientHeight);
|
|
170
|
-
if (difference <= 5 && !
|
|
171
|
-
|
|
172
|
-
const result = yield loadOptions(page, limit);
|
|
173
|
-
setOptions([...options, ...result.options]);
|
|
174
|
-
setHasMore(result.hasMore);
|
|
175
|
-
setPage((prev) => prev + 1);
|
|
176
|
-
setLoading(false);
|
|
167
|
+
if (difference <= 5 && !isLoading && !isFetching && hasNextPage) {
|
|
168
|
+
fetchNextPage();
|
|
177
169
|
}
|
|
178
170
|
})
|
|
179
171
|
})
|
|
@@ -185,32 +177,78 @@ var AsyncAutocomplete = (_a) => {
|
|
|
185
177
|
var import_api_axios = require("@availity/api-axios");
|
|
186
178
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
187
179
|
var fetchOrgs = (config) => __async(void 0, null, function* () {
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
} catch (e) {
|
|
195
|
-
return {
|
|
196
|
-
options: [],
|
|
197
|
-
hasMore: false
|
|
198
|
-
};
|
|
199
|
-
}
|
|
180
|
+
const resp = yield import_api_axios.avOrganizationsApi.getOrganizations(config);
|
|
181
|
+
return {
|
|
182
|
+
options: resp.data.organizations,
|
|
183
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
184
|
+
offset: config.params.offset
|
|
185
|
+
};
|
|
200
186
|
});
|
|
201
187
|
var OrganizationAutocomplete = (_a) => {
|
|
202
|
-
var _b = _a, {
|
|
203
|
-
|
|
204
|
-
|
|
188
|
+
var _b = _a, {
|
|
189
|
+
apiConfig = {},
|
|
190
|
+
queryKey = "org-autocomplete"
|
|
191
|
+
} = _b, rest = __objRest(_b, [
|
|
192
|
+
"apiConfig",
|
|
193
|
+
"queryKey"
|
|
194
|
+
]);
|
|
195
|
+
const handleLoadOptions = (offset, limit) => __async(void 0, null, function* () {
|
|
205
196
|
const resp = yield fetchOrgs(__spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({ dropdown: true }, apiConfig.params), { offset, limit }) }));
|
|
206
197
|
return resp;
|
|
207
198
|
});
|
|
208
199
|
const handleGetOptionLabel = (org) => org.name;
|
|
209
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
200
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
201
|
+
AsyncAutocomplete,
|
|
202
|
+
__spreadProps(__spreadValues({
|
|
203
|
+
getOptionLabel: handleGetOptionLabel,
|
|
204
|
+
queryKey
|
|
205
|
+
}, rest), {
|
|
206
|
+
loadOptions: handleLoadOptions
|
|
207
|
+
})
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
// src/lib/ProviderAutocomplete.tsx
|
|
212
|
+
var import_api_axios2 = require("@availity/api-axios");
|
|
213
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
214
|
+
var fetchProviders = (customerId, config) => __async(void 0, null, function* () {
|
|
215
|
+
const resp = yield import_api_axios2.avProvidersApi.getProviders(customerId, config);
|
|
216
|
+
return {
|
|
217
|
+
options: resp.data.providers,
|
|
218
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
219
|
+
offset: config.params.offset
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
var ProviderAutocomplete = (_a) => {
|
|
223
|
+
var _b = _a, {
|
|
224
|
+
apiConfig = {},
|
|
225
|
+
customerId,
|
|
226
|
+
queryKey = "prov-autocomplete"
|
|
227
|
+
} = _b, rest = __objRest(_b, [
|
|
228
|
+
"apiConfig",
|
|
229
|
+
"customerId",
|
|
230
|
+
"queryKey"
|
|
231
|
+
]);
|
|
232
|
+
const handleLoadOptions = (offset, limit) => __async(void 0, null, function* () {
|
|
233
|
+
const resp = yield fetchProviders(customerId, __spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({}, apiConfig.params), { offset, limit }) }));
|
|
234
|
+
return resp;
|
|
235
|
+
});
|
|
236
|
+
const handleGetOptionLabel = (option) => option.uiDisplayName;
|
|
237
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
238
|
+
AsyncAutocomplete,
|
|
239
|
+
__spreadProps(__spreadValues({
|
|
240
|
+
getOptionLabel: handleGetOptionLabel,
|
|
241
|
+
queryOptions: { enabled: !!customerId },
|
|
242
|
+
queryKey
|
|
243
|
+
}, rest), {
|
|
244
|
+
loadOptions: handleLoadOptions
|
|
245
|
+
})
|
|
246
|
+
);
|
|
210
247
|
};
|
|
211
248
|
// Annotate the CommonJS export names for ESM import in node:
|
|
212
249
|
0 && (module.exports = {
|
|
213
250
|
AsyncAutocomplete,
|
|
214
251
|
Autocomplete,
|
|
215
|
-
OrganizationAutocomplete
|
|
252
|
+
OrganizationAutocomplete,
|
|
253
|
+
ProviderAutocomplete
|
|
216
254
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -96,51 +96,42 @@ var Autocomplete = (_a) => {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
// src/lib/AsyncAutocomplete.tsx
|
|
99
|
-
import {
|
|
99
|
+
import { useInfiniteQuery } from "@tanstack/react-query";
|
|
100
100
|
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
101
101
|
var AsyncAutocomplete = (_a) => {
|
|
102
102
|
var _b = _a, {
|
|
103
103
|
loadOptions,
|
|
104
104
|
limit = 50,
|
|
105
|
-
|
|
105
|
+
queryKey,
|
|
106
|
+
ListboxProps,
|
|
107
|
+
queryOptions
|
|
106
108
|
} = _b, rest = __objRest(_b, [
|
|
107
109
|
"loadOptions",
|
|
108
110
|
"limit",
|
|
109
|
-
"
|
|
111
|
+
"queryKey",
|
|
112
|
+
"ListboxProps",
|
|
113
|
+
"queryOptions"
|
|
110
114
|
]);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
setHasMore(result.hasMore);
|
|
121
|
-
setPage((prev) => prev + 1);
|
|
122
|
-
setLoading(false);
|
|
123
|
-
});
|
|
124
|
-
if (!loading && hasMore && page === 0) {
|
|
125
|
-
getInitialOptions();
|
|
126
|
-
}
|
|
127
|
-
}, [page, loading, loadOptions]);
|
|
115
|
+
const { isLoading, isFetching, data, hasNextPage, fetchNextPage } = useInfiniteQuery(__spreadValues({
|
|
116
|
+
queryKey: [queryKey, limit],
|
|
117
|
+
queryFn: (_0) => __async(void 0, [_0], function* ({ pageParam = 0 }) {
|
|
118
|
+
return loadOptions(pageParam, limit);
|
|
119
|
+
}),
|
|
120
|
+
staleTime: 1e4,
|
|
121
|
+
getNextPageParam: (lastPage) => lastPage.hasMore ? lastPage.offset + limit : false
|
|
122
|
+
}, queryOptions));
|
|
123
|
+
const options = (data == null ? void 0 : data.pages) ? data.pages.map((page) => page.options).flat() : [];
|
|
128
124
|
return /* @__PURE__ */ jsx2(
|
|
129
125
|
Autocomplete,
|
|
130
126
|
__spreadProps(__spreadValues({}, rest), {
|
|
131
|
-
loading,
|
|
127
|
+
loading: isFetching,
|
|
132
128
|
options,
|
|
133
129
|
ListboxProps: __spreadProps(__spreadValues({}, ListboxProps), {
|
|
134
130
|
onScroll: (event) => __async(void 0, null, function* () {
|
|
135
131
|
const listboxNode = event.currentTarget;
|
|
136
132
|
const difference = listboxNode.scrollHeight - (listboxNode.scrollTop + listboxNode.clientHeight);
|
|
137
|
-
if (difference <= 5 && !
|
|
138
|
-
|
|
139
|
-
const result = yield loadOptions(page, limit);
|
|
140
|
-
setOptions([...options, ...result.options]);
|
|
141
|
-
setHasMore(result.hasMore);
|
|
142
|
-
setPage((prev) => prev + 1);
|
|
143
|
-
setLoading(false);
|
|
133
|
+
if (difference <= 5 && !isLoading && !isFetching && hasNextPage) {
|
|
134
|
+
fetchNextPage();
|
|
144
135
|
}
|
|
145
136
|
})
|
|
146
137
|
})
|
|
@@ -152,31 +143,77 @@ var AsyncAutocomplete = (_a) => {
|
|
|
152
143
|
import { avOrganizationsApi } from "@availity/api-axios";
|
|
153
144
|
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
154
145
|
var fetchOrgs = (config) => __async(void 0, null, function* () {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
} catch (e) {
|
|
162
|
-
return {
|
|
163
|
-
options: [],
|
|
164
|
-
hasMore: false
|
|
165
|
-
};
|
|
166
|
-
}
|
|
146
|
+
const resp = yield avOrganizationsApi.getOrganizations(config);
|
|
147
|
+
return {
|
|
148
|
+
options: resp.data.organizations,
|
|
149
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
150
|
+
offset: config.params.offset
|
|
151
|
+
};
|
|
167
152
|
});
|
|
168
153
|
var OrganizationAutocomplete = (_a) => {
|
|
169
|
-
var _b = _a, {
|
|
170
|
-
|
|
171
|
-
|
|
154
|
+
var _b = _a, {
|
|
155
|
+
apiConfig = {},
|
|
156
|
+
queryKey = "org-autocomplete"
|
|
157
|
+
} = _b, rest = __objRest(_b, [
|
|
158
|
+
"apiConfig",
|
|
159
|
+
"queryKey"
|
|
160
|
+
]);
|
|
161
|
+
const handleLoadOptions = (offset, limit) => __async(void 0, null, function* () {
|
|
172
162
|
const resp = yield fetchOrgs(__spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({ dropdown: true }, apiConfig.params), { offset, limit }) }));
|
|
173
163
|
return resp;
|
|
174
164
|
});
|
|
175
165
|
const handleGetOptionLabel = (org) => org.name;
|
|
176
|
-
return /* @__PURE__ */ jsx3(
|
|
166
|
+
return /* @__PURE__ */ jsx3(
|
|
167
|
+
AsyncAutocomplete,
|
|
168
|
+
__spreadProps(__spreadValues({
|
|
169
|
+
getOptionLabel: handleGetOptionLabel,
|
|
170
|
+
queryKey
|
|
171
|
+
}, rest), {
|
|
172
|
+
loadOptions: handleLoadOptions
|
|
173
|
+
})
|
|
174
|
+
);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// src/lib/ProviderAutocomplete.tsx
|
|
178
|
+
import { avProvidersApi } from "@availity/api-axios";
|
|
179
|
+
import { jsx as jsx4 } from "react/jsx-runtime";
|
|
180
|
+
var fetchProviders = (customerId, config) => __async(void 0, null, function* () {
|
|
181
|
+
const resp = yield avProvidersApi.getProviders(customerId, config);
|
|
182
|
+
return {
|
|
183
|
+
options: resp.data.providers,
|
|
184
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
185
|
+
offset: config.params.offset
|
|
186
|
+
};
|
|
187
|
+
});
|
|
188
|
+
var ProviderAutocomplete = (_a) => {
|
|
189
|
+
var _b = _a, {
|
|
190
|
+
apiConfig = {},
|
|
191
|
+
customerId,
|
|
192
|
+
queryKey = "prov-autocomplete"
|
|
193
|
+
} = _b, rest = __objRest(_b, [
|
|
194
|
+
"apiConfig",
|
|
195
|
+
"customerId",
|
|
196
|
+
"queryKey"
|
|
197
|
+
]);
|
|
198
|
+
const handleLoadOptions = (offset, limit) => __async(void 0, null, function* () {
|
|
199
|
+
const resp = yield fetchProviders(customerId, __spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({}, apiConfig.params), { offset, limit }) }));
|
|
200
|
+
return resp;
|
|
201
|
+
});
|
|
202
|
+
const handleGetOptionLabel = (option) => option.uiDisplayName;
|
|
203
|
+
return /* @__PURE__ */ jsx4(
|
|
204
|
+
AsyncAutocomplete,
|
|
205
|
+
__spreadProps(__spreadValues({
|
|
206
|
+
getOptionLabel: handleGetOptionLabel,
|
|
207
|
+
queryOptions: { enabled: !!customerId },
|
|
208
|
+
queryKey
|
|
209
|
+
}, rest), {
|
|
210
|
+
loadOptions: handleLoadOptions
|
|
211
|
+
})
|
|
212
|
+
);
|
|
177
213
|
};
|
|
178
214
|
export {
|
|
179
215
|
AsyncAutocomplete,
|
|
180
216
|
Autocomplete,
|
|
181
|
-
OrganizationAutocomplete
|
|
217
|
+
OrganizationAutocomplete,
|
|
218
|
+
ProviderAutocomplete
|
|
182
219
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@availity/mui-autocomplete",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Availity MUI Autocomplete Component - part of the @availity/element design system",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"@availity/mui-form-utils": "^0.11.3",
|
|
41
41
|
"@availity/mui-textfield": "^0.5.21",
|
|
42
42
|
"@mui/material": "^5.15.15",
|
|
43
|
+
"@tanstack/react-query": "^4.36.1",
|
|
43
44
|
"react": "18.2.0",
|
|
44
45
|
"react-dom": "18.2.0",
|
|
45
46
|
"tsup": "^8.0.2",
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"@availity/mui-form-utils": "^0.11.3",
|
|
51
52
|
"@availity/mui-textfield": "^0.5.21",
|
|
52
53
|
"@mui/material": "^5.11.9",
|
|
54
|
+
"@tanstack/react-query": "^4.36.1",
|
|
53
55
|
"react": ">=16.3.0"
|
|
54
56
|
},
|
|
55
57
|
"publishConfig": {
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Each exported component in the package should have its own stories file
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import AvApi, { ApiConfig } from '@availity/api-axios';
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
5
|
|
|
5
6
|
import { AsyncAutocomplete } from './AsyncAutocomplete';
|
|
6
7
|
|
|
@@ -35,42 +36,46 @@ type ExampleResponse = {
|
|
|
35
36
|
count: number;
|
|
36
37
|
};
|
|
37
38
|
|
|
38
|
-
const getResults = async (
|
|
39
|
-
const offset = page * limit;
|
|
40
|
-
|
|
41
|
-
const resp = await api.post<ExampleResponse>({ offset, limit }, { params: {} });
|
|
39
|
+
const getResults = async (offset: number, limit: number) => {
|
|
40
|
+
// const offset = page * limit;
|
|
41
|
+
const resp = await api.post<ExampleResponse>({ offset, limit }, { params: {} });
|
|
42
42
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} catch {
|
|
51
|
-
return {
|
|
52
|
-
totalCount: 0,
|
|
53
|
-
offset,
|
|
54
|
-
limit,
|
|
55
|
-
options: [],
|
|
56
|
-
count: 0,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
43
|
+
return {
|
|
44
|
+
totalCount: resp.data.totalCount,
|
|
45
|
+
offset,
|
|
46
|
+
limit,
|
|
47
|
+
options: resp.data.options,
|
|
48
|
+
count: resp.data.count,
|
|
49
|
+
};
|
|
59
50
|
};
|
|
60
51
|
|
|
61
|
-
const loadOptions = async (
|
|
62
|
-
const { options, totalCount
|
|
52
|
+
const loadOptions = async (offset: number, limit: number) => {
|
|
53
|
+
const { options, totalCount } = await getResults(offset, limit);
|
|
63
54
|
|
|
64
55
|
return {
|
|
65
56
|
options,
|
|
66
57
|
hasMore: offset + limit < totalCount,
|
|
58
|
+
offset,
|
|
67
59
|
};
|
|
68
60
|
};
|
|
69
61
|
|
|
62
|
+
const client = new QueryClient({
|
|
63
|
+
defaultOptions: {
|
|
64
|
+
queries: {
|
|
65
|
+
refetchOnWindowFocus: false,
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
70
|
export const _Async: StoryObj<typeof AsyncAutocomplete> = {
|
|
71
71
|
render: (args) => {
|
|
72
|
-
return
|
|
72
|
+
return (
|
|
73
|
+
<QueryClientProvider client={client}>
|
|
74
|
+
<AsyncAutocomplete {...args} />
|
|
75
|
+
</QueryClientProvider>
|
|
76
|
+
);
|
|
73
77
|
},
|
|
78
|
+
decorators: [],
|
|
74
79
|
parameters: {
|
|
75
80
|
controls: {
|
|
76
81
|
exclude: /loading(?!Text)|options/,
|
|
@@ -81,5 +86,6 @@ export const _Async: StoryObj<typeof AsyncAutocomplete> = {
|
|
|
81
86
|
getOptionLabel: (val: Option) => val.label,
|
|
82
87
|
loadOptions,
|
|
83
88
|
limit: 10,
|
|
89
|
+
queryKey: 'example',
|
|
84
90
|
},
|
|
85
91
|
};
|
|
@@ -2,11 +2,14 @@ import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
|
2
2
|
import AvApi, { ApiConfig } from '@availity/api-axios';
|
|
3
3
|
/* eslint-disable @nx/enforce-module-boundaries */
|
|
4
4
|
import { server } from '@availity/mock/src/lib/server';
|
|
5
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
5
6
|
|
|
6
7
|
import { AsyncAutocomplete } from './AsyncAutocomplete';
|
|
7
8
|
|
|
8
9
|
const api = new AvApi({ name: 'example' } as ApiConfig);
|
|
9
10
|
|
|
11
|
+
const client = new QueryClient();
|
|
12
|
+
|
|
10
13
|
type Option = {
|
|
11
14
|
label: string;
|
|
12
15
|
value: number;
|
|
@@ -18,8 +21,7 @@ type ExampleResponse = {
|
|
|
18
21
|
count: number;
|
|
19
22
|
};
|
|
20
23
|
|
|
21
|
-
const getResults = async (
|
|
22
|
-
const offset = page * limit;
|
|
24
|
+
const getResults = async (offset: number, limit: number) => {
|
|
23
25
|
try {
|
|
24
26
|
const resp = await api.post<ExampleResponse>({ offset, limit }, { params: {} });
|
|
25
27
|
|
|
@@ -41,12 +43,13 @@ const getResults = async (page: number, limit: number) => {
|
|
|
41
43
|
}
|
|
42
44
|
};
|
|
43
45
|
|
|
44
|
-
const loadOptions = async (
|
|
45
|
-
const { options, totalCount
|
|
46
|
+
const loadOptions = async (offset: number, limit: number) => {
|
|
47
|
+
const { options, totalCount } = await getResults(offset, limit);
|
|
46
48
|
|
|
47
49
|
return {
|
|
48
50
|
options,
|
|
49
51
|
hasMore: offset + limit < totalCount,
|
|
52
|
+
offset,
|
|
50
53
|
};
|
|
51
54
|
};
|
|
52
55
|
|
|
@@ -64,13 +67,21 @@ describe('AsyncAutocomplete', () => {
|
|
|
64
67
|
});
|
|
65
68
|
|
|
66
69
|
test('should render successfully', () => {
|
|
67
|
-
const { getByLabelText } = render(
|
|
70
|
+
const { getByLabelText } = render(
|
|
71
|
+
<QueryClientProvider client={client}>
|
|
72
|
+
<AsyncAutocomplete queryKey="test" FieldProps={{ label: 'Test' }} loadOptions={loadOptions} />
|
|
73
|
+
</QueryClientProvider>
|
|
74
|
+
);
|
|
68
75
|
|
|
69
76
|
expect(getByLabelText('Test')).toBeTruthy();
|
|
70
77
|
});
|
|
71
78
|
|
|
72
79
|
test('options should be available', async () => {
|
|
73
|
-
render(
|
|
80
|
+
render(
|
|
81
|
+
<QueryClientProvider client={client}>
|
|
82
|
+
<AsyncAutocomplete queryKey="test1" loadOptions={loadOptions} FieldProps={{ label: 'Test' }} />
|
|
83
|
+
</QueryClientProvider>
|
|
84
|
+
);
|
|
74
85
|
|
|
75
86
|
const input = screen.getByRole('combobox');
|
|
76
87
|
fireEvent.click(input);
|
|
@@ -88,7 +99,11 @@ describe('AsyncAutocomplete', () => {
|
|
|
88
99
|
});
|
|
89
100
|
|
|
90
101
|
test('should call loadOptions when scroll to the bottom', async () => {
|
|
91
|
-
render(
|
|
102
|
+
render(
|
|
103
|
+
<QueryClientProvider client={client}>
|
|
104
|
+
<AsyncAutocomplete queryKey="test2" loadOptions={loadOptions} limit={10} FieldProps={{ label: 'Test' }} />
|
|
105
|
+
</QueryClientProvider>
|
|
106
|
+
);
|
|
92
107
|
|
|
93
108
|
const input = screen.getByRole('combobox');
|
|
94
109
|
fireEvent.click(input);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
1
|
import type { ChipTypeMap } from '@mui/material/Chip';
|
|
2
|
+
import { useInfiniteQuery, UseInfiniteQueryOptions } from '@tanstack/react-query';
|
|
3
3
|
|
|
4
4
|
import { Autocomplete, AutocompleteProps } from './Autocomplete';
|
|
5
5
|
|
|
@@ -13,11 +13,15 @@ export interface AsyncAutocompleteProps<
|
|
|
13
13
|
AutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>,
|
|
14
14
|
'options' | 'disableListWrap' | 'loading'
|
|
15
15
|
> {
|
|
16
|
-
/** Function that
|
|
17
|
-
loadOptions: (
|
|
16
|
+
/** Function that is called to fetch the options for the list. Returns a promise with options, hasMore, and offset */
|
|
17
|
+
loadOptions: (offset: number, limit: number) => Promise<{ options: Option[]; hasMore: boolean; offset: number }>;
|
|
18
|
+
/** The key used by @tanstack/react-query to cache the response */
|
|
19
|
+
queryKey: string;
|
|
18
20
|
/** The number of options to request from the api
|
|
19
21
|
* @default 50 */
|
|
20
22
|
limit?: number;
|
|
23
|
+
/** Config options for the useInfiniteQuery hook */
|
|
24
|
+
queryOptions?: UseInfiniteQueryOptions<{ options: Option[]; hasMore: boolean; offset: number }>;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
export const AsyncAutocomplete = <
|
|
@@ -29,33 +33,25 @@ export const AsyncAutocomplete = <
|
|
|
29
33
|
>({
|
|
30
34
|
loadOptions,
|
|
31
35
|
limit = 50,
|
|
36
|
+
queryKey,
|
|
32
37
|
ListboxProps,
|
|
38
|
+
queryOptions,
|
|
33
39
|
...rest
|
|
34
40
|
}: AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>) => {
|
|
35
|
-
const
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
41
|
+
const { isLoading, isFetching, data, hasNextPage, fetchNextPage } = useInfiniteQuery({
|
|
42
|
+
queryKey: [queryKey, limit],
|
|
43
|
+
queryFn: async ({ pageParam = 0 }) => loadOptions(pageParam, limit),
|
|
44
|
+
staleTime: 10000,
|
|
45
|
+
getNextPageParam: (lastPage) => (lastPage.hasMore ? lastPage.offset + limit : false),
|
|
46
|
+
...queryOptions,
|
|
47
|
+
});
|
|
39
48
|
|
|
40
|
-
|
|
41
|
-
const getInitialOptions = async () => {
|
|
42
|
-
setLoading(true);
|
|
43
|
-
const result = await loadOptions(page, limit);
|
|
44
|
-
setOptions(result.options);
|
|
45
|
-
setHasMore(result.hasMore);
|
|
46
|
-
setPage((prev) => prev + 1);
|
|
47
|
-
setLoading(false);
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
if (!loading && hasMore && page === 0) {
|
|
51
|
-
getInitialOptions();
|
|
52
|
-
}
|
|
53
|
-
}, [page, loading, loadOptions]);
|
|
49
|
+
const options = data?.pages ? data.pages.map((page) => page.options).flat() : [];
|
|
54
50
|
|
|
55
51
|
return (
|
|
56
52
|
<Autocomplete
|
|
57
53
|
{...rest}
|
|
58
|
-
loading={
|
|
54
|
+
loading={isFetching}
|
|
59
55
|
options={options}
|
|
60
56
|
ListboxProps={{
|
|
61
57
|
...ListboxProps,
|
|
@@ -64,13 +60,8 @@ export const AsyncAutocomplete = <
|
|
|
64
60
|
const difference = listboxNode.scrollHeight - (listboxNode.scrollTop + listboxNode.clientHeight);
|
|
65
61
|
|
|
66
62
|
// Only fetch if we are near the bottom, not already fetching, and there are more results
|
|
67
|
-
if (difference <= 5 && !
|
|
68
|
-
|
|
69
|
-
const result = await loadOptions(page, limit);
|
|
70
|
-
setOptions([...options, ...result.options]);
|
|
71
|
-
setHasMore(result.hasMore);
|
|
72
|
-
setPage((prev) => prev + 1);
|
|
73
|
-
setLoading(false);
|
|
63
|
+
if (difference <= 5 && !isLoading && !isFetching && hasNextPage) {
|
|
64
|
+
fetchNextPage();
|
|
74
65
|
}
|
|
75
66
|
},
|
|
76
67
|
}}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// Each exported component in the package should have its own stories file
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
3
4
|
|
|
4
5
|
import { OrganizationAutocomplete } from './OrganizationAutocomplete';
|
|
5
6
|
|
|
@@ -21,9 +22,21 @@ const meta: Meta<typeof OrganizationAutocomplete> = {
|
|
|
21
22
|
|
|
22
23
|
export default meta;
|
|
23
24
|
|
|
25
|
+
const client = new QueryClient({
|
|
26
|
+
defaultOptions: {
|
|
27
|
+
queries: {
|
|
28
|
+
refetchOnWindowFocus: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
24
33
|
export const _OrganizationAutocomplete: StoryObj<typeof OrganizationAutocomplete> = {
|
|
25
34
|
render: (args) => {
|
|
26
|
-
return
|
|
35
|
+
return (
|
|
36
|
+
<QueryClientProvider client={client}>
|
|
37
|
+
<OrganizationAutocomplete {...args} />
|
|
38
|
+
</QueryClientProvider>
|
|
39
|
+
);
|
|
27
40
|
},
|
|
28
41
|
args: {
|
|
29
42
|
FieldProps: {
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
2
|
/* eslint-disable @nx/enforce-module-boundaries */
|
|
3
3
|
import { server } from '@availity/mock/src/lib/server';
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
5
|
|
|
5
6
|
import { OrganizationAutocomplete } from './OrganizationAutocomplete';
|
|
6
7
|
|
|
8
|
+
const client = new QueryClient();
|
|
9
|
+
|
|
7
10
|
describe('OrganizationAutocomplete', () => {
|
|
8
11
|
beforeAll(() => {
|
|
9
12
|
// Start the interception.
|
|
@@ -15,10 +18,15 @@ describe('OrganizationAutocomplete', () => {
|
|
|
15
18
|
// in individual tests (runtime handlers).
|
|
16
19
|
server.resetHandlers();
|
|
17
20
|
jest.restoreAllMocks();
|
|
21
|
+
client.clear();
|
|
18
22
|
});
|
|
19
23
|
|
|
20
24
|
test('organizations are fetched and displayed by name', async () => {
|
|
21
|
-
render(
|
|
25
|
+
render(
|
|
26
|
+
<QueryClientProvider client={client}>
|
|
27
|
+
<OrganizationAutocomplete FieldProps={{ label: 'Test' }} />
|
|
28
|
+
</QueryClientProvider>
|
|
29
|
+
);
|
|
22
30
|
|
|
23
31
|
const input = screen.getByRole('combobox');
|
|
24
32
|
fireEvent.click(input);
|
|
@@ -2,6 +2,7 @@ import { avOrganizationsApi, ApiConfig } from '@availity/api-axios';
|
|
|
2
2
|
import type { ChipTypeMap } from '@mui/material/Chip';
|
|
3
3
|
|
|
4
4
|
import { AsyncAutocomplete, AsyncAutocompleteProps } from './AsyncAutocomplete';
|
|
5
|
+
import type { Optional } from './util';
|
|
5
6
|
|
|
6
7
|
export type Organization = {
|
|
7
8
|
customerId: string;
|
|
@@ -11,20 +12,14 @@ export type Organization = {
|
|
|
11
12
|
links: Record<string, Record<string, string>>;
|
|
12
13
|
};
|
|
13
14
|
|
|
14
|
-
const fetchOrgs = async (config: ApiConfig): Promise<{ options: Organization[]; hasMore: boolean }> => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
} catch {
|
|
23
|
-
return {
|
|
24
|
-
options: [],
|
|
25
|
-
hasMore: false,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
15
|
+
const fetchOrgs = async (config: ApiConfig): Promise<{ options: Organization[]; hasMore: boolean; offset: number }> => {
|
|
16
|
+
const resp = await avOrganizationsApi.getOrganizations(config);
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
options: resp.data.organizations as Organization[],
|
|
20
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
21
|
+
offset: config.params.offset,
|
|
22
|
+
};
|
|
28
23
|
};
|
|
29
24
|
|
|
30
25
|
export interface OrgAutocompleteProps<
|
|
@@ -33,14 +28,19 @@ export interface OrgAutocompleteProps<
|
|
|
33
28
|
DisableClearable extends boolean | undefined = false,
|
|
34
29
|
FreeSolo extends boolean | undefined = false,
|
|
35
30
|
ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent'],
|
|
36
|
-
> extends Omit<
|
|
31
|
+
> extends Omit<
|
|
32
|
+
Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>,
|
|
33
|
+
'loadOptions'
|
|
34
|
+
> {
|
|
37
35
|
apiConfig?: ApiConfig;
|
|
38
36
|
}
|
|
39
37
|
|
|
40
|
-
export const OrganizationAutocomplete = ({
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
38
|
+
export const OrganizationAutocomplete = ({
|
|
39
|
+
apiConfig = {},
|
|
40
|
+
queryKey = 'org-autocomplete',
|
|
41
|
+
...rest
|
|
42
|
+
}: OrgAutocompleteProps) => {
|
|
43
|
+
const handleLoadOptions = async (offset: number, limit: number) => {
|
|
44
44
|
const resp = await fetchOrgs({ ...apiConfig, params: { dropdown: true, ...apiConfig.params, offset, limit } });
|
|
45
45
|
|
|
46
46
|
return resp;
|
|
@@ -48,5 +48,12 @@ export const OrganizationAutocomplete = ({ apiConfig = {}, ...rest }: OrgAutocom
|
|
|
48
48
|
|
|
49
49
|
const handleGetOptionLabel = (org: Organization) => org.name;
|
|
50
50
|
|
|
51
|
-
return
|
|
51
|
+
return (
|
|
52
|
+
<AsyncAutocomplete
|
|
53
|
+
getOptionLabel={handleGetOptionLabel}
|
|
54
|
+
queryKey={queryKey}
|
|
55
|
+
{...rest}
|
|
56
|
+
loadOptions={handleLoadOptions}
|
|
57
|
+
/>
|
|
58
|
+
);
|
|
52
59
|
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
// Each exported component in the package should have its own stories file
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
4
|
+
|
|
5
|
+
import { ProviderAutocomplete } from './ProviderAutocomplete';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof ProviderAutocomplete> = {
|
|
8
|
+
title: 'Form Components/Autocomplete/ProviderAutocomplete',
|
|
9
|
+
component: ProviderAutocomplete,
|
|
10
|
+
tags: ['autodocs'],
|
|
11
|
+
args: {
|
|
12
|
+
id: 'example',
|
|
13
|
+
},
|
|
14
|
+
argTypes: {
|
|
15
|
+
multiple: {
|
|
16
|
+
table: {
|
|
17
|
+
disable: true,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default meta;
|
|
24
|
+
|
|
25
|
+
const client = new QueryClient({
|
|
26
|
+
defaultOptions: {
|
|
27
|
+
queries: {
|
|
28
|
+
refetchOnWindowFocus: false,
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const _ProviderAutocomplete: StoryObj<typeof ProviderAutocomplete> = {
|
|
34
|
+
render: (args) => {
|
|
35
|
+
return (
|
|
36
|
+
<QueryClientProvider client={client}>
|
|
37
|
+
<ProviderAutocomplete {...args} />
|
|
38
|
+
</QueryClientProvider>
|
|
39
|
+
);
|
|
40
|
+
},
|
|
41
|
+
args: {
|
|
42
|
+
FieldProps: {
|
|
43
|
+
label: 'Provider Select',
|
|
44
|
+
helperText: 'Select a Provider from the list',
|
|
45
|
+
placeholder: 'Select...',
|
|
46
|
+
fullWidth: false,
|
|
47
|
+
},
|
|
48
|
+
limit: 10,
|
|
49
|
+
customerId: '1234',
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
2
|
+
/* eslint-disable @nx/enforce-module-boundaries */
|
|
3
|
+
import { server } from '@availity/mock/src/lib/server';
|
|
4
|
+
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
import { ProviderAutocomplete } from './ProviderAutocomplete';
|
|
7
|
+
|
|
8
|
+
const client = new QueryClient();
|
|
9
|
+
|
|
10
|
+
describe('ProviderAutocomplete', () => {
|
|
11
|
+
beforeAll(() => {
|
|
12
|
+
// Start the interception.
|
|
13
|
+
server.listen();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
// Remove any handlers you may have added
|
|
18
|
+
// in individual tests (runtime handlers).
|
|
19
|
+
server.resetHandlers();
|
|
20
|
+
jest.restoreAllMocks();
|
|
21
|
+
client.clear();
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('providers are fetched and displayed by name', async () => {
|
|
25
|
+
render(
|
|
26
|
+
<QueryClientProvider client={client}>
|
|
27
|
+
<ProviderAutocomplete customerId="123" FieldProps={{ label: 'Test' }} />
|
|
28
|
+
</QueryClientProvider>
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const input = screen.getByRole('combobox');
|
|
32
|
+
fireEvent.click(input);
|
|
33
|
+
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
34
|
+
|
|
35
|
+
await waitFor(() => {
|
|
36
|
+
expect(screen.getByText('Provider 1')).toBeDefined();
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('providers are not fetched when customerId is not present', async () => {
|
|
41
|
+
render(
|
|
42
|
+
<QueryClientProvider client={client}>
|
|
43
|
+
<ProviderAutocomplete customerId="" FieldProps={{ label: 'Test' }} />
|
|
44
|
+
</QueryClientProvider>
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
const input = screen.getByRole('combobox');
|
|
48
|
+
fireEvent.click(input);
|
|
49
|
+
fireEvent.keyDown(input, { key: 'ArrowDown' });
|
|
50
|
+
|
|
51
|
+
await waitFor(() => {
|
|
52
|
+
expect(screen.getByText('No options')).toBeDefined();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { avProvidersApi, ApiConfig } from '@availity/api-axios';
|
|
2
|
+
import type { ChipTypeMap } from '@mui/material/Chip';
|
|
3
|
+
|
|
4
|
+
import { AsyncAutocomplete, AsyncAutocompleteProps } from './AsyncAutocomplete';
|
|
5
|
+
import type { Optional } from './util';
|
|
6
|
+
|
|
7
|
+
export type Provider = {
|
|
8
|
+
id: string;
|
|
9
|
+
businessName: string;
|
|
10
|
+
uiDisplayName: string;
|
|
11
|
+
aytypical: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const fetchProviders = async (
|
|
15
|
+
customerId: string,
|
|
16
|
+
config: ApiConfig
|
|
17
|
+
): Promise<{ options: Provider[]; hasMore: boolean; offset: number }> => {
|
|
18
|
+
const resp = await avProvidersApi.getProviders(customerId, config);
|
|
19
|
+
|
|
20
|
+
return {
|
|
21
|
+
options: resp.data.providers as Provider[],
|
|
22
|
+
hasMore: config.params.offset + config.params.limit < resp.data.totalCount,
|
|
23
|
+
offset: config.params.offset,
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface ProviderAutocompleteProps<
|
|
28
|
+
Option = Provider,
|
|
29
|
+
Multiple extends boolean | undefined = false,
|
|
30
|
+
DisableClearable extends boolean | undefined = false,
|
|
31
|
+
FreeSolo extends boolean | undefined = false,
|
|
32
|
+
ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent'],
|
|
33
|
+
> extends Omit<
|
|
34
|
+
Optional<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'queryKey'>,
|
|
35
|
+
'loadOptions'
|
|
36
|
+
> {
|
|
37
|
+
customerId: string;
|
|
38
|
+
apiConfig?: ApiConfig;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const ProviderAutocomplete = ({
|
|
42
|
+
apiConfig = {},
|
|
43
|
+
customerId,
|
|
44
|
+
queryKey = 'prov-autocomplete',
|
|
45
|
+
...rest
|
|
46
|
+
}: ProviderAutocompleteProps) => {
|
|
47
|
+
const handleLoadOptions = async (offset: number, limit: number) => {
|
|
48
|
+
const resp = await fetchProviders(customerId, { ...apiConfig, params: { ...apiConfig.params, offset, limit } });
|
|
49
|
+
|
|
50
|
+
return resp;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleGetOptionLabel = (option: Provider) => option.uiDisplayName;
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<AsyncAutocomplete
|
|
57
|
+
getOptionLabel={handleGetOptionLabel}
|
|
58
|
+
queryOptions={{ enabled: !!customerId }}
|
|
59
|
+
queryKey={queryKey}
|
|
60
|
+
{...rest}
|
|
61
|
+
loadOptions={handleLoadOptions}
|
|
62
|
+
/>
|
|
63
|
+
);
|
|
64
|
+
};
|
package/src/lib/util.ts
ADDED