@olaboot/esm-patient-registration-app 9.2.1 → 10.0.2
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/dist/1339.js +1 -0
- package/dist/1339.js.map +1 -0
- package/dist/1480.js +1 -0
- package/dist/1480.js.map +1 -0
- package/dist/1646.js +1 -0
- package/dist/1646.js.map +1 -0
- package/dist/1789.js +1 -0
- package/dist/1789.js.map +1 -0
- package/dist/1869.js +1 -0
- package/dist/1869.js.map +1 -0
- package/dist/1877.js +1 -0
- package/dist/1877.js.map +1 -0
- package/dist/2317.js +1 -0
- package/dist/2317.js.map +1 -0
- package/dist/2416.js +1 -0
- package/dist/2416.js.map +1 -0
- package/dist/2747.js +1 -0
- package/dist/2747.js.map +1 -0
- package/dist/282.js +1 -0
- package/dist/282.js.map +1 -0
- package/dist/2881.js +1 -0
- package/dist/2881.js.map +1 -0
- package/dist/3378.js +1 -0
- package/dist/3378.js.map +1 -0
- package/dist/3720.js +1 -0
- package/dist/3720.js.map +1 -0
- package/dist/3906.js +1 -0
- package/dist/3906.js.map +1 -0
- package/dist/3963.js +1 -0
- package/dist/3963.js.map +1 -0
- package/dist/3989.js +1 -0
- package/dist/3989.js.map +1 -0
- package/dist/4106.js +1 -0
- package/dist/4106.js.map +1 -0
- package/dist/4111.js +1 -0
- package/dist/4111.js.map +1 -0
- package/dist/434.js +1 -0
- package/dist/434.js.map +1 -0
- package/dist/4348.js +1 -0
- package/dist/4348.js.map +1 -0
- package/dist/4383.js +1 -0
- package/dist/4383.js.map +1 -0
- package/dist/4658.js +1 -0
- package/dist/4658.js.map +1 -0
- package/dist/466.js +1 -0
- package/dist/466.js.map +1 -0
- package/dist/4928.js +1 -0
- package/dist/4928.js.map +1 -0
- package/dist/5117.js +1 -0
- package/dist/5117.js.map +1 -0
- package/dist/5132.js +1 -0
- package/dist/5132.js.map +1 -0
- package/dist/5145.js +1 -0
- package/dist/5145.js.map +1 -0
- package/dist/5208.js +43 -0
- package/dist/5208.js.map +1 -0
- package/dist/527.js +1 -0
- package/dist/527.js.map +1 -0
- package/dist/5280.js +1 -0
- package/dist/5280.js.map +1 -0
- package/dist/5338.js +6 -0
- package/dist/5338.js.map +1 -0
- package/dist/5503.js +1 -0
- package/dist/5503.js.map +1 -0
- package/dist/555.js +1 -0
- package/dist/555.js.map +1 -0
- package/dist/556.js +1 -0
- package/dist/556.js.map +1 -0
- package/dist/5644.js +1 -0
- package/dist/5644.js.map +1 -0
- package/dist/5697.js +1 -0
- package/dist/{4024.js.map → 5697.js.map} +1 -1
- package/dist/5940.js +1 -0
- package/dist/5940.js.map +1 -0
- package/dist/6047.js +1 -0
- package/dist/6047.js.map +1 -0
- package/dist/6371.js +1 -0
- package/dist/6371.js.map +1 -0
- package/dist/6377.js +1 -0
- package/dist/6377.js.map +1 -0
- package/dist/6388.js +1 -0
- package/dist/6388.js.map +1 -0
- package/dist/6444.js +1 -0
- package/dist/6444.js.map +1 -0
- package/dist/6508.js +1 -0
- package/dist/6508.js.map +1 -0
- package/dist/6724.js +1 -0
- package/dist/6724.js.map +1 -0
- package/dist/689.js +1 -0
- package/dist/689.js.map +1 -0
- package/dist/6904.js +1 -0
- package/dist/6904.js.map +1 -0
- package/dist/7045.js +1 -0
- package/dist/7045.js.map +1 -0
- package/dist/7175.js +1 -0
- package/dist/7175.js.map +1 -0
- package/dist/7182.js +1 -0
- package/dist/7182.js.map +1 -0
- package/dist/7649.js +1 -0
- package/dist/7649.js.map +1 -0
- package/dist/7742.js +1 -0
- package/dist/7742.js.map +1 -0
- package/dist/7912.js +1 -0
- package/dist/7912.js.map +1 -0
- package/dist/8358.js +1 -0
- package/dist/8358.js.map +1 -0
- package/dist/8359.js +1 -0
- package/dist/8359.js.map +1 -0
- package/dist/8695.js +1 -0
- package/dist/8695.js.map +1 -0
- package/dist/903.js +1 -0
- package/dist/903.js.map +1 -0
- package/dist/9061.js +1 -0
- package/dist/9061.js.map +1 -0
- package/dist/9072.js +1 -0
- package/dist/9072.js.map +1 -0
- package/dist/9397.js +1 -0
- package/dist/9397.js.map +1 -0
- package/dist/9712.js +1 -0
- package/dist/9712.js.map +1 -0
- package/dist/9771.js +1 -0
- package/dist/9771.js.map +1 -0
- package/dist/9806.js +1 -0
- package/dist/9806.js.map +1 -0
- package/dist/9816.js +1 -0
- package/dist/9816.js.map +1 -0
- package/dist/main.js +7 -6
- package/dist/main.js.map +1 -1
- package/dist/openmrs-esm-patient-registration-app.js +6 -0
- package/dist/{olaboot-esm-patient-registration-app.js.buildmanifest.json → openmrs-esm-patient-registration-app.js.buildmanifest.json} +540 -455
- package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
- package/dist/routes.json +1 -1
- package/package.json +8 -9
- package/src/add-patient-link.extension.tsx +3 -2
- package/src/add-patient-link.test.tsx +2 -1
- package/src/config-schema.ts +507 -507
- package/src/constants.ts +1 -1
- package/src/index.ts +2 -24
- package/src/nav-link.test.tsx +1 -0
- package/src/offline.resources.ts +97 -31
- package/src/patient-registration/before-save-prompt.test.tsx +199 -0
- package/src/patient-registration/field/__mocks__/field.resource.ts +8 -7
- package/src/patient-registration/field/address/address-field.component.tsx +183 -186
- package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +6 -1
- package/src/patient-registration/field/address/address-hierarchy.test.tsx +289 -296
- package/src/patient-registration/field/address/address-search.component.tsx +20 -8
- package/src/patient-registration/field/address/address-search.scss +19 -2
- package/src/patient-registration/field/address/address-search.test.tsx +333 -141
- package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
- package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +1 -1
- package/src/patient-registration/field/cause-of-death/cause-of-death.test.tsx +251 -0
- package/src/patient-registration/field/custom-field.component.tsx +1 -1
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
- package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.test.tsx +144 -0
- package/src/patient-registration/field/dob/dob.component.tsx +2 -2
- package/src/patient-registration/field/dob/dob.test.tsx +370 -54
- package/src/patient-registration/field/field.component.tsx +1 -1
- package/src/patient-registration/field/field.resource.ts +2 -2
- package/src/patient-registration/field/field.test.tsx +333 -330
- package/src/patient-registration/field/gender/gender-field.test.tsx +240 -54
- package/src/patient-registration/field/id/id-field.component.tsx +146 -144
- package/src/patient-registration/field/id/id-field.test.tsx +177 -121
- package/src/patient-registration/field/id/identifier-selection-overlay.component.tsx +200 -200
- package/src/patient-registration/field/id/identifier-selection-overlay.test.tsx +346 -0
- package/src/patient-registration/field/name/name-field.component.tsx +2 -2
- package/src/patient-registration/field/name/name-field.test.tsx +282 -0
- package/src/patient-registration/field/obs/obs-field.test.tsx +294 -118
- package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +172 -108
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +3 -3
- package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +2 -5
- package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +2 -2
- package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +249 -131
- package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +1 -1
- package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +98 -70
- package/src/patient-registration/field/phone/phone-field.test.tsx +100 -0
- package/src/patient-registration/form-manager.test.ts +6 -5
- package/src/patient-registration/form-manager.ts +5 -2
- package/src/patient-registration/input/basic-input/input/input.component.tsx +3 -121
- package/src/patient-registration/input/basic-input/input/input.test.tsx +151 -51
- package/src/patient-registration/input/basic-input/select/select-input.test.tsx +113 -33
- package/src/patient-registration/input/combo-input/combo-input.component.tsx +60 -24
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +10 -101
- package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +144 -108
- package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +241 -177
- package/src/patient-registration/input/custom-input/identifier/utils.test.ts +47 -8
- package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +12 -12
- package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +52 -20
- package/src/patient-registration/input/input.scss +1 -2
- package/src/patient-registration/patient-registration-context.ts +5 -3
- package/src/patient-registration/patient-registration-hooks.ts +4 -12
- package/src/patient-registration/patient-registration-utils.test.ts +2 -1
- package/src/patient-registration/patient-registration-utils.ts +2 -98
- package/src/patient-registration/patient-registration.component.tsx +50 -46
- package/src/patient-registration/patient-registration.resource.test.tsx +4 -7
- package/src/patient-registration/patient-registration.resource.ts +1 -4
- package/src/patient-registration/patient-registration.scss +16 -3
- package/src/patient-registration/patient-registration.test.tsx +614 -580
- package/src/patient-registration/patient-registration.types.ts +17 -28
- package/src/patient-registration/section/death-info/death-info-section.test.tsx +130 -34
- package/src/patient-registration/section/demographics/demographics-section.test.tsx +122 -68
- package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +15 -15
- package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +278 -84
- package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
- package/src/patient-registration/ui-components/overlay/overlay.test.tsx +104 -0
- package/src/patient-registration/validation/patient-registration-validation.test.ts +2 -1
- package/src/patient-registration/validation/patient-registration-validation.ts +9 -3
- package/src/root.component.tsx +2 -5
- package/src/widgets/cancel-patient-edit.test.tsx +48 -11
- package/src/widgets/delete-identifier-confirmation.test.tsx +77 -24
- package/src/widgets/edit-patient-details-button.component.tsx +14 -18
- package/src/widgets/edit-patient-details-button.scss +2 -2
- package/src/widgets/edit-patient-details-button.test.tsx +11 -13
- package/translations/am.json +9 -4
- package/translations/ar.json +9 -4
- package/translations/ar_SY.json +9 -4
- package/translations/bn.json +9 -4
- package/translations/cs.json +9 -4
- package/translations/de.json +120 -115
- package/translations/en.json +10 -4
- package/translations/en_US.json +9 -4
- package/translations/es.json +9 -4
- package/translations/es_MX.json +9 -4
- package/translations/fr.json +9 -4
- package/translations/he.json +9 -4
- package/translations/hi.json +9 -4
- package/translations/hi_IN.json +9 -4
- package/translations/id.json +9 -4
- package/translations/it.json +9 -4
- package/translations/ka.json +9 -4
- package/translations/km.json +9 -4
- package/translations/ku.json +9 -4
- package/translations/ky.json +9 -4
- package/translations/lg.json +9 -4
- package/translations/ne.json +9 -4
- package/translations/pl.json +9 -4
- package/translations/pt.json +9 -4
- package/translations/pt_BR.json +10 -5
- package/translations/qu.json +9 -4
- package/translations/ro_RO.json +9 -4
- package/translations/ru_RU.json +9 -4
- package/translations/si.json +9 -4
- package/translations/sq.json +9 -4
- package/translations/sw.json +9 -4
- package/translations/sw_KE.json +9 -4
- package/translations/tr.json +9 -4
- package/translations/tr_TR.json +9 -4
- package/translations/uk.json +9 -4
- package/translations/uz.json +9 -4
- package/translations/uz@Latn.json +9 -4
- package/translations/uz_UZ.json +9 -4
- package/translations/vi.json +9 -4
- package/translations/zh.json +50 -45
- package/translations/zh_CN.json +9 -4
- package/translations/zh_TW.json +9 -4
- package/vitest.config.ts +4 -0
- package/dist/126.js +0 -1
- package/dist/15.js +0 -1
- package/dist/1564.js +0 -1
- package/dist/1567.js +0 -1
- package/dist/1845.js +0 -1
- package/dist/1953.js +0 -1
- package/dist/200.js +0 -1
- package/dist/200.js.map +0 -1
- package/dist/215.js +0 -1
- package/dist/2178.js +0 -1
- package/dist/250.js +0 -1
- package/dist/250.js.map +0 -1
- package/dist/2523.js +0 -1
- package/dist/2523.js.map +0 -1
- package/dist/2566.js +0 -1
- package/dist/2586.js +0 -1
- package/dist/2586.js.map +0 -1
- package/dist/2716.js +0 -1
- package/dist/2716.js.map +0 -1
- package/dist/2759.js +0 -1
- package/dist/2821.js +0 -6
- package/dist/2821.js.map +0 -1
- package/dist/3089.js +0 -1
- package/dist/3089.js.map +0 -1
- package/dist/3230.js +0 -1
- package/dist/3441.js +0 -1
- package/dist/3565.js +0 -1
- package/dist/3571.js +0 -1
- package/dist/3571.js.map +0 -1
- package/dist/3746.js +0 -1
- package/dist/3925.js +0 -1
- package/dist/3946.js +0 -1
- package/dist/4024.js +0 -1
- package/dist/4744.js +0 -1
- package/dist/4744.js.map +0 -1
- package/dist/4809.js +0 -1
- package/dist/4894.js +0 -1
- package/dist/4970.js +0 -1
- package/dist/4970.js.map +0 -1
- package/dist/5130.js +0 -1
- package/dist/5187.js +0 -1
- package/dist/5491.js +0 -1
- package/dist/5491.js.map +0 -1
- package/dist/5595.js +0 -1
- package/dist/5961.js +0 -1
- package/dist/6133.js +0 -1
- package/dist/634.js +0 -1
- package/dist/634.js.map +0 -1
- package/dist/6456.js +0 -1
- package/dist/6466.js +0 -1
- package/dist/6613.js +0 -1
- package/dist/6783.js +0 -1
- package/dist/7073.js +0 -38
- package/dist/7073.js.map +0 -1
- package/dist/7154.js +0 -1
- package/dist/7154.js.map +0 -1
- package/dist/7348.js +0 -1
- package/dist/7439.js +0 -1
- package/dist/7439.js.map +0 -1
- package/dist/7543.js +0 -1
- package/dist/7607.js +0 -1
- package/dist/772.js +0 -1
- package/dist/7984.js +0 -1
- package/dist/7984.js.map +0 -1
- package/dist/8538.js +0 -1
- package/dist/8538.js.map +0 -1
- package/dist/8599.js +0 -1
- package/dist/8727.js +0 -1
- package/dist/8847.js +0 -1
- package/dist/9015.js +0 -1
- package/dist/906.js +0 -1
- package/dist/9065.js +0 -1
- package/dist/9182.js +0 -1
- package/dist/9339.js +0 -1
- package/dist/9453.js +0 -1
- package/dist/9833.js +0 -1
- package/dist/9833.js.map +0 -1
- package/dist/9856.js +0 -1
- package/dist/9856.js.map +0 -1
- package/dist/9920.js +0 -1
- package/dist/9938.js +0 -1
- package/dist/9943.js +0 -1
- package/dist/9943.js.map +0 -1
- package/dist/olaboot-esm-patient-registration-app.js +0 -5
- package/dist/olaboot-esm-patient-registration-app.js.map +0 -1
- package/jest.config.js +0 -3
- package/src/resource.ts +0 -12
|
@@ -1,49 +1,129 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { vi, describe, it, expect } from 'vitest';
|
|
3
|
+
import { render, screen, waitFor } from '@testing-library/react';
|
|
3
4
|
import userEvent from '@testing-library/user-event';
|
|
4
5
|
import { Formik, Form } from 'formik';
|
|
5
6
|
import { SelectInput } from './select-input.component';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Helper to render SelectInput component with Formik.
|
|
10
|
+
*/
|
|
11
|
+
function renderSelectInput(
|
|
12
|
+
props: {
|
|
13
|
+
name?: string;
|
|
14
|
+
label?: string;
|
|
15
|
+
options?: string[];
|
|
16
|
+
required?: boolean;
|
|
17
|
+
} = {},
|
|
18
|
+
initialValues: Record<string, string> = {},
|
|
19
|
+
) {
|
|
20
|
+
const defaultProps = {
|
|
21
|
+
name: 'select',
|
|
22
|
+
label: 'Select',
|
|
23
|
+
options: ['A Option', 'B Option'],
|
|
24
|
+
required: false,
|
|
25
|
+
...props,
|
|
17
26
|
};
|
|
18
27
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
28
|
+
return render(
|
|
29
|
+
<Formik initialValues={{ select: '', ...initialValues }} onSubmit={() => {}}>
|
|
30
|
+
<Form>
|
|
31
|
+
<SelectInput {...defaultProps} />
|
|
32
|
+
</Form>
|
|
33
|
+
</Formik>,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('SelectInput component', () => {
|
|
38
|
+
describe('Rendering', () => {
|
|
39
|
+
it('renders the select input', async () => {
|
|
40
|
+
renderSelectInput({ required: true });
|
|
41
|
+
|
|
42
|
+
const select = await screen.findByRole('combobox', { name: /select/i });
|
|
43
|
+
expect(select).toBeInTheDocument();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('shows required label when field is required', async () => {
|
|
47
|
+
renderSelectInput({ required: true });
|
|
48
|
+
|
|
49
|
+
const select = await screen.findByRole('combobox', { name: /^select$/i });
|
|
50
|
+
expect(select).toBeInTheDocument();
|
|
51
|
+
expect(screen.queryByRole('combobox', { name: /select \(optional\)/i })).not.toBeInTheDocument();
|
|
52
|
+
});
|
|
23
53
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const input = await setupSelect();
|
|
27
|
-
const expected = 'A Option';
|
|
54
|
+
it('shows optional label when field is not required', async () => {
|
|
55
|
+
renderSelectInput({ required: false });
|
|
28
56
|
|
|
29
|
-
|
|
57
|
+
const select = await screen.findByRole('combobox', { name: /select \(optional\)/i });
|
|
58
|
+
expect(select).toBeInTheDocument();
|
|
59
|
+
});
|
|
30
60
|
|
|
31
|
-
|
|
61
|
+
it('renders all provided options', async () => {
|
|
62
|
+
renderSelectInput({ options: ['Option 1', 'Option 2', 'Option 3'], required: true });
|
|
63
|
+
|
|
64
|
+
await screen.findByRole('combobox');
|
|
65
|
+
|
|
66
|
+
expect(screen.getByText('Option 1')).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByText('Option 2')).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByText('Option 3')).toBeInTheDocument();
|
|
69
|
+
});
|
|
32
70
|
});
|
|
33
71
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
72
|
+
describe('User interaction', () => {
|
|
73
|
+
it('allows user to select an option', async () => {
|
|
74
|
+
const user = userEvent.setup();
|
|
75
|
+
renderSelectInput({ required: true });
|
|
76
|
+
|
|
77
|
+
const select = await screen.findByRole('combobox', { name: /^select$/i });
|
|
78
|
+
await user.selectOptions(select, 'A Option');
|
|
79
|
+
|
|
80
|
+
await waitFor(() => {
|
|
81
|
+
expect(select).toHaveValue('A Option');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('updates form values when user selects an option', async () => {
|
|
86
|
+
const user = userEvent.setup();
|
|
87
|
+
let formValues: Record<string, string> = {};
|
|
88
|
+
|
|
89
|
+
render(
|
|
90
|
+
<Formik initialValues={{ select: '' }} onSubmit={() => {}}>
|
|
91
|
+
{({ values }) => {
|
|
92
|
+
formValues = values as Record<string, string>;
|
|
93
|
+
return (
|
|
94
|
+
<Form>
|
|
95
|
+
<SelectInput name="select" label="Select" options={['A Option', 'B Option']} required />
|
|
96
|
+
</Form>
|
|
97
|
+
);
|
|
98
|
+
}}
|
|
99
|
+
</Formik>,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
const select = await screen.findByRole('combobox');
|
|
103
|
+
await user.selectOptions(select, 'B Option');
|
|
104
|
+
|
|
105
|
+
await waitFor(() => {
|
|
106
|
+
expect(formValues.select).toBe('B Option');
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('allows user to switch between options', async () => {
|
|
111
|
+
const user = userEvent.setup();
|
|
112
|
+
renderSelectInput({ required: true });
|
|
113
|
+
|
|
114
|
+
const select = await screen.findByRole('combobox', { name: /^select$/i });
|
|
115
|
+
|
|
116
|
+
await user.selectOptions(select, 'A Option');
|
|
117
|
+
|
|
118
|
+
await waitFor(() => {
|
|
119
|
+
expect(select).toHaveValue('A Option');
|
|
120
|
+
});
|
|
42
121
|
|
|
43
|
-
|
|
122
|
+
await user.selectOptions(select, 'B Option');
|
|
44
123
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
124
|
+
await waitFor(() => {
|
|
125
|
+
expect(select).toHaveValue('B Option');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
48
128
|
});
|
|
49
129
|
});
|
|
@@ -1,23 +1,36 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
2
2
|
import classNames from 'classnames';
|
|
3
|
+
import { useTranslation } from 'react-i18next';
|
|
3
4
|
import { TextInput, Layer } from '@carbon/react';
|
|
4
5
|
import SelectionTick from './selection-tick.component';
|
|
5
6
|
import styles from '../input.scss';
|
|
6
7
|
|
|
8
|
+
type TextInputProps = React.ComponentProps<typeof TextInput>;
|
|
9
|
+
|
|
7
10
|
interface ComboInputProps {
|
|
8
11
|
entries: Array<string>;
|
|
12
|
+
error?: Error;
|
|
13
|
+
isLoading?: boolean;
|
|
9
14
|
name: string;
|
|
10
15
|
fieldProps: {
|
|
11
16
|
value: string;
|
|
12
17
|
labelText: string;
|
|
13
18
|
id?: string;
|
|
14
|
-
|
|
15
|
-
};
|
|
19
|
+
} & Omit<TextInputProps, 'value' | 'labelText' | 'id' | 'onChange' | 'onFocus' | 'autoComplete' | 'onKeyDown'>;
|
|
16
20
|
handleInputChange: (newValue: string) => void;
|
|
17
|
-
handleSelection: (newSelection) => void;
|
|
21
|
+
handleSelection: (newSelection: string) => void;
|
|
18
22
|
}
|
|
19
23
|
|
|
20
|
-
const ComboInput: React.FC<ComboInputProps> = ({
|
|
24
|
+
const ComboInput: React.FC<ComboInputProps> = ({
|
|
25
|
+
entries,
|
|
26
|
+
error,
|
|
27
|
+
isLoading,
|
|
28
|
+
name,
|
|
29
|
+
fieldProps,
|
|
30
|
+
handleInputChange,
|
|
31
|
+
handleSelection,
|
|
32
|
+
}) => {
|
|
33
|
+
const { t } = useTranslation();
|
|
21
34
|
const [highlightedEntry, setHighlightedEntry] = useState(-1);
|
|
22
35
|
const { value = '' } = fieldProps;
|
|
23
36
|
const [showEntries, setShowEntries] = useState(false);
|
|
@@ -60,15 +73,18 @@ const ComboInput: React.FC<ComboInputProps> = ({ entries, name, fieldProps, hand
|
|
|
60
73
|
setHighlightedEntry((prev) => Math.max(-1, prev - 1));
|
|
61
74
|
} else if (e.key === 'ArrowDown') {
|
|
62
75
|
setHighlightedEntry((prev) => Math.min(totalResults - 1, prev + 1));
|
|
63
|
-
} else if (e.key === 'Enter'
|
|
64
|
-
|
|
76
|
+
} else if (e.key === 'Enter') {
|
|
77
|
+
e.preventDefault();
|
|
78
|
+
if (highlightedEntry > -1) {
|
|
79
|
+
handleOptionClick(filteredEntries[highlightedEntry]);
|
|
80
|
+
}
|
|
65
81
|
}
|
|
66
82
|
},
|
|
67
83
|
[highlightedEntry, handleOptionClick, filteredEntries, setHighlightedEntry, setShowEntries],
|
|
68
84
|
);
|
|
69
85
|
|
|
70
86
|
useEffect(() => {
|
|
71
|
-
const listener = (e) => {
|
|
87
|
+
const listener = (e: MouseEvent) => {
|
|
72
88
|
if (!comboInputRef.current.contains(e.target as Node)) {
|
|
73
89
|
setShowEntries(false);
|
|
74
90
|
setHighlightedEntry(-1);
|
|
@@ -99,26 +115,46 @@ const ComboInput: React.FC<ComboInputProps> = ({ entries, name, fieldProps, hand
|
|
|
99
115
|
{showEntries && (
|
|
100
116
|
<div className="cds--combo-box cds--list-box cds--list-box--expanded">
|
|
101
117
|
<div id="downshift-1-menu" className="cds--list-box__menu" role="listbox">
|
|
102
|
-
{
|
|
103
|
-
<div
|
|
104
|
-
className={classNames('cds--list-box__menu-
|
|
105
|
-
'
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
118
|
+
{isLoading ? (
|
|
119
|
+
<div className="cds--list-box__menu-item">
|
|
120
|
+
<div className={classNames('cds--list-box__menu-item__option', styles.comboInputItemOption)}>
|
|
121
|
+
{t('searching', 'Searching...')}
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
) : error ? (
|
|
125
|
+
<div className="cds--list-box__menu-item">
|
|
126
|
+
<div className={classNames('cds--list-box__menu-item__option', styles.comboInputItemOption)}>
|
|
127
|
+
{t('errorFetchingResults', 'Error fetching results')}
|
|
128
|
+
</div>
|
|
129
|
+
</div>
|
|
130
|
+
) : filteredEntries.length > 0 ? (
|
|
131
|
+
filteredEntries.map((entry, indx) => (
|
|
113
132
|
<div
|
|
114
|
-
className={classNames('cds--list-box__menu-
|
|
115
|
-
'cds--list-box__menu-item--
|
|
116
|
-
})}
|
|
117
|
-
{
|
|
118
|
-
|
|
133
|
+
className={classNames('cds--list-box__menu-item', {
|
|
134
|
+
'cds--list-box__menu-item--highlighted': indx === highlightedEntry,
|
|
135
|
+
})}
|
|
136
|
+
key={indx}
|
|
137
|
+
id="downshift-1-item-0"
|
|
138
|
+
role="option"
|
|
139
|
+
tabIndex={-1}
|
|
140
|
+
aria-selected="true"
|
|
141
|
+
onClick={() => handleOptionClick(entry)}>
|
|
142
|
+
<div
|
|
143
|
+
className={classNames('cds--list-box__menu-item__option', styles.comboInputItemOption, {
|
|
144
|
+
'cds--list-box__menu-item--active': entry === value,
|
|
145
|
+
})}>
|
|
146
|
+
{entry}
|
|
147
|
+
{entry === value && <SelectionTick />}
|
|
148
|
+
</div>
|
|
149
|
+
</div>
|
|
150
|
+
))
|
|
151
|
+
) : value ? (
|
|
152
|
+
<div className="cds--list-box__menu-item">
|
|
153
|
+
<div className={classNames('cds--list-box__menu-item__option', styles.comboInputItemOption)}>
|
|
154
|
+
{t('noMatchingResults', 'No matching results')}
|
|
119
155
|
</div>
|
|
120
156
|
</div>
|
|
121
|
-
)
|
|
157
|
+
) : null}
|
|
122
158
|
</div>
|
|
123
159
|
</div>
|
|
124
160
|
)}
|
|
@@ -1,112 +1,20 @@
|
|
|
1
|
-
import React, {
|
|
1
|
+
import React, { useEffect, useRef, useState } from 'react';
|
|
2
2
|
import { Layer, Search } from '@carbon/react';
|
|
3
3
|
import classNames from 'classnames';
|
|
4
4
|
import styles from './autosuggest.scss';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
|
|
6
|
+
type SearchProps = React.ComponentProps<typeof Search>;
|
|
8
7
|
|
|
9
|
-
interface
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*/
|
|
14
|
-
autoComplete?: string;
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Specify an optional className to be applied to the container node
|
|
18
|
-
*/
|
|
19
|
-
className?: string;
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Specify a label to be read by screen readers on the "close" button
|
|
23
|
-
*/
|
|
24
|
-
closeButtonLabelText?: string;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Optionally provide the default value of the `<input>`
|
|
28
|
-
*/
|
|
29
|
-
defaultValue?: string | number;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Specify whether the `<input>` should be disabled
|
|
33
|
-
*/
|
|
34
|
-
disabled?: boolean;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Specify whether or not ExpandableSearch should render expanded or not
|
|
38
|
-
*/
|
|
39
|
-
isExpanded?: boolean;
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Specify a custom `id` for the input
|
|
43
|
-
*/
|
|
44
|
-
id?: string;
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Provide the label text for the Search icon
|
|
48
|
-
*/
|
|
49
|
-
labelText: React.ReactNode;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Optional callback called when the search value changes.
|
|
53
|
-
*/
|
|
54
|
-
onChange?(e: React.ChangeEvent<HTMLInputElement>): void;
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Optional callback called when the search value is cleared.
|
|
58
|
-
*/
|
|
59
|
-
onClear?(): void;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Optional callback called when the magnifier icon is clicked in ExpandableSearch.
|
|
63
|
-
*/
|
|
64
|
-
onExpand?(e: React.MouseEvent<HTMLDivElement> | React.KeyboardEvent<HTMLDivElement>): void;
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Provide an optional placeholder text for the Search.
|
|
68
|
-
* Note: if the label and placeholder differ,
|
|
69
|
-
* VoiceOver on Mac will read both
|
|
70
|
-
*/
|
|
71
|
-
placeholder?: string;
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Rendered icon for the Search.
|
|
75
|
-
* Can be a React component class
|
|
76
|
-
*/
|
|
77
|
-
renderIcon?: React.ComponentType | React.FunctionComponent;
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Specify the role for the underlying `<input>`, defaults to `searchbox`
|
|
81
|
-
*/
|
|
82
|
-
role?: string;
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Specify the size of the Search
|
|
86
|
-
*/
|
|
87
|
-
size?: 'sm' | 'md' | 'lg';
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Optional prop to specify the type of the `<input>`
|
|
91
|
-
*/
|
|
92
|
-
type?: string;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Specify the value of the `<input>`
|
|
96
|
-
*/
|
|
97
|
-
value?: string | number;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
interface AutosuggestProps extends SearchProps {
|
|
101
|
-
getDisplayValue: (suggestion: any) => string;
|
|
102
|
-
getFieldValue: (suggestion: any) => string;
|
|
103
|
-
getSearchResults: (query: string) => Promise<Array<any>>;
|
|
8
|
+
interface AutosuggestProps<Suggestion = unknown> extends SearchProps {
|
|
9
|
+
getDisplayValue: (suggestion: Suggestion) => string;
|
|
10
|
+
getFieldValue: (suggestion: Suggestion) => string;
|
|
11
|
+
getSearchResults: (query: string) => Promise<Array<Suggestion>>;
|
|
104
12
|
onSuggestionSelected: (field: string, value: string) => void;
|
|
105
13
|
invalid?: boolean | undefined;
|
|
106
14
|
invalidText?: string | undefined;
|
|
107
15
|
}
|
|
108
16
|
|
|
109
|
-
export const Autosuggest
|
|
17
|
+
export const Autosuggest = <Suggestion = unknown,>({
|
|
110
18
|
getDisplayValue,
|
|
111
19
|
getFieldValue,
|
|
112
20
|
getSearchResults,
|
|
@@ -114,8 +22,8 @@ export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
|
114
22
|
invalid,
|
|
115
23
|
invalidText,
|
|
116
24
|
...searchProps
|
|
117
|
-
}) => {
|
|
118
|
-
const [suggestions, setSuggestions] = useState([]);
|
|
25
|
+
}: AutosuggestProps<Suggestion>) => {
|
|
26
|
+
const [suggestions, setSuggestions] = useState<Array<Suggestion>>([]);
|
|
119
27
|
const searchBox = useRef(null);
|
|
120
28
|
const wrapper = useRef(null);
|
|
121
29
|
const { id: name, labelText } = searchProps;
|
|
@@ -169,6 +77,7 @@ export const Autosuggest: React.FC<AutosuggestProps> = ({
|
|
|
169
77
|
onClear={handleClear}
|
|
170
78
|
ref={searchBox}
|
|
171
79
|
className={styles.autocompleteSearch}
|
|
80
|
+
labelText={labelText}
|
|
172
81
|
{...searchProps}
|
|
173
82
|
/>
|
|
174
83
|
</Layer>
|