@olaboot/esm-patient-registration-app 9.2.0 → 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.
Files changed (346) hide show
  1. package/dist/1339.js +1 -0
  2. package/dist/1339.js.map +1 -0
  3. package/dist/1480.js +1 -0
  4. package/dist/1480.js.map +1 -0
  5. package/dist/1646.js +1 -0
  6. package/dist/1646.js.map +1 -0
  7. package/dist/1789.js +1 -0
  8. package/dist/1789.js.map +1 -0
  9. package/dist/1869.js +1 -0
  10. package/dist/1869.js.map +1 -0
  11. package/dist/1877.js +1 -0
  12. package/dist/1877.js.map +1 -0
  13. package/dist/2317.js +1 -0
  14. package/dist/2317.js.map +1 -0
  15. package/dist/2416.js +1 -0
  16. package/dist/2416.js.map +1 -0
  17. package/dist/2747.js +1 -0
  18. package/dist/2747.js.map +1 -0
  19. package/dist/282.js +1 -0
  20. package/dist/282.js.map +1 -0
  21. package/dist/2881.js +1 -0
  22. package/dist/2881.js.map +1 -0
  23. package/dist/3378.js +1 -0
  24. package/dist/3378.js.map +1 -0
  25. package/dist/3720.js +1 -0
  26. package/dist/3720.js.map +1 -0
  27. package/dist/3906.js +1 -0
  28. package/dist/3906.js.map +1 -0
  29. package/dist/3963.js +1 -0
  30. package/dist/3963.js.map +1 -0
  31. package/dist/3989.js +1 -0
  32. package/dist/3989.js.map +1 -0
  33. package/dist/4106.js +1 -0
  34. package/dist/4106.js.map +1 -0
  35. package/dist/4111.js +1 -0
  36. package/dist/4111.js.map +1 -0
  37. package/dist/434.js +1 -0
  38. package/dist/434.js.map +1 -0
  39. package/dist/4348.js +1 -0
  40. package/dist/4348.js.map +1 -0
  41. package/dist/4383.js +1 -0
  42. package/dist/4383.js.map +1 -0
  43. package/dist/4658.js +1 -0
  44. package/dist/4658.js.map +1 -0
  45. package/dist/466.js +1 -0
  46. package/dist/466.js.map +1 -0
  47. package/dist/4928.js +1 -0
  48. package/dist/4928.js.map +1 -0
  49. package/dist/5117.js +1 -0
  50. package/dist/5117.js.map +1 -0
  51. package/dist/5132.js +1 -0
  52. package/dist/5132.js.map +1 -0
  53. package/dist/5145.js +1 -0
  54. package/dist/5145.js.map +1 -0
  55. package/dist/5208.js +43 -0
  56. package/dist/5208.js.map +1 -0
  57. package/dist/527.js +1 -0
  58. package/dist/527.js.map +1 -0
  59. package/dist/5280.js +1 -0
  60. package/dist/5280.js.map +1 -0
  61. package/dist/5338.js +6 -0
  62. package/dist/5338.js.map +1 -0
  63. package/dist/5503.js +1 -0
  64. package/dist/5503.js.map +1 -0
  65. package/dist/555.js +1 -0
  66. package/dist/555.js.map +1 -0
  67. package/dist/556.js +1 -0
  68. package/dist/556.js.map +1 -0
  69. package/dist/5644.js +1 -0
  70. package/dist/5644.js.map +1 -0
  71. package/dist/5697.js +1 -0
  72. package/dist/{4024.js.map → 5697.js.map} +1 -1
  73. package/dist/5940.js +1 -0
  74. package/dist/5940.js.map +1 -0
  75. package/dist/6047.js +1 -0
  76. package/dist/6047.js.map +1 -0
  77. package/dist/6371.js +1 -0
  78. package/dist/6371.js.map +1 -0
  79. package/dist/6377.js +1 -0
  80. package/dist/6377.js.map +1 -0
  81. package/dist/6388.js +1 -0
  82. package/dist/6388.js.map +1 -0
  83. package/dist/6444.js +1 -0
  84. package/dist/6444.js.map +1 -0
  85. package/dist/6508.js +1 -0
  86. package/dist/6508.js.map +1 -0
  87. package/dist/6724.js +1 -0
  88. package/dist/6724.js.map +1 -0
  89. package/dist/689.js +1 -0
  90. package/dist/689.js.map +1 -0
  91. package/dist/6904.js +1 -0
  92. package/dist/6904.js.map +1 -0
  93. package/dist/7045.js +1 -0
  94. package/dist/7045.js.map +1 -0
  95. package/dist/7175.js +1 -0
  96. package/dist/7175.js.map +1 -0
  97. package/dist/7182.js +1 -0
  98. package/dist/7182.js.map +1 -0
  99. package/dist/7649.js +1 -0
  100. package/dist/7649.js.map +1 -0
  101. package/dist/7742.js +1 -0
  102. package/dist/7742.js.map +1 -0
  103. package/dist/7912.js +1 -0
  104. package/dist/7912.js.map +1 -0
  105. package/dist/8358.js +1 -0
  106. package/dist/8358.js.map +1 -0
  107. package/dist/8359.js +1 -0
  108. package/dist/8359.js.map +1 -0
  109. package/dist/8695.js +1 -0
  110. package/dist/8695.js.map +1 -0
  111. package/dist/903.js +1 -0
  112. package/dist/903.js.map +1 -0
  113. package/dist/9061.js +1 -0
  114. package/dist/9061.js.map +1 -0
  115. package/dist/9072.js +1 -0
  116. package/dist/9072.js.map +1 -0
  117. package/dist/9397.js +1 -0
  118. package/dist/9397.js.map +1 -0
  119. package/dist/9712.js +1 -0
  120. package/dist/9712.js.map +1 -0
  121. package/dist/9771.js +1 -0
  122. package/dist/9771.js.map +1 -0
  123. package/dist/9806.js +1 -0
  124. package/dist/9806.js.map +1 -0
  125. package/dist/9816.js +1 -0
  126. package/dist/9816.js.map +1 -0
  127. package/dist/main.js +7 -6
  128. package/dist/main.js.map +1 -1
  129. package/dist/openmrs-esm-patient-registration-app.js +6 -0
  130. package/dist/{olaboot-esm-patient-registration-app.js.buildmanifest.json → openmrs-esm-patient-registration-app.js.buildmanifest.json} +540 -455
  131. package/dist/openmrs-esm-patient-registration-app.js.map +1 -0
  132. package/dist/routes.json +1 -1
  133. package/package.json +8 -9
  134. package/src/add-patient-link.extension.tsx +3 -2
  135. package/src/add-patient-link.test.tsx +2 -1
  136. package/src/config-schema.ts +1 -1
  137. package/src/index.ts +2 -24
  138. package/src/nav-link.test.tsx +1 -0
  139. package/src/offline.resources.ts +97 -31
  140. package/src/patient-registration/before-save-prompt.test.tsx +199 -0
  141. package/src/patient-registration/field/__mocks__/field.resource.ts +8 -7
  142. package/src/patient-registration/field/address/address-field.component.tsx +10 -13
  143. package/src/patient-registration/field/address/address-hierarchy-levels.component.tsx +6 -1
  144. package/src/patient-registration/field/address/address-hierarchy.test.tsx +191 -198
  145. package/src/patient-registration/field/address/address-search.component.tsx +20 -8
  146. package/src/patient-registration/field/address/address-search.scss +19 -2
  147. package/src/patient-registration/field/address/address-search.test.tsx +249 -57
  148. package/src/patient-registration/field/address/custom-address-field.component.tsx +1 -1
  149. package/src/patient-registration/field/cause-of-death/cause-of-death.component.tsx +1 -1
  150. package/src/patient-registration/field/cause-of-death/cause-of-death.test.tsx +251 -0
  151. package/src/patient-registration/field/custom-field.component.tsx +1 -1
  152. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.component.tsx +1 -1
  153. package/src/patient-registration/field/date-and-time-of-death/date-and-time-of-death.test.tsx +144 -0
  154. package/src/patient-registration/field/dob/dob.component.tsx +2 -2
  155. package/src/patient-registration/field/dob/dob.test.tsx +370 -54
  156. package/src/patient-registration/field/field.component.tsx +1 -1
  157. package/src/patient-registration/field/field.resource.ts +2 -2
  158. package/src/patient-registration/field/field.test.tsx +25 -22
  159. package/src/patient-registration/field/gender/gender-field.test.tsx +240 -54
  160. package/src/patient-registration/field/id/id-field.component.tsx +15 -5
  161. package/src/patient-registration/field/id/id-field.test.tsx +103 -47
  162. package/src/patient-registration/field/id/identifier-selection-overlay.test.tsx +346 -0
  163. package/src/patient-registration/field/name/name-field.component.tsx +2 -2
  164. package/src/patient-registration/field/name/name-field.test.tsx +282 -0
  165. package/src/patient-registration/field/obs/obs-field.test.tsx +294 -118
  166. package/src/patient-registration/field/person-attributes/coded-person-attribute-field.test.tsx +172 -108
  167. package/src/patient-registration/field/person-attributes/location-person-attribute-field.component.tsx +3 -3
  168. package/src/patient-registration/field/person-attributes/location-person-attribute-field.resource.tsx +2 -5
  169. package/src/patient-registration/field/person-attributes/person-attribute-field.component.tsx +2 -2
  170. package/src/patient-registration/field/person-attributes/person-attribute-field.test.tsx +249 -131
  171. package/src/patient-registration/field/person-attributes/person-attributes.resource.ts +1 -1
  172. package/src/patient-registration/field/person-attributes/text-person-attribute-field.test.tsx +98 -70
  173. package/src/patient-registration/field/phone/phone-field.test.tsx +100 -0
  174. package/src/patient-registration/form-manager.test.ts +6 -5
  175. package/src/patient-registration/form-manager.ts +5 -2
  176. package/src/patient-registration/input/basic-input/input/input.component.tsx +3 -121
  177. package/src/patient-registration/input/basic-input/input/input.test.tsx +151 -51
  178. package/src/patient-registration/input/basic-input/select/select-input.test.tsx +113 -33
  179. package/src/patient-registration/input/combo-input/combo-input.component.tsx +60 -24
  180. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.component.tsx +10 -101
  181. package/src/patient-registration/input/custom-input/autosuggest/autosuggest.test.tsx +144 -108
  182. package/src/patient-registration/input/custom-input/identifier/identifier-input.test.tsx +241 -177
  183. package/src/patient-registration/input/custom-input/identifier/utils.test.ts +47 -8
  184. package/src/patient-registration/input/dummy-data/dummy-data-input.component.tsx +12 -12
  185. package/src/patient-registration/input/dummy-data/dummy-data-input.test.tsx +52 -20
  186. package/src/patient-registration/input/input.scss +1 -2
  187. package/src/patient-registration/patient-registration-context.ts +5 -3
  188. package/src/patient-registration/patient-registration-hooks.ts +4 -12
  189. package/src/patient-registration/patient-registration-utils.test.ts +2 -1
  190. package/src/patient-registration/patient-registration-utils.ts +2 -98
  191. package/src/patient-registration/patient-registration.component.tsx +50 -46
  192. package/src/patient-registration/patient-registration.resource.test.tsx +4 -7
  193. package/src/patient-registration/patient-registration.resource.ts +1 -4
  194. package/src/patient-registration/patient-registration.scss +16 -3
  195. package/src/patient-registration/patient-registration.test.tsx +99 -65
  196. package/src/patient-registration/patient-registration.types.ts +17 -28
  197. package/src/patient-registration/section/death-info/death-info-section.test.tsx +130 -34
  198. package/src/patient-registration/section/demographics/demographics-section.test.tsx +122 -68
  199. package/src/patient-registration/section/patient-relationships/relationships-section.component.tsx +15 -15
  200. package/src/patient-registration/section/patient-relationships/relationships-section.test.tsx +278 -84
  201. package/src/patient-registration/section/section-wrapper.component.tsx +1 -1
  202. package/src/patient-registration/ui-components/overlay/overlay.test.tsx +104 -0
  203. package/src/patient-registration/validation/patient-registration-validation.test.ts +2 -1
  204. package/src/patient-registration/validation/patient-registration-validation.ts +9 -3
  205. package/src/root.component.tsx +2 -5
  206. package/src/widgets/cancel-patient-edit.test.tsx +48 -11
  207. package/src/widgets/delete-identifier-confirmation.test.tsx +77 -24
  208. package/src/widgets/edit-patient-details-button.component.tsx +14 -18
  209. package/src/widgets/edit-patient-details-button.scss +2 -2
  210. package/src/widgets/edit-patient-details-button.test.tsx +11 -13
  211. package/translations/am.json +9 -4
  212. package/translations/ar.json +9 -4
  213. package/translations/ar_SY.json +9 -4
  214. package/translations/bn.json +9 -4
  215. package/translations/cs.json +9 -4
  216. package/translations/de.json +120 -115
  217. package/translations/en.json +9 -4
  218. package/translations/en_US.json +9 -4
  219. package/translations/es.json +9 -4
  220. package/translations/es_MX.json +9 -4
  221. package/translations/fr.json +9 -4
  222. package/translations/he.json +9 -4
  223. package/translations/hi.json +9 -4
  224. package/translations/hi_IN.json +9 -4
  225. package/translations/id.json +9 -4
  226. package/translations/it.json +9 -4
  227. package/translations/ka.json +9 -4
  228. package/translations/km.json +9 -4
  229. package/translations/ku.json +9 -4
  230. package/translations/ky.json +9 -4
  231. package/translations/lg.json +9 -4
  232. package/translations/ne.json +9 -4
  233. package/translations/pl.json +9 -4
  234. package/translations/pt.json +9 -4
  235. package/translations/pt_BR.json +10 -5
  236. package/translations/qu.json +9 -4
  237. package/translations/ro_RO.json +9 -4
  238. package/translations/ru_RU.json +9 -4
  239. package/translations/si.json +9 -4
  240. package/translations/sq.json +9 -4
  241. package/translations/sw.json +9 -4
  242. package/translations/sw_KE.json +9 -4
  243. package/translations/tr.json +9 -4
  244. package/translations/tr_TR.json +9 -4
  245. package/translations/uk.json +9 -4
  246. package/translations/uz.json +9 -4
  247. package/translations/uz@Latn.json +9 -4
  248. package/translations/uz_UZ.json +9 -4
  249. package/translations/vi.json +9 -4
  250. package/translations/zh.json +50 -45
  251. package/translations/zh_CN.json +9 -4
  252. package/translations/zh_TW.json +9 -4
  253. package/vitest.config.ts +4 -0
  254. package/ADDRESS_CONFIGURATION.md +0 -152
  255. package/IDENTIFIER_CONFIGURATION.md +0 -142
  256. package/IMPLEMENTATION_SUMMARY.md +0 -111
  257. package/QUICK_START.md +0 -95
  258. package/address-required-fields-config.json +0 -26
  259. package/dist/126.js +0 -1
  260. package/dist/15.js +0 -1
  261. package/dist/1564.js +0 -1
  262. package/dist/1567.js +0 -1
  263. package/dist/1845.js +0 -1
  264. package/dist/1953.js +0 -1
  265. package/dist/200.js +0 -1
  266. package/dist/200.js.map +0 -1
  267. package/dist/215.js +0 -1
  268. package/dist/2178.js +0 -1
  269. package/dist/250.js +0 -1
  270. package/dist/250.js.map +0 -1
  271. package/dist/2523.js +0 -1
  272. package/dist/2523.js.map +0 -1
  273. package/dist/2566.js +0 -1
  274. package/dist/2586.js +0 -1
  275. package/dist/2586.js.map +0 -1
  276. package/dist/2716.js +0 -1
  277. package/dist/2716.js.map +0 -1
  278. package/dist/2759.js +0 -1
  279. package/dist/2821.js +0 -6
  280. package/dist/2821.js.map +0 -1
  281. package/dist/3089.js +0 -1
  282. package/dist/3089.js.map +0 -1
  283. package/dist/3230.js +0 -1
  284. package/dist/3441.js +0 -1
  285. package/dist/3565.js +0 -1
  286. package/dist/3571.js +0 -1
  287. package/dist/3571.js.map +0 -1
  288. package/dist/3746.js +0 -1
  289. package/dist/3925.js +0 -1
  290. package/dist/3946.js +0 -1
  291. package/dist/4024.js +0 -1
  292. package/dist/4744.js +0 -1
  293. package/dist/4744.js.map +0 -1
  294. package/dist/4809.js +0 -1
  295. package/dist/4894.js +0 -1
  296. package/dist/4970.js +0 -1
  297. package/dist/4970.js.map +0 -1
  298. package/dist/5130.js +0 -1
  299. package/dist/5187.js +0 -1
  300. package/dist/5491.js +0 -1
  301. package/dist/5491.js.map +0 -1
  302. package/dist/5595.js +0 -1
  303. package/dist/5961.js +0 -1
  304. package/dist/6133.js +0 -1
  305. package/dist/634.js +0 -1
  306. package/dist/634.js.map +0 -1
  307. package/dist/6456.js +0 -1
  308. package/dist/6466.js +0 -1
  309. package/dist/6613.js +0 -1
  310. package/dist/6783.js +0 -1
  311. package/dist/7073.js +0 -38
  312. package/dist/7073.js.map +0 -1
  313. package/dist/7154.js +0 -1
  314. package/dist/7154.js.map +0 -1
  315. package/dist/7348.js +0 -1
  316. package/dist/7439.js +0 -1
  317. package/dist/7439.js.map +0 -1
  318. package/dist/7543.js +0 -1
  319. package/dist/7607.js +0 -1
  320. package/dist/772.js +0 -1
  321. package/dist/7984.js +0 -1
  322. package/dist/7984.js.map +0 -1
  323. package/dist/8538.js +0 -1
  324. package/dist/8538.js.map +0 -1
  325. package/dist/8599.js +0 -1
  326. package/dist/8727.js +0 -1
  327. package/dist/8847.js +0 -1
  328. package/dist/9015.js +0 -1
  329. package/dist/906.js +0 -1
  330. package/dist/9065.js +0 -1
  331. package/dist/9182.js +0 -1
  332. package/dist/9339.js +0 -1
  333. package/dist/9453.js +0 -1
  334. package/dist/9833.js +0 -1
  335. package/dist/9833.js.map +0 -1
  336. package/dist/9856.js +0 -1
  337. package/dist/9856.js.map +0 -1
  338. package/dist/9920.js +0 -1
  339. package/dist/9938.js +0 -1
  340. package/dist/9943.js +0 -1
  341. package/dist/9943.js.map +0 -1
  342. package/dist/olaboot-esm-patient-registration-app.js +0 -5
  343. package/dist/olaboot-esm-patient-registration-app.js.map +0 -1
  344. package/example-config.json +0 -14
  345. package/jest.config.js +0 -3
  346. package/src/resource.ts +0 -12
@@ -1,49 +1,129 @@
1
1
  import React from 'react';
2
- import { render, screen } from '@testing-library/react';
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
- describe('the select input', () => {
8
- const setupSelect = async () => {
9
- render(
10
- <Formik initialValues={{ select: '' }} onSubmit={null}>
11
- <Form>
12
- <SelectInput label="Select" name="select" options={['A Option', 'B Option']} required />
13
- </Form>
14
- </Formik>,
15
- );
16
- return screen.getByLabelText('Select') as HTMLInputElement;
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
- it('exists', async () => {
20
- const input = await setupSelect();
21
- expect(input.type).toEqual('select-one');
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
- it('can input data', async () => {
25
- const user = userEvent.setup();
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
- await user.selectOptions(input, expected);
57
+ const select = await screen.findByRole('combobox', { name: /select \(optional\)/i });
58
+ expect(select).toBeInTheDocument();
59
+ });
30
60
 
31
- await expect(input.value).toEqual(expected);
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
- it('should show optional label if the input is not required', async () => {
35
- render(
36
- <Formik initialValues={{ select: '' }} onSubmit={null}>
37
- <Form>
38
- <SelectInput label="Select" name="select" options={['A Option', 'B Option']} />
39
- </Form>
40
- </Formik>,
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
- await screen.findByRole('combobox');
122
+ await user.selectOptions(select, 'B Option');
44
123
 
45
- const selectInput = screen.getByRole('combobox', { name: 'Select (optional)' }) as HTMLSelectElement;
46
- expect(selectInput.labels).toHaveLength(1);
47
- expect(selectInput.labels[0]).toHaveTextContent('Select (optional)');
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
- [x: string]: any;
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> = ({ entries, name, fieldProps, handleInputChange, handleSelection }) => {
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' && highlightedEntry > -1) {
64
- handleOptionClick(filteredEntries[highlightedEntry], e);
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
- {filteredEntries.map((entry, indx) => (
103
- <div
104
- className={classNames('cds--list-box__menu-item', {
105
- 'cds--list-box__menu-item--highlighted': indx === highlightedEntry,
106
- })}
107
- key={indx}
108
- id="downshift-1-item-0"
109
- role="option"
110
- tabIndex={-1}
111
- aria-selected="true"
112
- onClick={() => handleOptionClick(entry)}>
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-item__option', styles.comboInputItemOption, {
115
- 'cds--list-box__menu-item--active': entry === value,
116
- })}>
117
- {entry}
118
- {entry === value && <SelectionTick />}
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, { type HTMLAttributes, useEffect, useRef, useState } from '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
- // FIXME Temporarily included types from Carbon
7
- type InputPropsBase = Omit<HTMLAttributes<HTMLInputElement>, 'onChange'>;
6
+ type SearchProps = React.ComponentProps<typeof Search>;
8
7
 
9
- interface SearchProps extends InputPropsBase {
10
- /**
11
- * Specify an optional value for the `autocomplete` property on the underlying
12
- * `<input>`, defaults to "off"
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: React.FC<AutosuggestProps> = ({
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>