@incodetech/web 2.0.0-alpha.2 → 2.0.0-alpha.3

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 (256) hide show
  1. package/dist/asset-manifest.json +16 -16
  2. package/package.json +5 -2
  3. package/.turbo/turbo-build.log +0 -58
  4. package/.turbo/turbo-coverage.log +0 -23
  5. package/.turbo/turbo-format.log +0 -6
  6. package/.turbo/turbo-lint$colon$fix.log +0 -6
  7. package/.turbo/turbo-lint.log +0 -6
  8. package/.turbo/turbo-test.log +0 -1033
  9. package/.turbo/turbo-typecheck.log +0 -5
  10. package/coverage/base.css +0 -224
  11. package/coverage/block-navigation.js +0 -87
  12. package/coverage/email/email.tsx.html +0 -850
  13. package/coverage/email/emailInput.tsx.html +0 -340
  14. package/coverage/email/index.html +0 -131
  15. package/coverage/favicon.png +0 -0
  16. package/coverage/flow/flow.tsx.html +0 -961
  17. package/coverage/flow/flowCompleted.tsx.html +0 -448
  18. package/coverage/flow/flowInit.ts.html +0 -367
  19. package/coverage/flow/flowStart.tsx.html +0 -208
  20. package/coverage/flow/index.html +0 -221
  21. package/coverage/flow/preloadFlow.ts.html +0 -598
  22. package/coverage/flow/unsupportedModule.tsx.html +0 -202
  23. package/coverage/flow/useFlowInitialization.ts.html +0 -469
  24. package/coverage/flow/useModuleLoader.ts.html +0 -361
  25. package/coverage/hooks/index.html +0 -116
  26. package/coverage/hooks/useManager.ts.html +0 -205
  27. package/coverage/index.html +0 -401
  28. package/coverage/permissions/boldWithArrow.tsx.html +0 -208
  29. package/coverage/permissions/denied.tsx.html +0 -172
  30. package/coverage/permissions/deniedAndroid.tsx.html +0 -253
  31. package/coverage/permissions/deniedDesktop.tsx.html +0 -277
  32. package/coverage/permissions/deniedIOS.tsx.html +0 -304
  33. package/coverage/permissions/deniedInstructions.tsx.html +0 -142
  34. package/coverage/permissions/iconWrapper.tsx.html +0 -130
  35. package/coverage/permissions/index.html +0 -251
  36. package/coverage/permissions/learnMore.tsx.html +0 -340
  37. package/coverage/permissions/numberedStep.tsx.html +0 -127
  38. package/coverage/permissions/permissions.tsx.html +0 -289
  39. package/coverage/phone/index.html +0 -116
  40. package/coverage/phone/phoneInput.tsx.html +0 -832
  41. package/coverage/prettify.css +0 -1
  42. package/coverage/prettify.js +0 -2
  43. package/coverage/selfie/index.html +0 -131
  44. package/coverage/selfie/selfie.tsx.html +0 -334
  45. package/coverage/selfie/tutorial.tsx.html +0 -214
  46. package/coverage/shared/baseTutorial/baseTutorial.tsx.html +0 -250
  47. package/coverage/shared/baseTutorial/index.html +0 -131
  48. package/coverage/shared/baseTutorial/replaceBaseTutorial.ts.html +0 -289
  49. package/coverage/shared/button/button.tsx.html +0 -226
  50. package/coverage/shared/button/index.html +0 -116
  51. package/coverage/shared/componentRoot/incodeComponent.tsx.html +0 -121
  52. package/coverage/shared/componentRoot/index.html +0 -116
  53. package/coverage/shared/countries/countries.ts.html +0 -502
  54. package/coverage/shared/countries/index.html +0 -116
  55. package/coverage/shared/icons/chevronDown.tsx.html +0 -151
  56. package/coverage/shared/icons/index.html +0 -131
  57. package/coverage/shared/icons/successIcon.tsx.html +0 -163
  58. package/coverage/shared/loader/index.html +0 -116
  59. package/coverage/shared/loader/loadingIcon.tsx.html +0 -286
  60. package/coverage/shared/otpInput/index.html +0 -116
  61. package/coverage/shared/otpInput/otpInput.tsx.html +0 -808
  62. package/coverage/shared/page/index.html +0 -146
  63. package/coverage/shared/page/page.tsx.html +0 -358
  64. package/coverage/shared/page/pageUiConfig.ts.html +0 -277
  65. package/coverage/shared/page/verifiedByIncode.tsx.html +0 -310
  66. package/coverage/shared/spacer/index.html +0 -116
  67. package/coverage/shared/spacer/spacer.tsx.html +0 -349
  68. package/coverage/shared/spinner/index.html +0 -116
  69. package/coverage/shared/spinner/spinner.tsx.html +0 -280
  70. package/coverage/shared/title/index.html +0 -116
  71. package/coverage/shared/title/title.tsx.html +0 -121
  72. package/coverage/shared/uiConfig/index.html +0 -116
  73. package/coverage/shared/uiConfig/uiConfig.ts.html +0 -193
  74. package/coverage/shared/webComponent/incodeModule.ts.html +0 -172
  75. package/coverage/shared/webComponent/index.html +0 -131
  76. package/coverage/shared/webComponent/registerIncodeElement.ts.html +0 -130
  77. package/coverage/sort-arrow-sprite.png +0 -0
  78. package/coverage/sorter.js +0 -210
  79. package/coverage/styles/cn.tsx.html +0 -148
  80. package/coverage/styles/fetchTheme.ts.html +0 -349
  81. package/coverage/styles/index.html +0 -131
  82. package/dev/README.md +0 -163
  83. package/dev/getToken.ts +0 -36
  84. package/dev/headless.html +0 -875
  85. package/dev/index.html +0 -366
  86. package/dev/main-headless.tsx +0 -1332
  87. package/dev/main-orchestrated-flow.tsx +0 -1158
  88. package/dev/main-preact.tsx +0 -323
  89. package/dev/main-simplified.tsx +0 -123
  90. package/dev/main-web-component.tsx +0 -256
  91. package/dev/main.tsx +0 -332
  92. package/dev/manual.html +0 -27
  93. package/dev/orchestrated-flow.html +0 -64
  94. package/dev/simplified.html +0 -64
  95. package/dev/tiktok-logo.svg +0 -7
  96. package/src/defineCustomElement.tsx +0 -30
  97. package/src/email/email.test.tsx +0 -368
  98. package/src/email/email.tsx +0 -255
  99. package/src/email/emailInput.test.tsx +0 -264
  100. package/src/email/emailInput.tsx +0 -85
  101. package/src/email/styles.css +0 -59
  102. package/src/flow/flow.test.tsx +0 -796
  103. package/src/flow/flow.tsx +0 -292
  104. package/src/flow/flowCompleted.css +0 -30
  105. package/src/flow/flowCompleted.test.tsx +0 -331
  106. package/src/flow/flowCompleted.tsx +0 -121
  107. package/src/flow/flowInit.test.ts +0 -264
  108. package/src/flow/flowInit.ts +0 -94
  109. package/src/flow/flowStart.css +0 -58
  110. package/src/flow/flowStart.test.tsx +0 -49
  111. package/src/flow/flowStart.tsx +0 -41
  112. package/src/flow/incode-logo.svg +0 -8
  113. package/src/flow/index.ts +0 -7
  114. package/src/flow/preloadFlow.test.ts +0 -421
  115. package/src/flow/preloadFlow.ts +0 -171
  116. package/src/flow/styles.css +0 -9
  117. package/src/flow/unsupportedModule.css +0 -21
  118. package/src/flow/unsupportedModule.tsx +0 -39
  119. package/src/flow/useFlowInitialization.test.tsx +0 -292
  120. package/src/flow/useFlowInitialization.ts +0 -128
  121. package/src/flow/useModuleLoader.test.tsx +0 -212
  122. package/src/flow/useModuleLoader.ts +0 -92
  123. package/src/hooks/index.ts +0 -1
  124. package/src/hooks/useManager.test.ts +0 -91
  125. package/src/hooks/useManager.ts +0 -40
  126. package/src/i18n/index.ts +0 -3
  127. package/src/i18n/instance.ts +0 -16
  128. package/src/i18n/setup.ts +0 -184
  129. package/src/i18n/useTranslation.ts +0 -42
  130. package/src/index.ts +0 -27
  131. package/src/permissions/assets/android-dots-icon.svg +0 -7
  132. package/src/permissions/assets/android-settings-icon.svg +0 -16
  133. package/src/permissions/assets/android-toggle-icon.svg +0 -20
  134. package/src/permissions/assets/bank-card-icon.svg +0 -14
  135. package/src/permissions/assets/camera-icon.svg +0 -12
  136. package/src/permissions/assets/camera-ios.svg +0 -53
  137. package/src/permissions/assets/check-icon.svg +0 -8
  138. package/src/permissions/assets/chrome-icon.svg +0 -43
  139. package/src/permissions/assets/password-icon.svg +0 -11
  140. package/src/permissions/assets/permissions-img.svg +0 -51
  141. package/src/permissions/assets/safari-icon.svg +0 -37
  142. package/src/permissions/assets/settings-icon.svg +0 -33
  143. package/src/permissions/assets/toggle-icon.svg +0 -19
  144. package/src/permissions/assets/warning-icon.svg +0 -6
  145. package/src/permissions/boldWithArrow.css +0 -9
  146. package/src/permissions/boldWithArrow.tsx +0 -41
  147. package/src/permissions/denied.css +0 -37
  148. package/src/permissions/denied.tsx +0 -29
  149. package/src/permissions/deniedAndroid.tsx +0 -56
  150. package/src/permissions/deniedDesktop.css +0 -9
  151. package/src/permissions/deniedDesktop.tsx +0 -64
  152. package/src/permissions/deniedIOS.tsx +0 -73
  153. package/src/permissions/deniedInstructions.tsx +0 -19
  154. package/src/permissions/iconWrapper.css +0 -9
  155. package/src/permissions/iconWrapper.tsx +0 -15
  156. package/src/permissions/learnMore.css +0 -37
  157. package/src/permissions/learnMore.tsx +0 -85
  158. package/src/permissions/numberedStep.css +0 -13
  159. package/src/permissions/numberedStep.tsx +0 -14
  160. package/src/permissions/permissions.css +0 -13
  161. package/src/permissions/permissions.tsx +0 -68
  162. package/src/phone/phone.tsx +0 -246
  163. package/src/phone/phoneInput.test.tsx +0 -275
  164. package/src/phone/phoneInput.tsx +0 -249
  165. package/src/phone/styles.css +0 -158
  166. package/src/selfie/cameraButton.css +0 -13
  167. package/src/selfie/cameraButton.tsx +0 -35
  168. package/src/selfie/capture.css +0 -57
  169. package/src/selfie/capture.tsx +0 -232
  170. package/src/selfie/errorModal.tsx +0 -218
  171. package/src/selfie/errorModalContent.css +0 -33
  172. package/src/selfie/errorModalContent.tsx +0 -44
  173. package/src/selfie/faceOutline.css +0 -5
  174. package/src/selfie/faceOutline.tsx +0 -22
  175. package/src/selfie/loadingBorder.css +0 -12
  176. package/src/selfie/loadingBorder.tsx +0 -77
  177. package/src/selfie/manualCaptureButton.css +0 -13
  178. package/src/selfie/manualCaptureButton.tsx +0 -35
  179. package/src/selfie/noMoreAttemptsModal.tsx +0 -44
  180. package/src/selfie/notification.css +0 -9
  181. package/src/selfie/notification.tsx +0 -36
  182. package/src/selfie/retryErrorModal.tsx +0 -56
  183. package/src/selfie/selfie.test.tsx +0 -458
  184. package/src/selfie/selfie.tsx +0 -83
  185. package/src/selfie/selfieTutorial.json +0 -2626
  186. package/src/selfie/styles.css +0 -1
  187. package/src/selfie/tutorial.test.tsx +0 -200
  188. package/src/selfie/tutorial.tsx +0 -43
  189. package/src/setup.ts +0 -33
  190. package/src/shared/baseTutorial/baseTutorial.css +0 -21
  191. package/src/shared/baseTutorial/baseTutorial.test.tsx +0 -184
  192. package/src/shared/baseTutorial/baseTutorial.tsx +0 -55
  193. package/src/shared/baseTutorial/replaceBaseTutorial.test.ts +0 -267
  194. package/src/shared/baseTutorial/replaceBaseTutorial.ts +0 -68
  195. package/src/shared/button/button.css +0 -55
  196. package/src/shared/button/button.test.tsx +0 -101
  197. package/src/shared/button/button.tsx +0 -47
  198. package/src/shared/componentRoot/incodeComponent.tsx +0 -12
  199. package/src/shared/countries/countries.test.ts +0 -75
  200. package/src/shared/countries/countries.ts +0 -139
  201. package/src/shared/countries/index.ts +0 -6
  202. package/src/shared/icons/chevronDown.tsx +0 -22
  203. package/src/shared/icons/index.ts +0 -2
  204. package/src/shared/icons/successIcon.css +0 -5
  205. package/src/shared/icons/successIcon.test.tsx +0 -40
  206. package/src/shared/icons/successIcon.tsx +0 -26
  207. package/src/shared/loader/loadingIcon.css +0 -28
  208. package/src/shared/loader/loadingIcon.tsx +0 -67
  209. package/src/shared/lottie/lottie.tsx +0 -108
  210. package/src/shared/otpInput/otpInput.css +0 -85
  211. package/src/shared/otpInput/otpInput.test.tsx +0 -356
  212. package/src/shared/otpInput/otpInput.tsx +0 -241
  213. package/src/shared/page/incode-logo.svg +0 -3
  214. package/src/shared/page/page.css +0 -47
  215. package/src/shared/page/page.test.tsx +0 -97
  216. package/src/shared/page/page.tsx +0 -91
  217. package/src/shared/page/pageUiConfig.test.ts +0 -112
  218. package/src/shared/page/pageUiConfig.ts +0 -64
  219. package/src/shared/page/verifiedByIncode.css +0 -5
  220. package/src/shared/page/verifiedByIncode.tsx +0 -75
  221. package/src/shared/spacer/spacer.css +0 -149
  222. package/src/shared/spacer/spacer.test.tsx +0 -143
  223. package/src/shared/spacer/spacer.tsx +0 -88
  224. package/src/shared/spinner/index.ts +0 -2
  225. package/src/shared/spinner/spinner.css +0 -28
  226. package/src/shared/spinner/spinner.test.tsx +0 -82
  227. package/src/shared/spinner/spinner.tsx +0 -65
  228. package/src/shared/title/title.css +0 -7
  229. package/src/shared/title/title.tsx +0 -12
  230. package/src/shared/uiConfig/uiConfig.ts +0 -36
  231. package/src/shared/webComponent/incodeModule.ts +0 -29
  232. package/src/shared/webComponent/registerIncodeElement.ts +0 -15
  233. package/src/styles/__mocks__/fetchTheme.ts +0 -19
  234. package/src/styles/applyTheme.ts +0 -37
  235. package/src/styles/cn.test.tsx +0 -57
  236. package/src/styles/cn.tsx +0 -21
  237. package/src/styles/core.css +0 -12
  238. package/src/styles/fetchTheme.test.ts +0 -390
  239. package/src/styles/fetchTheme.ts +0 -88
  240. package/src/styles/generatePalette.ts +0 -111
  241. package/src/styles/reset.css +0 -65
  242. package/src/styles/resolveCssVariableToHex.ts +0 -28
  243. package/src/styles/tailwind.css +0 -291
  244. package/src/styles/themeTypes.ts +0 -18
  245. package/src/styles/tokens/colors.css +0 -190
  246. package/src/styles/tokens/components.css +0 -174
  247. package/src/styles/tokens/index.css +0 -4
  248. package/src/styles/tokens/primitives.css +0 -129
  249. package/src/styles/tokens/semantic.css +0 -51
  250. package/src/svg.d.ts +0 -4
  251. package/src/types/assets.d.ts +0 -1
  252. package/src/types/custom-elements.d.ts +0 -104
  253. package/tsconfig.json +0 -22
  254. package/vite.config.ts +0 -260
  255. package/vitest.config.ts +0 -40
  256. package/vitest.setup.ts +0 -16
@@ -1,275 +0,0 @@
1
- import { fireEvent, render, screen } from '@testing-library/preact';
2
- import { beforeEach, describe, expect, it, vi } from 'vitest';
3
- import { PhoneInput } from './phoneInput';
4
-
5
- describe('PhoneInput', () => {
6
- const defaultProps = {
7
- countryCode: 'US',
8
- phonePrefix: '+1',
9
- onPhoneChange: vi.fn(),
10
- };
11
-
12
- beforeEach(() => {
13
- vi.clearAllMocks();
14
- });
15
-
16
- describe('Rendering', () => {
17
- it('renders phone input field', () => {
18
- render(<PhoneInput {...defaultProps} />);
19
-
20
- const input = screen.getByTestId('phone-input');
21
- expect(input).toBeTruthy();
22
- });
23
-
24
- it('renders country selector button', () => {
25
- render(<PhoneInput {...defaultProps} />);
26
-
27
- const selector = screen.getByTestId('phone-country-selector');
28
- expect(selector).toBeTruthy();
29
- });
30
-
31
- it('renders with initial country code', () => {
32
- render(<PhoneInput {...defaultProps} countryCode="MX" />);
33
-
34
- const selector = screen.getByTestId('phone-country-selector');
35
- expect(selector.textContent).toContain('+52');
36
- });
37
-
38
- it('renders US flag emoji for US country', () => {
39
- render(<PhoneInput {...defaultProps} countryCode="US" />);
40
-
41
- const selector = screen.getByTestId('phone-country-selector');
42
- expect(selector.textContent).toContain('🇺🇸');
43
- });
44
- });
45
-
46
- describe('Phone input', () => {
47
- it('calls onPhoneChange when input changes', () => {
48
- const onPhoneChange = vi.fn();
49
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
50
-
51
- const input = screen.getByTestId('phone-input');
52
- fireEvent.input(input, { target: { value: '4155551234' } });
53
-
54
- expect(onPhoneChange).toHaveBeenCalled();
55
- });
56
-
57
- it('formats phone number as user types', () => {
58
- render(<PhoneInput {...defaultProps} />);
59
-
60
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
61
- fireEvent.input(input, { target: { value: '4155551234' } });
62
-
63
- // Should be formatted with AsYouType
64
- expect(input.value).toMatch(/[\d\s()-]+/);
65
- });
66
-
67
- it('validates phone number and reports validity', () => {
68
- const onPhoneChange = vi.fn();
69
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
70
-
71
- const input = screen.getByTestId('phone-input');
72
-
73
- // Invalid phone (too short)
74
- fireEvent.input(input, { target: { value: '123' } });
75
- expect(onPhoneChange).toHaveBeenLastCalledWith(expect.any(String), false);
76
-
77
- // Valid US phone
78
- fireEvent.input(input, { target: { value: '4155551234' } });
79
- expect(onPhoneChange).toHaveBeenLastCalledWith(expect.any(String), true);
80
- });
81
- });
82
-
83
- describe('Country selector', () => {
84
- it('opens dropdown on click', () => {
85
- render(<PhoneInput {...defaultProps} />);
86
-
87
- const selector = screen.getByTestId('phone-country-selector');
88
- fireEvent.click(selector);
89
-
90
- // Dropdown should be visible
91
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
92
- expect(dropdown).toBeTruthy();
93
- });
94
-
95
- it('closes dropdown when clicking outside', () => {
96
- render(<PhoneInput {...defaultProps} />);
97
-
98
- const selector = screen.getByTestId('phone-country-selector');
99
- fireEvent.click(selector);
100
-
101
- // Click outside
102
- fireEvent.mouseDown(document.body);
103
-
104
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
105
- expect(dropdown).toBeFalsy();
106
- });
107
-
108
- it('shows list of countries in dropdown', () => {
109
- render(<PhoneInput {...defaultProps} />);
110
-
111
- const selector = screen.getByTestId('phone-country-selector');
112
- fireEvent.click(selector);
113
-
114
- const options = document.querySelectorAll('.IncodePhoneCountryOption');
115
- expect(options.length).toBeGreaterThan(0);
116
- });
117
-
118
- it('changes country when option is selected', () => {
119
- const onPhoneChange = vi.fn();
120
- render(<PhoneInput {...defaultProps} onPhoneChange={onPhoneChange} />);
121
-
122
- const selector = screen.getByTestId('phone-country-selector');
123
- fireEvent.click(selector);
124
-
125
- // Find and click Mexico option
126
- const options = document.querySelectorAll('.IncodePhoneCountryOption');
127
- const mxOption = Array.from(options).find((opt) =>
128
- opt.textContent?.includes('Mexico'),
129
- );
130
-
131
- if (mxOption) {
132
- fireEvent.click(mxOption);
133
- }
134
-
135
- // Dropdown should close and country should change
136
- expect(selector.textContent).toContain('+52');
137
- });
138
-
139
- it('does not open dropdown when disabled', () => {
140
- render(<PhoneInput {...defaultProps} disabled />);
141
-
142
- const selector = screen.getByTestId('phone-country-selector');
143
- fireEvent.click(selector);
144
-
145
- const dropdown = document.querySelector('.IncodePhoneCountryDropdown');
146
- expect(dropdown).toBeFalsy();
147
- });
148
- });
149
-
150
- describe('Prefilled phone', () => {
151
- it('populates input with prefilled phone', () => {
152
- render(<PhoneInput {...defaultProps} prefilledPhone="+14155551234" />);
153
-
154
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
155
- expect(input.value).toBeTruthy();
156
- });
157
-
158
- it('calls onPhoneChange with prefilled phone', () => {
159
- const onPhoneChange = vi.fn();
160
- render(
161
- <PhoneInput
162
- {...defaultProps}
163
- prefilledPhone="+14155551234"
164
- onPhoneChange={onPhoneChange}
165
- />,
166
- );
167
-
168
- // Should have called onPhoneChange with the prefilled value
169
- expect(onPhoneChange).toHaveBeenCalledWith('+14155551234', true);
170
- });
171
- });
172
-
173
- describe('Error state', () => {
174
- it('displays error message when phoneError is set', () => {
175
- render(
176
- <PhoneInput {...defaultProps} phoneError="Invalid phone number" />,
177
- );
178
-
179
- expect(screen.getByTestId('phone-error')).toBeTruthy();
180
- expect(screen.getByText('Invalid phone number')).toBeTruthy();
181
- });
182
-
183
- it('shows error styling on input wrapper', () => {
184
- const { container } = render(
185
- <PhoneInput {...defaultProps} phoneError="Error" />,
186
- );
187
-
188
- const wrapper = container.querySelector('.IncodePhoneInputWrapperError');
189
- expect(wrapper).toBeTruthy();
190
- });
191
- });
192
-
193
- describe('Opt-in checkbox', () => {
194
- it('does not show opt-in when optinEnabled is false', () => {
195
- render(<PhoneInput {...defaultProps} optinEnabled={false} />);
196
-
197
- expect(screen.queryByTestId('phone-optin')).toBeNull();
198
- });
199
-
200
- it('shows opt-in checkbox when optinEnabled is true', () => {
201
- render(<PhoneInput {...defaultProps} optinEnabled />);
202
-
203
- expect(screen.getByTestId('phone-optin')).toBeTruthy();
204
- });
205
-
206
- it('calls onOptInChange when checkbox is toggled', () => {
207
- const onOptInChange = vi.fn();
208
- render(
209
- <PhoneInput
210
- {...defaultProps}
211
- optinEnabled
212
- onOptInChange={onOptInChange}
213
- />,
214
- );
215
-
216
- const checkbox = screen.getByTestId('phone-optin');
217
- fireEvent.change(checkbox, { target: { checked: true } });
218
-
219
- expect(onOptInChange).toHaveBeenCalledWith(true);
220
- });
221
- });
222
-
223
- describe('Disabled state', () => {
224
- it('disables input when disabled prop is true', () => {
225
- render(<PhoneInput {...defaultProps} disabled />);
226
-
227
- const input = screen.getByTestId('phone-input') as HTMLInputElement;
228
- expect(input.disabled).toBe(true);
229
- });
230
-
231
- it('disables country selector when disabled', () => {
232
- render(<PhoneInput {...defaultProps} disabled />);
233
-
234
- const button = screen.getByTestId('phone-country-selector');
235
- expect(button.hasAttribute('disabled')).toBe(true);
236
- });
237
-
238
- it('shows disabled styling', () => {
239
- const { container } = render(<PhoneInput {...defaultProps} disabled />);
240
-
241
- const wrapper = container.querySelector(
242
- '.IncodePhoneInputWrapperDisabled',
243
- );
244
- expect(wrapper).toBeTruthy();
245
- });
246
- });
247
-
248
- describe('Accessibility', () => {
249
- it('country selector button is accessible', () => {
250
- render(<PhoneInput {...defaultProps} />);
251
-
252
- // Button should be accessible - it contains flag and country code
253
- const button = screen.getByTestId('phone-country-selector');
254
- expect(button.tagName.toLowerCase()).toBe('button');
255
- expect(button.textContent).toContain('+1'); // Country code is visible
256
- });
257
-
258
- it('has aria-expanded attribute on country selector', () => {
259
- render(<PhoneInput {...defaultProps} />);
260
-
261
- const selector = screen.getByTestId('phone-country-selector');
262
- expect(selector.getAttribute('aria-expanded')).toBe('false');
263
-
264
- fireEvent.click(selector);
265
- expect(selector.getAttribute('aria-expanded')).toBe('true');
266
- });
267
-
268
- it('has aria-haspopup attribute on country selector', () => {
269
- render(<PhoneInput {...defaultProps} />);
270
-
271
- const selector = screen.getByTestId('phone-country-selector');
272
- expect(selector.getAttribute('aria-haspopup')).toBe('listbox');
273
- });
274
- });
275
- });
@@ -1,249 +0,0 @@
1
- import {
2
- AsYouType,
3
- type CountryCode,
4
- parsePhoneNumberFromString,
5
- } from 'libphonenumber-js';
6
- import type { FC } from 'preact/compat';
7
- import { useEffect, useRef, useState } from 'preact/hooks';
8
- import {
9
- type CountryData,
10
- countries,
11
- getCountryByCode,
12
- } from '../shared/countries';
13
- import { ChevronDown } from '../shared/icons';
14
- import { Spacer } from '../shared/spacer/spacer';
15
- import './styles.css';
16
-
17
- export type PhoneInputProps = {
18
- countryCode: string;
19
- phonePrefix: string;
20
- prefilledPhone?: string;
21
- phoneError?: string;
22
- optinEnabled?: boolean;
23
- disabled?: boolean;
24
- onPhoneChange: (phone: string, isValid: boolean) => void;
25
- onOptInChange?: (granted: boolean) => void;
26
- };
27
-
28
- export const PhoneInput: FC<PhoneInputProps> = ({
29
- countryCode,
30
- prefilledPhone,
31
- phoneError,
32
- optinEnabled = false,
33
- disabled = false,
34
- onPhoneChange,
35
- onOptInChange,
36
- }) => {
37
- const [selectedCountry, setSelectedCountry] = useState<CountryData | null>(
38
- null,
39
- );
40
- const [isDropdownOpen, setIsDropdownOpen] = useState(false);
41
- const [phone, setPhone] = useState('');
42
- const [optInGranted, setOptInGranted] = useState(false);
43
- const dropdownRef = useRef<HTMLDivElement>(null);
44
-
45
- // Initialize selected country from countryCode prop
46
- useEffect(() => {
47
- const country = getCountryByCode(countryCode);
48
- if (country) {
49
- setSelectedCountry(country);
50
- } else {
51
- // Default to US if not found
52
- setSelectedCountry(
53
- countries.find((c) => c.code === 'US') || countries[0],
54
- );
55
- }
56
- }, [countryCode]);
57
-
58
- // Handle prefilled phone
59
- useEffect(() => {
60
- if (prefilledPhone && selectedCountry) {
61
- // Parse the prefilled phone to extract national number
62
- const phoneToFormat = prefilledPhone.startsWith('+')
63
- ? prefilledPhone
64
- : `+${prefilledPhone}`;
65
- const parsed = parsePhoneNumberFromString(phoneToFormat);
66
-
67
- if (parsed?.isValid()) {
68
- // Update country if different
69
- if (parsed.country && parsed.country !== selectedCountry.code) {
70
- const newCountry = getCountryByCode(parsed.country);
71
- if (newCountry) {
72
- setSelectedCountry(newCountry);
73
- }
74
- }
75
- // Format and set the national number
76
- const formatter = new AsYouType(parsed.country);
77
- const formatted = formatter.input(parsed.nationalNumber);
78
- setPhone(formatted);
79
- onPhoneChange(phoneToFormat, true);
80
- }
81
- }
82
- }, [prefilledPhone, selectedCountry]);
83
-
84
- // Close dropdown when clicking outside
85
- useEffect(() => {
86
- const handleClickOutside = (e: MouseEvent) => {
87
- if (
88
- dropdownRef.current &&
89
- !dropdownRef.current.contains(e.target as Node)
90
- ) {
91
- setIsDropdownOpen(false);
92
- }
93
- };
94
-
95
- if (isDropdownOpen) {
96
- document.addEventListener('mousedown', handleClickOutside);
97
- }
98
- return () => document.removeEventListener('mousedown', handleClickOutside);
99
- }, [isDropdownOpen]);
100
-
101
- const validateAndNotify = (nationalNumber: string, country: CountryData) => {
102
- const fullNumber = `${country.callingCode}${nationalNumber.replace(/\D/g, '')}`;
103
- try {
104
- const parsed = parsePhoneNumberFromString(
105
- fullNumber,
106
- country.code as CountryCode,
107
- );
108
- const valid = parsed?.isValid() ?? false;
109
- onPhoneChange(fullNumber, valid);
110
- } catch {
111
- onPhoneChange(fullNumber, false);
112
- }
113
- };
114
-
115
- const handlePhoneChange = (e: Event) => {
116
- const target = e.target as HTMLInputElement;
117
- const rawValue = target.value;
118
-
119
- if (selectedCountry) {
120
- // Format as user types
121
- const formatter = new AsYouType(selectedCountry.code);
122
- const formatted = formatter.input(rawValue);
123
- setPhone(formatted);
124
- validateAndNotify(rawValue, selectedCountry);
125
- } else {
126
- setPhone(rawValue);
127
- }
128
- };
129
-
130
- const handleCountrySelect = (country: CountryData) => {
131
- setSelectedCountry(country);
132
- setIsDropdownOpen(false);
133
-
134
- // Revalidate with new country
135
- if (phone) {
136
- validateAndNotify(phone, country);
137
- }
138
- };
139
-
140
- const handleOptInChange = (e: Event) => {
141
- const target = e.target as HTMLInputElement;
142
- const granted = target.checked;
143
- setOptInGranted(granted);
144
- onOptInChange?.(granted);
145
- };
146
-
147
- const toggleDropdown = () => {
148
- if (!disabled) {
149
- setIsDropdownOpen(!isDropdownOpen);
150
- }
151
- };
152
-
153
- return (
154
- <div class="IncodePhoneInput">
155
- <div class="IncodePhoneInputContainer">
156
- <div class="IncodePhoneInputRow">
157
- {/* Country Selector */}
158
- <div
159
- class={`IncodePhoneCountrySelector ${isDropdownOpen ? 'IncodePhoneCountrySelectorOpen' : ''} ${disabled ? 'IncodePhoneCountrySelectorDisabled' : ''}`}
160
- ref={dropdownRef}
161
- >
162
- <button
163
- type="button"
164
- class="IncodePhoneCountryButton"
165
- onClick={toggleDropdown}
166
- disabled={disabled}
167
- aria-expanded={isDropdownOpen}
168
- aria-haspopup="listbox"
169
- data-testid="phone-country-selector"
170
- >
171
- <span class="IncodePhoneCountryFlag" aria-hidden="true">
172
- {selectedCountry?.emoji}
173
- </span>
174
- <span class="IncodePhoneCountryCode">
175
- {selectedCountry?.callingCode}
176
- </span>
177
- <ChevronDown
178
- class={`IncodePhoneCountryArrow ${isDropdownOpen ? 'IncodePhoneCountryArrowUp' : ''}`}
179
- />
180
- </button>
181
-
182
- {/* Country Dropdown */}
183
- {isDropdownOpen && (
184
- <div class="IncodePhoneCountryDropdown">
185
- {countries.map((country) => (
186
- <button
187
- key={country.code}
188
- type="button"
189
- class={`IncodePhoneCountryOption ${selectedCountry?.code === country.code ? 'IncodePhoneCountryOptionSelected' : ''}`}
190
- onClick={() => handleCountrySelect(country)}
191
- >
192
- <span class="IncodePhoneCountryFlag" aria-hidden="true">
193
- {country.emoji}
194
- </span>
195
- <span class="IncodePhoneCountryOptionCode">
196
- {country.callingCode}
197
- </span>
198
- <span class="IncodePhoneCountryOptionName">
199
- {country.name}
200
- </span>
201
- </button>
202
- ))}
203
- </div>
204
- )}
205
- </div>
206
-
207
- {/* Phone Input */}
208
- <div
209
- class={`IncodePhoneInputWrapper ${phoneError ? 'IncodePhoneInputWrapperError' : ''} ${disabled ? 'IncodePhoneInputWrapperDisabled' : ''}`}
210
- >
211
- <input
212
- type="tel"
213
- value={phone}
214
- onInput={handlePhoneChange}
215
- placeholder="(123) 456-7890"
216
- disabled={disabled}
217
- class="IncodePhoneInputField"
218
- data-testid="phone-input"
219
- />
220
- </div>
221
- </div>
222
-
223
- {phoneError && (
224
- <>
225
- <Spacer size={8} />
226
- <p class="IncodePhoneInputError" data-testid="phone-error">
227
- {phoneError}
228
- </p>
229
- </>
230
- )}
231
-
232
- {optinEnabled && (
233
- <>
234
- <Spacer size={16} />
235
- <label class="IncodePhoneInputOptIn">
236
- <input
237
- type="checkbox"
238
- checked={optInGranted}
239
- onChange={handleOptInChange}
240
- data-testid="phone-optin"
241
- />
242
- <span>I agree to receive SMS notifications</span>
243
- </label>
244
- </>
245
- )}
246
- </div>
247
- </div>
248
- );
249
- };
@@ -1,158 +0,0 @@
1
- @reference "../styles/tailwind.css";
2
-
3
- /* Phone Page Layout */
4
- .IncodePhonePage .IncodePageContent {
5
- @apply pt-32 pb-16;
6
- }
7
-
8
- .IncodePhoneInput {
9
- @apply flex flex-col items-center w-full;
10
- }
11
-
12
- .IncodePhoneInputContainer {
13
- @apply flex flex-col items-center w-full;
14
- }
15
-
16
- /* Row container for country selector and input */
17
- .IncodePhoneInputRow {
18
- @apply relative flex items-stretch gap-8 w-full max-w-[340px];
19
- }
20
-
21
- /* Phone Input Wrapper (separate box) */
22
- .IncodePhoneInputWrapper {
23
- @apply flex-1 flex items-center border border-input-border-default rounded-small bg-input-surface-default transition-colors duration-200;
24
- }
25
-
26
- .IncodePhoneInputWrapper:focus-within {
27
- @apply border-input-border-active;
28
- }
29
-
30
- .IncodePhoneInputWrapper.IncodePhoneInputWrapperError {
31
- @apply border-input-border-negative;
32
- }
33
-
34
- .IncodePhoneInputWrapper.IncodePhoneInputWrapperDisabled {
35
- @apply border-input-border-disabled bg-input-surface-disabled;
36
- }
37
-
38
- /* Country Selector (separate box) */
39
- .IncodePhoneCountrySelector {
40
- @apply static shrink-0 w-[124px];
41
- @apply border border-input-border-default rounded-small bg-input-surface-default;
42
- @apply transition-colors duration-200;
43
- }
44
-
45
- .IncodePhoneCountrySelector.IncodePhoneCountrySelectorOpen {
46
- @apply border-input-border-active;
47
- }
48
-
49
- .IncodePhoneCountrySelector.IncodePhoneCountrySelectorDisabled {
50
- @apply border-input-border-disabled bg-input-surface-disabled;
51
- }
52
-
53
- .IncodePhoneCountryButton {
54
- @apply flex items-center justify-center w-full h-full;
55
- @apply p-8 pl-16;
56
- @apply bg-transparent border-none cursor-pointer;
57
- @apply text-18 font-medium text-text-body-primary;
58
- @apply rounded-small;
59
- font-family: inherit;
60
- }
61
-
62
- .IncodePhoneCountryButton:disabled {
63
- @apply cursor-not-allowed text-input-text-field-disabled;
64
- }
65
-
66
- .IncodePhoneCountryButton:focus {
67
- @apply outline-none;
68
- }
69
-
70
- .IncodePhoneCountryFlag {
71
- @apply text-24 leading-none;
72
- }
73
-
74
- .IncodePhoneCountryCode {
75
- @apply text-18 font-medium whitespace-nowrap text-text-body-primary ml-4;
76
- }
77
-
78
- .IncodePhoneCountryArrow {
79
- @apply w-24 h-24 text-icon-neutral-500 transition-transform duration-200 ml-8;
80
- }
81
-
82
- .IncodePhoneCountryArrowUp {
83
- @apply rotate-180;
84
- }
85
-
86
- .IncodePhoneCountryButton:disabled .IncodePhoneCountryArrow {
87
- @apply text-icon-neutral-300;
88
- }
89
-
90
- /* Country Dropdown */
91
- .IncodePhoneCountryDropdown {
92
- @apply absolute top-full left-0 right-0 z-50 mt-8;
93
- @apply max-h-[320px] overflow-y-auto;
94
- @apply bg-surface-neutral-0 border border-input-border-default rounded-small;
95
- @apply p-8;
96
- box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);
97
- }
98
-
99
- .IncodePhoneCountryOption {
100
- @apply w-full flex items-center gap-12 py-12 px-12 cursor-pointer rounded-small;
101
- @apply text-16 font-medium text-text-body-primary;
102
- @apply bg-transparent border-none text-left;
103
- font-family: inherit;
104
- }
105
-
106
- .IncodePhoneCountryOption:hover {
107
- @apply bg-surface-neutral-100;
108
- }
109
-
110
- .IncodePhoneCountryOption:focus {
111
- @apply outline-none bg-surface-neutral-100;
112
- }
113
-
114
- .IncodePhoneCountryOptionSelected {
115
- @apply bg-surface-brand-50;
116
- }
117
-
118
- .IncodePhoneCountryOptionSelected:hover {
119
- @apply bg-surface-brand-50;
120
- }
121
-
122
- .IncodePhoneCountryOptionCode {
123
- @apply text-16 text-text-body-secondary min-w-[52px];
124
- }
125
-
126
- .IncodePhoneCountryOptionName {
127
- @apply text-16 text-text-body-primary flex-1 truncate;
128
- }
129
-
130
- /* Phone Input Field */
131
- .IncodePhoneInputField {
132
- @apply w-full py-16 px-16 text-18 font-medium border-none outline-none bg-transparent text-input-text-field-default;
133
- @apply rounded-small;
134
- }
135
-
136
- .IncodePhoneInputField::placeholder {
137
- @apply text-input-text-field-placeholder font-medium;
138
- }
139
-
140
- .IncodePhoneInputField:disabled {
141
- @apply cursor-not-allowed text-input-text-field-disabled;
142
- }
143
-
144
- .IncodePhoneInputError {
145
- @apply text-input-text-helper-negative text-14 text-center m-0 font-medium;
146
- }
147
-
148
- .IncodePhoneInputOptIn {
149
- @apply flex items-center gap-8 text-14 text-checkbox-text-default cursor-pointer;
150
- }
151
-
152
- .IncodePhoneInputOptIn input[type="checkbox"] {
153
- @apply w-[18px] h-[18px] cursor-pointer accent-checkbox-surface-selected rounded-checkbox;
154
- }
155
-
156
- .IncodePhoneInputOptIn span {
157
- @apply select-none font-medium;
158
- }
@@ -1,13 +0,0 @@
1
- @reference "../styles/tailwind.css";
2
-
3
- .IncodeCameraButton {
4
- @apply flex items-center justify-center;
5
-
6
- .IncodeCameraButtonPath {
7
- @apply fill-border-brand-500;
8
- }
9
-
10
- .IncodeCameraButtonBackground {
11
- @apply fill-surface-brand-50;
12
- }
13
- }