@availity/mui-autocomplete 0.4.6 → 0.5.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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
4
4
 
5
+ ## [0.5.1](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.5.0...@availity/mui-autocomplete@0.5.1) (2024-06-21)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * add deps ([520c3c2](https://github.com/Availity/element/commit/520c3c2549af82006fc563bc453b010ed6aadb73))
11
+ * add export and delay ([20d6792](https://github.com/Availity/element/commit/20d6792e7a995afa0f12e597fbd9fc4c16c92df2))
12
+
13
+ ## [0.5.0](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.4.6...@availity/mui-autocomplete@0.5.0) (2024-06-20)
14
+
15
+
16
+ ### Features
17
+
18
+ * **mui-autocomplete:** add OrganizationAutocomplete component ([9f3ea07](https://github.com/Availity/element/commit/9f3ea0753e0147d7e1aeaf73230716046caba01e))
19
+
5
20
  ## [0.4.6](https://github.com/Availity/element/compare/@availity/mui-autocomplete@0.4.5...@availity/mui-autocomplete@0.4.6) (2024-06-14)
6
21
 
7
22
  ### Dependency Updates
package/README.md CHANGED
@@ -54,7 +54,7 @@ The `Autcomplete` component can be used standalone or with a form state library
54
54
 
55
55
  `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.
56
56
 
57
- ```tsx
57
+ ```jsx
58
58
  import { Autocomplete } from '@availity/element';
59
59
 
60
60
  const MyAutocomplete = () => {
@@ -74,13 +74,13 @@ const MyAutocomplete = () => {
74
74
 
75
75
  #### Direct import
76
76
 
77
- ```tsx
77
+ ```jsx
78
78
  import { Autocomplete } from '@availity/mui-autocomplete';
79
79
  ```
80
80
 
81
81
  #### Usage with `react-hook-form`
82
82
 
83
- ```tsx
83
+ ```jsx
84
84
  import { useForm, Controller } from 'react-hook-form';
85
85
  import { Autocomplete, Button } from '@availity/element';
86
86
 
@@ -123,7 +123,7 @@ const Form = () => {
123
123
 
124
124
  An `AsyncAutocomplete` component is exported for use cases that require fetching paginated results from an api. You will need to use the `loadOptions` prop. The `loadOptions` function will be called when the user scrolls to the bottom of the dropdown. It will be passed the current page 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` and a `hasMore` property. `hasMore` tells the `AsyncAutocomplete` component whether or not it should call `loadOptions` again. The returned `options` will be concatenated to the existing options array.
125
125
 
126
- ```tsx
126
+ ```jsx
127
127
  import { Autocomplete } from '@availity/element';
128
128
 
129
129
  const Example = () => {
@@ -139,3 +139,17 @@ const Example = () => {
139
139
  return <Autocomplete FieldProps={{ label: 'Async Dropdown' }} loadOptions={loadOptions} />;
140
140
  };
141
141
  ```
142
+
143
+ #### `OrganizationAutocomplete` Usage
144
+
145
+ The `OrganizationAutocomplete` component is an extension of the `AsyncAutocomplete` component which calls our Organizations endpoint. The props are the same as `AsyncAutocomplete` except you do not need to pass a function to `loadOptions`. This has already been done for you. The component uses the `name` from the returned organizations as the label for the option. Pass in your own `getOptionLabel` function if you would like to change the label.
146
+
147
+ 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
+
149
+ ```jsx
150
+ import { OrganizationAutocomplete } from '@availity/element';
151
+
152
+ const Example = () => {
153
+ return <OrganizationAutocomplete FieldProps={{ label: 'Organization Select', placeholder: 'Select...' }} />;
154
+ };
155
+ ```
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 { ApiConfig } from '@availity/api-axios';
6
7
 
7
8
  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'> {
8
9
  /** Props applied to the `TextField` component */
@@ -23,4 +24,16 @@ interface AsyncAutocompleteProps<Option, Multiple extends boolean | undefined, D
23
24
  }
24
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;
25
26
 
26
- export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps };
27
+ type Organization = {
28
+ customerId: string;
29
+ name: string;
30
+ id: string;
31
+ createDate: string;
32
+ links: Record<string, Record<string, string>>;
33
+ };
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'> {
35
+ apiConfig?: ApiConfig;
36
+ }
37
+ declare const OrganizationAutocomplete: ({ apiConfig, ...rest }: OrgAutocompleteProps) => react_jsx_runtime.JSX.Element;
38
+
39
+ export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete };
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 { ApiConfig } from '@availity/api-axios';
6
7
 
7
8
  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'> {
8
9
  /** Props applied to the `TextField` component */
@@ -23,4 +24,16 @@ interface AsyncAutocompleteProps<Option, Multiple extends boolean | undefined, D
23
24
  }
24
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;
25
26
 
26
- export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps };
27
+ type Organization = {
28
+ customerId: string;
29
+ name: string;
30
+ id: string;
31
+ createDate: string;
32
+ links: Record<string, Record<string, string>>;
33
+ };
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'> {
35
+ apiConfig?: ApiConfig;
36
+ }
37
+ declare const OrganizationAutocomplete: ({ apiConfig, ...rest }: OrgAutocompleteProps) => react_jsx_runtime.JSX.Element;
38
+
39
+ export { AsyncAutocomplete, type AsyncAutocompleteProps, Autocomplete, type AutocompleteProps, type OrgAutocompleteProps, type Organization, OrganizationAutocomplete };
package/dist/index.js CHANGED
@@ -80,7 +80,8 @@ var __async = (__this, __arguments, generator) => {
80
80
  var src_exports = {};
81
81
  __export(src_exports, {
82
82
  AsyncAutocomplete: () => AsyncAutocomplete,
83
- Autocomplete: () => Autocomplete
83
+ Autocomplete: () => Autocomplete,
84
+ OrganizationAutocomplete: () => OrganizationAutocomplete
84
85
  });
85
86
  module.exports = __toCommonJS(src_exports);
86
87
 
@@ -179,8 +180,37 @@ var AsyncAutocomplete = (_a) => {
179
180
  })
180
181
  );
181
182
  };
183
+
184
+ // src/lib/OrganizationAutocomplete.tsx
185
+ var import_api_axios = require("@availity/api-axios");
186
+ var import_jsx_runtime3 = require("react/jsx-runtime");
187
+ var fetchOrgs = (config) => __async(void 0, null, function* () {
188
+ try {
189
+ const resp = yield import_api_axios.avOrganizationsApi.getOrganizations(config);
190
+ return {
191
+ options: resp.data.organizations,
192
+ hasMore: config.params.offset + config.params.limit < resp.data.totalCount
193
+ };
194
+ } catch (e) {
195
+ return {
196
+ options: [],
197
+ hasMore: false
198
+ };
199
+ }
200
+ });
201
+ var OrganizationAutocomplete = (_a) => {
202
+ var _b = _a, { apiConfig = {} } = _b, rest = __objRest(_b, ["apiConfig"]);
203
+ const handleLoadOptions = (page, limit) => __async(void 0, null, function* () {
204
+ const offset = page * limit;
205
+ const resp = yield fetchOrgs(__spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({ dropdown: true }, apiConfig.params), { offset, limit }) }));
206
+ return resp;
207
+ });
208
+ const handleGetOptionLabel = (org) => org.name;
209
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(AsyncAutocomplete, __spreadProps(__spreadValues({ getOptionLabel: handleGetOptionLabel }, rest), { loadOptions: handleLoadOptions }));
210
+ };
182
211
  // Annotate the CommonJS export names for ESM import in node:
183
212
  0 && (module.exports = {
184
213
  AsyncAutocomplete,
185
- Autocomplete
214
+ Autocomplete,
215
+ OrganizationAutocomplete
186
216
  });
package/dist/index.mjs CHANGED
@@ -147,7 +147,36 @@ var AsyncAutocomplete = (_a) => {
147
147
  })
148
148
  );
149
149
  };
150
+
151
+ // src/lib/OrganizationAutocomplete.tsx
152
+ import { avOrganizationsApi } from "@availity/api-axios";
153
+ import { jsx as jsx3 } from "react/jsx-runtime";
154
+ var fetchOrgs = (config) => __async(void 0, null, function* () {
155
+ try {
156
+ const resp = yield avOrganizationsApi.getOrganizations(config);
157
+ return {
158
+ options: resp.data.organizations,
159
+ hasMore: config.params.offset + config.params.limit < resp.data.totalCount
160
+ };
161
+ } catch (e) {
162
+ return {
163
+ options: [],
164
+ hasMore: false
165
+ };
166
+ }
167
+ });
168
+ var OrganizationAutocomplete = (_a) => {
169
+ var _b = _a, { apiConfig = {} } = _b, rest = __objRest(_b, ["apiConfig"]);
170
+ const handleLoadOptions = (page, limit) => __async(void 0, null, function* () {
171
+ const offset = page * limit;
172
+ const resp = yield fetchOrgs(__spreadProps(__spreadValues({}, apiConfig), { params: __spreadProps(__spreadValues({ dropdown: true }, apiConfig.params), { offset, limit }) }));
173
+ return resp;
174
+ });
175
+ const handleGetOptionLabel = (org) => org.name;
176
+ return /* @__PURE__ */ jsx3(AsyncAutocomplete, __spreadProps(__spreadValues({ getOptionLabel: handleGetOptionLabel }, rest), { loadOptions: handleLoadOptions }));
177
+ };
150
178
  export {
151
179
  AsyncAutocomplete,
152
- Autocomplete
180
+ Autocomplete,
181
+ OrganizationAutocomplete
153
182
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@availity/mui-autocomplete",
3
- "version": "0.4.6",
3
+ "version": "0.5.1",
4
4
  "description": "Availity MUI Autocomplete Component - part of the @availity/element design system",
5
5
  "keywords": [
6
6
  "react",
@@ -36,6 +36,9 @@
36
36
  "@mui/types": "^7.2.14"
37
37
  },
38
38
  "devDependencies": {
39
+ "@availity/api-axios": "^8.0.7",
40
+ "@availity/mui-form-utils": "^0.11.2",
41
+ "@availity/mui-textfield": "^0.5.20",
39
42
  "@mui/material": "^5.15.15",
40
43
  "react": "18.2.0",
41
44
  "react-dom": "18.2.0",
@@ -43,6 +46,7 @@
43
46
  "typescript": "^5.4.5"
44
47
  },
45
48
  "peerDependencies": {
49
+ "@availity/api-axios": "^8.0.7",
46
50
  "@availity/mui-form-utils": "^0.11.2",
47
51
  "@availity/mui-textfield": "^0.5.20",
48
52
  "@mui/material": "^5.11.9",
package/src/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './lib/Autocomplete';
2
2
  export * from './lib/AsyncAutocomplete';
3
+ export * from './lib/OrganizationAutocomplete';
@@ -0,0 +1,85 @@
1
+ // Each exported component in the package should have its own stories file
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+ import AvApi, { ApiConfig } from '@availity/api-axios';
4
+
5
+ import { AsyncAutocomplete } from './AsyncAutocomplete';
6
+
7
+ const meta: Meta<typeof AsyncAutocomplete> = {
8
+ title: 'Form Components/Autocomplete/AsyncAutocomplete',
9
+ component: AsyncAutocomplete,
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 api = new AvApi({ name: 'example' } as ApiConfig);
26
+
27
+ type Option = {
28
+ label: string;
29
+ value: number;
30
+ };
31
+
32
+ type ExampleResponse = {
33
+ totalCount: number;
34
+ options: Option[];
35
+ count: number;
36
+ };
37
+
38
+ const getResults = async (page: number, limit: number) => {
39
+ const offset = page * limit;
40
+ try {
41
+ const resp = await api.post<ExampleResponse>({ offset, limit }, { params: {} });
42
+
43
+ return {
44
+ totalCount: resp.data.totalCount,
45
+ offset,
46
+ limit,
47
+ options: resp.data.options,
48
+ count: resp.data.count,
49
+ };
50
+ } catch {
51
+ return {
52
+ totalCount: 0,
53
+ offset,
54
+ limit,
55
+ options: [],
56
+ count: 0,
57
+ };
58
+ }
59
+ };
60
+
61
+ const loadOptions = async (page: number, limit: number) => {
62
+ const { options, totalCount, offset } = await getResults(page, limit);
63
+
64
+ return {
65
+ options,
66
+ hasMore: offset + limit < totalCount,
67
+ };
68
+ };
69
+
70
+ export const _Async: StoryObj<typeof AsyncAutocomplete> = {
71
+ render: (args) => {
72
+ return <AsyncAutocomplete {...args} />;
73
+ },
74
+ parameters: {
75
+ controls: {
76
+ exclude: /loading(?!Text)|options/,
77
+ },
78
+ },
79
+ args: {
80
+ FieldProps: { label: 'Async Select', helperText: 'Helper Text', fullWidth: false },
81
+ getOptionLabel: (val: Option) => val.label,
82
+ loadOptions,
83
+ limit: 10,
84
+ },
85
+ };
@@ -1,28 +1,75 @@
1
1
  import { act, fireEvent, render, screen, waitFor } from '@testing-library/react';
2
+ import AvApi, { ApiConfig } from '@availity/api-axios';
3
+ /* eslint-disable @nx/enforce-module-boundaries */
4
+ import { server } from '@availity/mock/src/lib/server';
5
+
2
6
  import { AsyncAutocomplete } from './AsyncAutocomplete';
3
7
 
8
+ const api = new AvApi({ name: 'example' } as ApiConfig);
9
+
10
+ type Option = {
11
+ label: string;
12
+ value: number;
13
+ };
14
+
15
+ type ExampleResponse = {
16
+ totalCount: number;
17
+ options: Option[];
18
+ count: number;
19
+ };
20
+
21
+ const getResults = async (page: number, limit: number) => {
22
+ const offset = page * limit;
23
+ try {
24
+ const resp = await api.post<ExampleResponse>({ offset, limit }, { params: {} });
25
+
26
+ return {
27
+ totalCount: resp.data.totalCount,
28
+ offset,
29
+ limit,
30
+ options: resp.data.options,
31
+ count: resp.data.count,
32
+ };
33
+ } catch {
34
+ return {
35
+ totalCount: 0,
36
+ offset,
37
+ limit,
38
+ options: [],
39
+ count: 0,
40
+ };
41
+ }
42
+ };
43
+
44
+ const loadOptions = async (page: number, limit: number) => {
45
+ const { options, totalCount, offset } = await getResults(page, limit);
46
+
47
+ return {
48
+ options,
49
+ hasMore: offset + limit < totalCount,
50
+ };
51
+ };
52
+
4
53
  describe('AsyncAutocomplete', () => {
54
+ beforeAll(() => {
55
+ // Start the interception.
56
+ server.listen();
57
+ });
58
+
59
+ afterEach(() => {
60
+ // Remove any handlers you may have added
61
+ // in individual tests (runtime handlers).
62
+ server.resetHandlers();
63
+ jest.restoreAllMocks();
64
+ });
65
+
5
66
  test('should render successfully', () => {
6
- const { getByLabelText } = render(
7
- <AsyncAutocomplete
8
- FieldProps={{ label: 'Test' }}
9
- loadOptions={async () => ({
10
- options: ['1', '2', '3'],
11
- hasMore: false,
12
- })}
13
- />
14
- );
67
+ const { getByLabelText } = render(<AsyncAutocomplete FieldProps={{ label: 'Test' }} loadOptions={loadOptions} />);
68
+
15
69
  expect(getByLabelText('Test')).toBeTruthy();
16
70
  });
17
71
 
18
72
  test('options should be available', async () => {
19
- const loadOptions = () =>
20
- Promise.resolve({
21
- options: [{ label: 'Option 1' }],
22
- getOptionLabel: (option: { label: string }) => option.label,
23
- hasMore: false,
24
- });
25
-
26
73
  render(<AsyncAutocomplete loadOptions={loadOptions} FieldProps={{ label: 'Test' }} />);
27
74
 
28
75
  const input = screen.getByRole('combobox');
@@ -41,19 +88,7 @@ describe('AsyncAutocomplete', () => {
41
88
  });
42
89
 
43
90
  test('should call loadOptions when scroll to the bottom', async () => {
44
- const loadOptions = jest.fn();
45
- loadOptions.mockResolvedValueOnce({
46
- options: [
47
- { label: 'Option 1' },
48
- { label: 'Option 2' },
49
- { label: 'Option 3' },
50
- { label: 'Option 4' },
51
- { label: 'Option 5' },
52
- { label: 'Option 6' },
53
- ],
54
- hasMore: true,
55
- });
56
- render(<AsyncAutocomplete loadOptions={loadOptions} FieldProps={{ label: 'Test' }} />);
91
+ render(<AsyncAutocomplete loadOptions={loadOptions} limit={10} FieldProps={{ label: 'Test' }} />);
57
92
 
58
93
  const input = screen.getByRole('combobox');
59
94
  fireEvent.click(input);
@@ -63,24 +98,14 @@ describe('AsyncAutocomplete', () => {
63
98
  expect(screen.getByText('Option 1')).toBeDefined();
64
99
  });
65
100
 
66
- expect(loadOptions).toHaveBeenCalled();
67
- expect(loadOptions).toHaveBeenCalledTimes(1);
68
- expect(loadOptions).toHaveBeenCalledWith(0, 50);
69
-
70
- loadOptions.mockResolvedValueOnce({
71
- options: [{ label: 'Option 7' }],
72
- hasMore: false,
73
- });
74
-
75
101
  await act(async () => {
76
102
  const options = await screen.findByRole('listbox');
77
103
  fireEvent.scroll(options, { target: { scrollTop: options.scrollHeight } });
78
104
  });
79
105
 
80
106
  await waitFor(() => {
81
- expect(loadOptions).toHaveBeenCalled();
82
- expect(loadOptions).toHaveBeenCalledTimes(2);
83
- expect(loadOptions).toHaveBeenLastCalledWith(1, 50);
107
+ expect(screen.getByText('Option 10')).toBeDefined();
108
+ expect(() => screen.getByText('Option 20')).toThrowError();
84
109
  });
85
110
  });
86
111
  });
@@ -1,7 +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
3
  import { Autocomplete } from './Autocomplete';
4
- import { AsyncAutocomplete } from './AsyncAutocomplete';
5
4
 
6
5
  const meta: Meta<typeof Autocomplete> = {
7
6
  title: 'Form Components/Autocomplete/Autocomplete',
@@ -43,116 +42,3 @@ export const _Multi: StoryObj<typeof Autocomplete> = {
43
42
  multiple: true,
44
43
  },
45
44
  };
46
-
47
- type Org = {
48
- id: string;
49
- name: string;
50
- };
51
-
52
- const organizations: Org[] = [
53
- {
54
- id: '1',
55
- name: 'Org 1',
56
- },
57
- {
58
- id: '2',
59
- name: 'Org 2',
60
- },
61
- {
62
- id: '3',
63
- name: 'Org 3',
64
- },
65
- {
66
- id: '4',
67
- name: 'Org 4',
68
- },
69
- {
70
- id: '5',
71
- name: 'Org 5',
72
- },
73
- {
74
- id: '6',
75
- name: 'Org 6',
76
- },
77
- {
78
- id: '7',
79
- name: 'Org 7',
80
- },
81
- {
82
- id: '8',
83
- name: 'Org 8',
84
- },
85
- {
86
- id: '9',
87
- name: 'Org 9',
88
- },
89
- {
90
- id: '10',
91
- name: 'Org 10',
92
- },
93
- {
94
- id: '11',
95
- name: 'Org 11',
96
- },
97
- {
98
- id: '12',
99
- name: 'Org 12',
100
- },
101
- {
102
- id: '13',
103
- name: 'Org 13',
104
- },
105
- {
106
- id: '14',
107
- name: 'Org 14',
108
- },
109
- {
110
- id: '15',
111
- name: 'Org 15',
112
- },
113
- ];
114
-
115
- async function sleep(duration = 2500) {
116
- await new Promise((resolve) => setTimeout(resolve, duration));
117
- }
118
-
119
- const getResults = (page: number, limit: number) => {
120
- const offset = page * limit;
121
- const orgs = organizations.slice(page * offset, page * offset + limit);
122
-
123
- return {
124
- totalCount: organizations.length,
125
- offset,
126
- limit,
127
- orgs,
128
- count: orgs.length,
129
- };
130
- };
131
-
132
- const loadOptions = async (page: number, limit: number) => {
133
- await sleep(1000);
134
-
135
- const { orgs, totalCount, offset } = getResults(page, limit);
136
-
137
- return {
138
- options: orgs,
139
- hasMore: offset + limit < totalCount,
140
- };
141
- };
142
-
143
- export const _Async: StoryObj<typeof AsyncAutocomplete> = {
144
- render: (args) => {
145
- return <AsyncAutocomplete {...args} />;
146
- },
147
- parameters: {
148
- controls: {
149
- exclude: /loading(?!Text)|options/,
150
- },
151
- },
152
- args: {
153
- FieldProps: { label: 'Async Select', helperText: 'Helper Text', fullWidth: false },
154
- getOptionLabel: (val: Org) => val.name,
155
- loadOptions,
156
- limit: 10,
157
- },
158
- };
@@ -0,0 +1,37 @@
1
+ // Each exported component in the package should have its own stories file
2
+ import type { Meta, StoryObj } from '@storybook/react';
3
+
4
+ import { OrganizationAutocomplete } from './OrganizationAutocomplete';
5
+
6
+ const meta: Meta<typeof OrganizationAutocomplete> = {
7
+ title: 'Form Components/Autocomplete/OrganizationAutocomplete',
8
+ component: OrganizationAutocomplete,
9
+ tags: ['autodocs'],
10
+ args: {
11
+ id: 'example',
12
+ },
13
+ argTypes: {
14
+ multiple: {
15
+ table: {
16
+ disable: true,
17
+ },
18
+ },
19
+ },
20
+ };
21
+
22
+ export default meta;
23
+
24
+ export const _OrganizationAutocomplete: StoryObj<typeof OrganizationAutocomplete> = {
25
+ render: (args) => {
26
+ return <OrganizationAutocomplete {...args} />;
27
+ },
28
+ args: {
29
+ FieldProps: {
30
+ label: 'Organization Select',
31
+ helperText: 'Select an Organization from the list',
32
+ placeholder: 'Select...',
33
+ fullWidth: false,
34
+ },
35
+ limit: 15,
36
+ },
37
+ };
@@ -0,0 +1,31 @@
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
+
5
+ import { OrganizationAutocomplete } from './OrganizationAutocomplete';
6
+
7
+ describe('OrganizationAutocomplete', () => {
8
+ beforeAll(() => {
9
+ // Start the interception.
10
+ server.listen();
11
+ });
12
+
13
+ afterEach(() => {
14
+ // Remove any handlers you may have added
15
+ // in individual tests (runtime handlers).
16
+ server.resetHandlers();
17
+ jest.restoreAllMocks();
18
+ });
19
+
20
+ test('organizations are fetched and displayed by name', async () => {
21
+ render(<OrganizationAutocomplete FieldProps={{ label: 'Test' }} />);
22
+
23
+ const input = screen.getByRole('combobox');
24
+ fireEvent.click(input);
25
+ fireEvent.keyDown(input, { key: 'ArrowDown' });
26
+
27
+ await waitFor(() => {
28
+ expect(screen.getByText('Organization 1')).toBeDefined();
29
+ });
30
+ });
31
+ });
@@ -0,0 +1,52 @@
1
+ import { avOrganizationsApi, ApiConfig } from '@availity/api-axios';
2
+ import type { ChipTypeMap } from '@mui/material/Chip';
3
+
4
+ import { AsyncAutocomplete, AsyncAutocompleteProps } from './AsyncAutocomplete';
5
+
6
+ export type Organization = {
7
+ customerId: string;
8
+ name: string;
9
+ id: string;
10
+ createDate: string;
11
+ links: Record<string, Record<string, string>>;
12
+ };
13
+
14
+ const fetchOrgs = async (config: ApiConfig): Promise<{ options: Organization[]; hasMore: boolean }> => {
15
+ try {
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
+ };
22
+ } catch {
23
+ return {
24
+ options: [],
25
+ hasMore: false,
26
+ };
27
+ }
28
+ };
29
+
30
+ export interface OrgAutocompleteProps<
31
+ Option = Organization,
32
+ Multiple extends boolean | undefined = false,
33
+ DisableClearable extends boolean | undefined = false,
34
+ FreeSolo extends boolean | undefined = false,
35
+ ChipComponent extends React.ElementType = ChipTypeMap['defaultComponent'],
36
+ > extends Omit<AsyncAutocompleteProps<Option, Multiple, DisableClearable, FreeSolo, ChipComponent>, 'loadOptions'> {
37
+ apiConfig?: ApiConfig;
38
+ }
39
+
40
+ export const OrganizationAutocomplete = ({ apiConfig = {}, ...rest }: OrgAutocompleteProps) => {
41
+ const handleLoadOptions = async (page: number, limit: number) => {
42
+ const offset = page * limit;
43
+
44
+ const resp = await fetchOrgs({ ...apiConfig, params: { dropdown: true, ...apiConfig.params, offset, limit } });
45
+
46
+ return resp;
47
+ };
48
+
49
+ const handleGetOptionLabel = (org: Organization) => org.name;
50
+
51
+ return <AsyncAutocomplete getOptionLabel={handleGetOptionLabel} {...rest} loadOptions={handleLoadOptions} />;
52
+ };