@jmruthers/pace-core 0.5.193 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/README.md +7 -1
  3. package/cursor-rules/00-pace-core-compliance.mdc +372 -0
  4. package/cursor-rules/01-standards-compliance.mdc +275 -0
  5. package/cursor-rules/02-project-structure.mdc +200 -0
  6. package/cursor-rules/03-solid-principles.mdc +341 -0
  7. package/cursor-rules/04-testing-standards.mdc +315 -0
  8. package/cursor-rules/05-bug-reports-and-features.mdc +246 -0
  9. package/cursor-rules/06-code-quality.mdc +392 -0
  10. package/cursor-rules/07-tech-stack-compliance.mdc +309 -0
  11. package/cursor-rules/CHANGELOG.md +101 -0
  12. package/cursor-rules/README.md +191 -0
  13. package/dist/{DataTable-Be6dH_dR.d.ts → DataTable-CH1U5Tpy.d.ts} +1 -1
  14. package/dist/{DataTable-5FU7IESH.js → DataTable-DQ7RSOHE.js} +6 -6
  15. package/dist/{PublicPageProvider-C0Sm_e5k.d.ts → PublicPageProvider-ce4xlHYA.d.ts} +34 -155
  16. package/dist/{UnifiedAuthProvider-RGJTDE2C.js → UnifiedAuthProvider-ATAP5UTR.js} +2 -2
  17. package/dist/{chunk-6C4YBBJM 5.js → chunk-3QRJFVBR.js} +1 -1
  18. package/dist/chunk-3QRJFVBR.js.map +1 -0
  19. package/dist/{chunk-IIELH4DL.js → chunk-3XTALGJF.js} +2 -2
  20. package/dist/{chunk-IIELH4DL.js.map → chunk-3XTALGJF.js.map} +1 -1
  21. package/dist/{chunk-HWIIPPNI.js → chunk-4N5C5XZU.js} +20 -20
  22. package/dist/chunk-4N5C5XZU.js.map +1 -0
  23. package/dist/{chunk-7EQTDTTJ.js → chunk-4ZC4GX36.js} +5 -5
  24. package/dist/{chunk-7EQTDTTJ.js 2.map → chunk-4ZC4GX36.js.map} +1 -1
  25. package/dist/{chunk-7FLMSG37.js → chunk-BYFSK72L.js} +22 -22
  26. package/dist/chunk-BYFSK72L.js.map +1 -0
  27. package/dist/{chunk-LFNCN2SP.js → chunk-EXUD6RNJ.js} +46 -7
  28. package/dist/chunk-EXUD6RNJ.js.map +1 -0
  29. package/dist/{chunk-NOAYCWCX 5.js → chunk-GLK6VM3F.js} +167 -169
  30. package/dist/chunk-GLK6VM3F.js.map +1 -0
  31. package/dist/{chunk-HW3OVDUF.js → chunk-J36DSWQK.js} +1 -1
  32. package/dist/{chunk-HW3OVDUF.js.map → chunk-J36DSWQK.js.map} +1 -1
  33. package/dist/{chunk-BC4IJKSL.js → chunk-JBKQ3SAO.js} +2 -2
  34. package/dist/{chunk-QWWZ5CAQ.js → chunk-LXQLPRQ2.js} +2 -2
  35. package/dist/{chunk-E3SPN4VZ 5.js → chunk-T33XF5ZC.js} +119 -114
  36. package/dist/chunk-T33XF5ZC.js.map +1 -0
  37. package/dist/{chunk-XNXXZ43G.js → chunk-XM25TVIE.js} +27 -4
  38. package/dist/chunk-XM25TVIE.js.map +1 -0
  39. package/dist/components.d.ts +3 -3
  40. package/dist/components.js +8 -8
  41. package/dist/hooks.d.ts +6 -6
  42. package/dist/hooks.js +17 -22
  43. package/dist/hooks.js.map +1 -1
  44. package/dist/index.d.ts +7 -7
  45. package/dist/index.js +15 -16
  46. package/dist/index.js.map +1 -1
  47. package/dist/providers.js +1 -1
  48. package/dist/rbac/index.d.ts +1 -1
  49. package/dist/rbac/index.js +5 -5
  50. package/dist/{usePublicRouteParams-TZe0gy-4.d.ts → usePublicRouteParams-BJAlWfuJ.d.ts} +3 -3
  51. package/dist/{useToast-C8gR5ir4.d.ts → useToast-AyaT-x7p.d.ts} +2 -2
  52. package/dist/utils.d.ts +1 -1
  53. package/dist/utils.js +3 -3
  54. package/docs/getting-started/cursor-rules.md +262 -0
  55. package/docs/getting-started/installation-guide.md +6 -1
  56. package/docs/getting-started/quick-start.md +6 -1
  57. package/docs/migration/MIGRATION_GUIDE.md +4 -4
  58. package/docs/migration/REACT_19_MIGRATION.md +227 -0
  59. package/docs/standards/README.md +39 -0
  60. package/docs/troubleshooting/migration.md +4 -4
  61. package/examples/PublicPages/PublicEventPage.tsx +1 -1
  62. package/package.json +11 -6
  63. package/scripts/audit-consuming-app.cjs +961 -0
  64. package/scripts/check-pace-core-compliance.cjs +34 -15
  65. package/scripts/install-cursor-rules.cjs +236 -0
  66. package/src/__tests__/helpers/test-providers.tsx +1 -1
  67. package/src/__tests__/helpers/test-utils.tsx +1 -1
  68. package/src/components/Badge/Badge.tsx +2 -4
  69. package/src/components/Button/Button.tsx +5 -4
  70. package/src/components/Calendar/Calendar.tsx +1 -1
  71. package/src/components/DataTable/DataTable.test.tsx +57 -93
  72. package/src/components/DataTable/DataTable.tsx +2 -2
  73. package/src/components/DataTable/__tests__/pagination.modes.test.tsx +13 -5
  74. package/src/components/DataTable/__tests__/ssr.strict-mode.test.tsx +12 -12
  75. package/src/components/DataTable/components/AccessDeniedPage.tsx +1 -1
  76. package/src/components/DataTable/components/BulkOperationsDropdown.tsx +1 -1
  77. package/src/components/DataTable/components/DataTableCore.tsx +4 -7
  78. package/src/components/DataTable/components/DataTableModals.tsx +1 -1
  79. package/src/components/DataTable/components/EditableRow.tsx +1 -1
  80. package/src/components/DataTable/components/UnifiedTableBody.tsx +6 -8
  81. package/src/components/DataTable/components/__tests__/DataTableModals.test.tsx +23 -23
  82. package/src/components/DataTable/components/__tests__/EditableRow.test.tsx +11 -11
  83. package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +36 -36
  84. package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +27 -27
  85. package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +39 -39
  86. package/src/components/DataTable/components/__tests__/UnifiedTableBody.test.tsx +33 -33
  87. package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +29 -29
  88. package/src/components/DataTable/hooks/useColumnReordering.ts +2 -2
  89. package/src/components/DataTable/hooks/useKeyboardNavigation.ts +2 -2
  90. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +8 -14
  91. package/src/components/Dialog/Dialog.tsx +6 -5
  92. package/src/components/ErrorBoundary/ErrorBoundary.tsx +1 -1
  93. package/src/components/EventSelector/EventSelector.tsx +1 -1
  94. package/src/components/FileDisplay/FileDisplay.test.tsx +2 -2
  95. package/src/components/Footer/Footer.tsx +1 -1
  96. package/src/components/Form/Form.test.tsx +36 -15
  97. package/src/components/Form/Form.tsx +30 -26
  98. package/src/components/Header/Header.tsx +1 -1
  99. package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +40 -40
  100. package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +1 -1
  101. package/src/components/Input/Input.tsx +28 -30
  102. package/src/components/Label/Label.tsx +1 -1
  103. package/src/components/LoadingSpinner/LoadingSpinner.tsx +1 -1
  104. package/src/components/LoginForm/LoginForm.test.tsx +42 -42
  105. package/src/components/LoginForm/LoginForm.tsx +8 -8
  106. package/src/components/NavigationMenu/NavigationMenu.tsx +1 -1
  107. package/src/components/PaceAppLayout/PaceAppLayout.performance.test.tsx +1 -1
  108. package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +50 -50
  109. package/src/components/PaceAppLayout/PaceAppLayout.tsx +1 -1
  110. package/src/components/PaceAppLayout/README.md +1 -1
  111. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  112. package/src/components/PasswordChange/PasswordChangeForm.test.tsx +33 -33
  113. package/src/components/PasswordChange/PasswordChangeForm.tsx +1 -1
  114. package/src/components/Progress/Progress.tsx +1 -1
  115. package/src/components/PublicLayout/PublicPageLayout.tsx +1 -1
  116. package/src/components/Select/Select.tsx +33 -22
  117. package/src/components/SessionRestorationLoader/SessionRestorationLoader.tsx +1 -1
  118. package/src/components/Table/Table.tsx +1 -1
  119. package/src/components/Textarea/Textarea.tsx +27 -29
  120. package/src/components/Toast/Toast.tsx +1 -1
  121. package/src/components/Tooltip/Tooltip.tsx +1 -1
  122. package/src/components/UserMenu/UserMenu.tsx +1 -1
  123. package/src/hooks/__tests__/hooks.integration.test.tsx +80 -55
  124. package/src/hooks/__tests__/useStorage.unit.test.ts +36 -36
  125. package/src/hooks/public/usePublicEvent.ts +1 -1
  126. package/src/hooks/public/usePublicEventLogo.ts +1 -1
  127. package/src/hooks/public/usePublicRouteParams.ts +1 -1
  128. package/src/hooks/useDataTableState.ts +8 -18
  129. package/src/hooks/useFocusManagement.ts +2 -2
  130. package/src/hooks/useFocusTrap.ts +4 -4
  131. package/src/hooks/useFormDialog.ts +8 -7
  132. package/src/hooks/useInactivityTracker.ts +1 -1
  133. package/src/hooks/usePermissionCache.ts +1 -1
  134. package/src/hooks/useSecureDataAccess.ts +19 -4
  135. package/src/hooks/useToast.ts +2 -2
  136. package/src/providers/__tests__/OrganisationProvider.test.tsx +57 -13
  137. package/src/providers/__tests__/ProviderLifecycle.test.tsx +21 -6
  138. package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +10 -10
  139. package/src/providers/services/UnifiedAuthProvider.tsx +22 -22
  140. package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +13 -3
  141. package/src/rbac/__tests__/adapters.comprehensive.test.tsx +24 -24
  142. package/src/rbac/components/EnhancedNavigationMenu.tsx +1 -1
  143. package/src/rbac/components/NavigationGuard.tsx +1 -1
  144. package/src/rbac/components/NavigationProvider.tsx +1 -1
  145. package/src/rbac/components/PagePermissionGuard.tsx +1 -1
  146. package/src/rbac/components/PagePermissionProvider.tsx +1 -1
  147. package/src/rbac/components/PermissionEnforcer.tsx +1 -1
  148. package/src/rbac/components/RoleBasedRouter.tsx +1 -1
  149. package/src/rbac/components/SecureDataProvider.tsx +1 -1
  150. package/src/rbac/secureClient.ts +12 -0
  151. package/src/utils/security/secureDataAccess.test.ts +31 -20
  152. package/src/utils/security/secureDataAccess.ts +4 -3
  153. package/dist/chunk-6C4YBBJM.js +0 -628
  154. package/dist/chunk-6C4YBBJM.js.map +0 -1
  155. package/dist/chunk-7D4SUZUM.js 2.map +0 -1
  156. package/dist/chunk-7EQTDTTJ.js.map +0 -1
  157. package/dist/chunk-7FLMSG37.js 2.map +0 -1
  158. package/dist/chunk-7FLMSG37.js.map +0 -1
  159. package/dist/chunk-E3SPN4VZ.js +0 -12917
  160. package/dist/chunk-E3SPN4VZ.js.map +0 -1
  161. package/dist/chunk-E66EQZE6 5.js +0 -37
  162. package/dist/chunk-E66EQZE6.js 2.map +0 -1
  163. package/dist/chunk-HWIIPPNI.js.map +0 -1
  164. package/dist/chunk-I7PSE6JW 5.js +0 -191
  165. package/dist/chunk-I7PSE6JW.js 2.map +0 -1
  166. package/dist/chunk-KNC55RTG.js 5.map +0 -1
  167. package/dist/chunk-KQCRWDSA.js 5.map +0 -1
  168. package/dist/chunk-LFNCN2SP.js 2.map +0 -1
  169. package/dist/chunk-LFNCN2SP.js.map +0 -1
  170. package/dist/chunk-LMC26NLJ 2.js +0 -84
  171. package/dist/chunk-NOAYCWCX.js +0 -4993
  172. package/dist/chunk-NOAYCWCX.js.map +0 -1
  173. package/dist/chunk-QWWZ5CAQ.js.map +0 -1
  174. package/dist/chunk-QXHPKYJV 3.js +0 -113
  175. package/dist/chunk-R77UEZ4E 3.js +0 -68
  176. package/dist/chunk-VBXEHIUJ.js 6.map +0 -1
  177. package/dist/chunk-XNXXZ43G.js.map +0 -1
  178. package/dist/chunk-ZSAAAMVR 6.js +0 -25
  179. package/dist/components.js 5.map +0 -1
  180. package/dist/styles/index 2.js +0 -12
  181. package/dist/styles/index.js 5.map +0 -1
  182. package/dist/theming/runtime 5.js +0 -19
  183. package/dist/theming/runtime.js 5.map +0 -1
  184. /package/dist/{DataTable-5FU7IESH.js.map → DataTable-DQ7RSOHE.js.map} +0 -0
  185. /package/dist/{UnifiedAuthProvider-RGJTDE2C.js.map → UnifiedAuthProvider-ATAP5UTR.js.map} +0 -0
  186. /package/dist/{chunk-BC4IJKSL.js.map → chunk-JBKQ3SAO.js.map} +0 -0
  187. /package/dist/{chunk-QWWZ5CAQ.js 3.map → chunk-LXQLPRQ2.js.map} +0 -0
  188. /package/examples/{rbac → RBAC}/CompleteRBACExample.tsx +0 -0
  189. /package/examples/{rbac → RBAC}/EventBasedApp.tsx +0 -0
  190. /package/examples/{rbac → RBAC}/PermissionExample.tsx +0 -0
  191. /package/examples/{rbac → RBAC}/index.ts +0 -0
@@ -55,13 +55,13 @@ vi.mock('../Label', () => ({
55
55
 
56
56
  describe('PasswordChangeForm', () => {
57
57
  const mockOnSubmit = vi.fn();
58
- const defaultProps: PasswordChangeFormProps = {
58
+ const baseProps: PasswordChangeFormProps = {
59
59
  onSubmit: mockOnSubmit
60
60
  };
61
61
 
62
62
  describe('Rendering', () => {
63
63
  it('renders password change form with all elements', () => {
64
- render(<PasswordChangeForm {...defaultProps} />);
64
+ render(<PasswordChangeForm {...baseProps} />);
65
65
 
66
66
  expect(screen.getByLabelText('New Password')).toBeInTheDocument();
67
67
  expect(screen.getByLabelText('Confirm Password')).toBeInTheDocument();
@@ -70,14 +70,14 @@ describe('PasswordChangeForm', () => {
70
70
 
71
71
  it('renders with custom className', () => {
72
72
  const customClass = 'custom-password-change-form';
73
- const { container } = render(<PasswordChangeForm {...defaultProps} className={customClass} />);
73
+ const { container } = render(<PasswordChangeForm {...baseProps} className={customClass} />);
74
74
 
75
75
  const form = container.querySelector('form');
76
76
  expect(form).toHaveClass(customClass);
77
77
  });
78
78
 
79
79
  it('has proper form structure and accessibility', () => {
80
- render(<PasswordChangeForm {...defaultProps} />);
80
+ render(<PasswordChangeForm {...baseProps} />);
81
81
 
82
82
  const newPasswordInput = screen.getByLabelText('New Password');
83
83
  expect(newPasswordInput).toHaveAttribute('type', 'password');
@@ -97,7 +97,7 @@ describe('PasswordChangeForm', () => {
97
97
  describe('Form Interaction', () => {
98
98
  it('updates new password input value when user types', async () => {
99
99
  const user = userEvent.setup();
100
- render(<PasswordChangeForm {...defaultProps} />);
100
+ render(<PasswordChangeForm {...baseProps} />);
101
101
 
102
102
  const newPasswordInput = screen.getByLabelText('New Password');
103
103
  await user.type(newPasswordInput, 'newpassword123');
@@ -107,7 +107,7 @@ describe('PasswordChangeForm', () => {
107
107
 
108
108
  it('updates confirm password input value when user types', async () => {
109
109
  const user = userEvent.setup();
110
- render(<PasswordChangeForm {...defaultProps} />);
110
+ render(<PasswordChangeForm {...baseProps} />);
111
111
 
112
112
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
113
113
  await user.type(confirmPasswordInput, 'newpassword123');
@@ -117,7 +117,7 @@ describe('PasswordChangeForm', () => {
117
117
 
118
118
  it('enables submit button when both passwords are provided', async () => {
119
119
  const user = userEvent.setup();
120
- render(<PasswordChangeForm {...defaultProps} />);
120
+ render(<PasswordChangeForm {...baseProps} />);
121
121
 
122
122
  const newPasswordInput = screen.getByLabelText('New Password');
123
123
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -133,7 +133,7 @@ describe('PasswordChangeForm', () => {
133
133
 
134
134
  it('disables submit button when new password is empty', async () => {
135
135
  const user = userEvent.setup();
136
- render(<PasswordChangeForm {...defaultProps} />);
136
+ render(<PasswordChangeForm {...baseProps} />);
137
137
 
138
138
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
139
139
  const submitButton = screen.getByRole('button', { name: 'Change Password' });
@@ -145,7 +145,7 @@ describe('PasswordChangeForm', () => {
145
145
 
146
146
  it('disables submit button when confirm password is empty', async () => {
147
147
  const user = userEvent.setup();
148
- render(<PasswordChangeForm {...defaultProps} />);
148
+ render(<PasswordChangeForm {...baseProps} />);
149
149
 
150
150
  const newPasswordInput = screen.getByLabelText('New Password');
151
151
  const submitButton = screen.getByRole('button', { name: 'Change Password' });
@@ -159,7 +159,7 @@ describe('PasswordChangeForm', () => {
159
159
  describe('Form Validation', () => {
160
160
  it('shows error when password is too short', async () => {
161
161
  const user = userEvent.setup();
162
- render(<PasswordChangeForm {...defaultProps} />);
162
+ render(<PasswordChangeForm {...baseProps} />);
163
163
 
164
164
  const newPasswordInput = screen.getByLabelText('New Password');
165
165
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -175,7 +175,7 @@ describe('PasswordChangeForm', () => {
175
175
 
176
176
  it('shows error when passwords do not match', async () => {
177
177
  const user = userEvent.setup();
178
- render(<PasswordChangeForm {...defaultProps} />);
178
+ render(<PasswordChangeForm {...baseProps} />);
179
179
 
180
180
  const newPasswordInput = screen.getByLabelText('New Password');
181
181
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -191,7 +191,7 @@ describe('PasswordChangeForm', () => {
191
191
 
192
192
  it('validates password length before checking match', async () => {
193
193
  const user = userEvent.setup();
194
- render(<PasswordChangeForm {...defaultProps} />);
194
+ render(<PasswordChangeForm {...baseProps} />);
195
195
 
196
196
  const newPasswordInput = screen.getByLabelText('New Password');
197
197
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -207,7 +207,7 @@ describe('PasswordChangeForm', () => {
207
207
 
208
208
  it('clears error when form is resubmitted with valid data', async () => {
209
209
  const user = userEvent.setup();
210
- render(<PasswordChangeForm {...defaultProps} />);
210
+ render(<PasswordChangeForm {...baseProps} />);
211
211
 
212
212
  const newPasswordInput = screen.getByLabelText('New Password');
213
213
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -236,7 +236,7 @@ describe('PasswordChangeForm', () => {
236
236
  const user = userEvent.setup();
237
237
  mockOnSubmit.mockResolvedValue({});
238
238
 
239
- render(<PasswordChangeForm {...defaultProps} />);
239
+ render(<PasswordChangeForm {...baseProps} />);
240
240
 
241
241
  const newPasswordInput = screen.getByLabelText('New Password');
242
242
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -254,7 +254,7 @@ describe('PasswordChangeForm', () => {
254
254
 
255
255
  it('prevents form submission when validation fails', async () => {
256
256
  const user = userEvent.setup();
257
- render(<PasswordChangeForm {...defaultProps} />);
257
+ render(<PasswordChangeForm {...baseProps} />);
258
258
 
259
259
  const newPasswordInput = screen.getByLabelText('New Password');
260
260
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -273,7 +273,7 @@ describe('PasswordChangeForm', () => {
273
273
  const user = userEvent.setup();
274
274
  mockOnSubmit.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
275
275
 
276
- render(<PasswordChangeForm {...defaultProps} />);
276
+ render(<PasswordChangeForm {...baseProps} />);
277
277
 
278
278
  const newPasswordInput = screen.getByLabelText('New Password');
279
279
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -291,7 +291,7 @@ describe('PasswordChangeForm', () => {
291
291
  const user = userEvent.setup();
292
292
  mockOnSubmit.mockImplementation(() => new Promise(resolve => setTimeout(resolve, 100)));
293
293
 
294
- render(<PasswordChangeForm {...defaultProps} />);
294
+ render(<PasswordChangeForm {...baseProps} />);
295
295
 
296
296
  const newPasswordInput = screen.getByLabelText('New Password');
297
297
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -309,7 +309,7 @@ describe('PasswordChangeForm', () => {
309
309
  const user = userEvent.setup();
310
310
  mockOnSubmit.mockResolvedValue({});
311
311
 
312
- render(<PasswordChangeForm {...defaultProps} />);
312
+ render(<PasswordChangeForm {...baseProps} />);
313
313
 
314
314
  const newPasswordInput = screen.getByLabelText('New Password');
315
315
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -329,7 +329,7 @@ describe('PasswordChangeForm', () => {
329
329
  const user = userEvent.setup();
330
330
  mockOnSubmit.mockResolvedValue({ error: { message: 'Test error' } });
331
331
 
332
- render(<PasswordChangeForm {...defaultProps} />);
332
+ render(<PasswordChangeForm {...baseProps} />);
333
333
 
334
334
  const newPasswordInput = screen.getByLabelText('New Password');
335
335
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -352,7 +352,7 @@ describe('PasswordChangeForm', () => {
352
352
  const errorMessage = 'Password change failed';
353
353
  mockOnSubmit.mockResolvedValue({ error: { message: errorMessage } });
354
354
 
355
- render(<PasswordChangeForm {...defaultProps} />);
355
+ render(<PasswordChangeForm {...baseProps} />);
356
356
 
357
357
  const newPasswordInput = screen.getByLabelText('New Password');
358
358
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -373,7 +373,7 @@ describe('PasswordChangeForm', () => {
373
373
  const errorMessage = 'Network error';
374
374
  mockOnSubmit.mockRejectedValue(new Error(errorMessage));
375
375
 
376
- render(<PasswordChangeForm {...defaultProps} />);
376
+ render(<PasswordChangeForm {...baseProps} />);
377
377
 
378
378
  const newPasswordInput = screen.getByLabelText('New Password');
379
379
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -393,7 +393,7 @@ describe('PasswordChangeForm', () => {
393
393
  const user = userEvent.setup();
394
394
  mockOnSubmit.mockRejectedValue('String error');
395
395
 
396
- render(<PasswordChangeForm {...defaultProps} />);
396
+ render(<PasswordChangeForm {...baseProps} />);
397
397
 
398
398
  const newPasswordInput = screen.getByLabelText('New Password');
399
399
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -412,7 +412,7 @@ describe('PasswordChangeForm', () => {
412
412
  const user = userEvent.setup();
413
413
  mockOnSubmit.mockResolvedValue({ error: { code: 'INVALID_PASSWORD' } });
414
414
 
415
- render(<PasswordChangeForm {...defaultProps} />);
415
+ render(<PasswordChangeForm {...baseProps} />);
416
416
 
417
417
  const newPasswordInput = screen.getByLabelText('New Password');
418
418
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -433,7 +433,7 @@ describe('PasswordChangeForm', () => {
433
433
  .mockResolvedValueOnce({ error: { message: 'First error' } })
434
434
  .mockResolvedValueOnce({});
435
435
 
436
- render(<PasswordChangeForm {...defaultProps} />);
436
+ render(<PasswordChangeForm {...baseProps} />);
437
437
 
438
438
  const newPasswordInput = screen.getByLabelText('New Password');
439
439
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -457,7 +457,7 @@ describe('PasswordChangeForm', () => {
457
457
 
458
458
  describe('Accessibility', () => {
459
459
  it('has proper form labels and associations', () => {
460
- render(<PasswordChangeForm {...defaultProps} />);
460
+ render(<PasswordChangeForm {...baseProps} />);
461
461
 
462
462
  const newPasswordInput = screen.getByLabelText('New Password');
463
463
  expect(newPasswordInput).toHaveAttribute('id', 'new-password');
@@ -472,7 +472,7 @@ describe('PasswordChangeForm', () => {
472
472
  const user = userEvent.setup();
473
473
  mockOnSubmit.mockResolvedValue({ error: { message: 'Test error' } });
474
474
 
475
- render(<PasswordChangeForm {...defaultProps} />);
475
+ render(<PasswordChangeForm {...baseProps} />);
476
476
 
477
477
  const newPasswordInput = screen.getByLabelText('New Password');
478
478
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -492,7 +492,7 @@ describe('PasswordChangeForm', () => {
492
492
  const user = userEvent.setup();
493
493
  mockOnSubmit.mockResolvedValue({});
494
494
 
495
- render(<PasswordChangeForm {...defaultProps} />);
495
+ render(<PasswordChangeForm {...baseProps} />);
496
496
 
497
497
  const newPasswordInput = screen.getByLabelText('New Password');
498
498
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -510,7 +510,7 @@ describe('PasswordChangeForm', () => {
510
510
 
511
511
  describe('Edge Cases', () => {
512
512
  it('handles empty password values', () => {
513
- render(<PasswordChangeForm {...defaultProps} />);
513
+ render(<PasswordChangeForm {...baseProps} />);
514
514
 
515
515
  const submitButton = screen.getByRole('button', { name: 'Change Password' });
516
516
 
@@ -519,7 +519,7 @@ describe('PasswordChangeForm', () => {
519
519
 
520
520
  it('handles whitespace-only passwords', async () => {
521
521
  const user = userEvent.setup();
522
- render(<PasswordChangeForm {...defaultProps} />);
522
+ render(<PasswordChangeForm {...baseProps} />);
523
523
 
524
524
  const newPasswordInput = screen.getByLabelText('New Password');
525
525
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -538,7 +538,7 @@ describe('PasswordChangeForm', () => {
538
538
  const longPassword = 'a'.repeat(1000);
539
539
  mockOnSubmit.mockResolvedValue({});
540
540
 
541
- render(<PasswordChangeForm {...defaultProps} />);
541
+ render(<PasswordChangeForm {...baseProps} />);
542
542
 
543
543
  const newPasswordInput = screen.getByLabelText('New Password');
544
544
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -559,7 +559,7 @@ describe('PasswordChangeForm', () => {
559
559
  const specialPassword = 'P@ssw0rd!@#$%^&*()_+-=';
560
560
  mockOnSubmit.mockResolvedValue({});
561
561
 
562
- render(<PasswordChangeForm {...defaultProps} />);
562
+ render(<PasswordChangeForm {...baseProps} />);
563
563
 
564
564
  const newPasswordInput = screen.getByLabelText('New Password');
565
565
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -580,7 +580,7 @@ describe('PasswordChangeForm', () => {
580
580
  const unicodePassword = 'pássw0rd_测试_пароль';
581
581
  mockOnSubmit.mockResolvedValue({});
582
582
 
583
- render(<PasswordChangeForm {...defaultProps} />);
583
+ render(<PasswordChangeForm {...baseProps} />);
584
584
 
585
585
  const newPasswordInput = screen.getByLabelText('New Password');
586
586
  const confirmPasswordInput = screen.getByLabelText('Confirm Password');
@@ -600,7 +600,7 @@ describe('PasswordChangeForm', () => {
600
600
  describe('Performance', () => {
601
601
  it('handles rapid input changes efficiently', async () => {
602
602
  const user = userEvent.setup();
603
- render(<PasswordChangeForm {...defaultProps} />);
603
+ render(<PasswordChangeForm {...baseProps} />);
604
604
 
605
605
  const newPasswordInput = screen.getByLabelText('New Password');
606
606
 
@@ -90,7 +90,7 @@
90
90
  * - Proper error handling
91
91
  *
92
92
  * @dependencies
93
- * - React 18+ - Hooks and state
93
+ * - React 19+ - Hooks and state
94
94
  * - Button component
95
95
  * - Input component
96
96
  * - Label component
@@ -57,7 +57,7 @@
57
57
  * - Minimal DOM structure (native HTML element)
58
58
  *
59
59
  * @dependencies
60
- * - React 18+ - Hooks and refs
60
+ * - React 19+ - Hooks and refs
61
61
  * - Tailwind CSS - Styling
62
62
  */
63
63
 
@@ -58,7 +58,7 @@
58
58
  * - Error handling for invalid routes
59
59
  *
60
60
  * @dependencies
61
- * - React 18+ - Component framework
61
+ * - React 19+ - Component framework
62
62
  * - React Router - Routing integration
63
63
  * - Public hooks - Data access
64
64
  * - Tailwind CSS - Styling
@@ -53,7 +53,7 @@ export interface UseSelectStateProps {
53
53
  export interface UseSelectEventsProps {
54
54
  state: SelectState;
55
55
  actions: SelectActions;
56
- selectRef: React.RefObject<HTMLElement>;
56
+ selectRef: React.RefObject<HTMLElement | null>;
57
57
  }
58
58
 
59
59
  export interface UseSelectSearchProps {
@@ -299,12 +299,13 @@ export const useSelectSearch = ({
299
299
  if (!React.isValidElement(child)) return child;
300
300
 
301
301
  // Check if this is a SelectItem by looking for the data-testid or value prop
302
- const isSelectItem = child.props &&
303
- (child.props['data-testid'] === 'select-item' ||
304
- child.props.value !== undefined);
302
+ const childProps = child.props as { 'data-testid'?: string; value?: unknown; children?: React.ReactNode; [key: string]: unknown };
303
+ const isSelectItem = childProps &&
304
+ (childProps['data-testid'] === 'select-item' ||
305
+ childProps.value !== undefined);
305
306
 
306
307
  if (isSelectItem) {
307
- const childText = React.Children.toArray(child.props.children).join(' ').toLowerCase();
308
+ const childText = React.Children.toArray(childProps.children).join(' ').toLowerCase();
308
309
  const searchLower = searchTerm.toLowerCase();
309
310
 
310
311
  if (childText.includes(searchLower)) {
@@ -313,8 +314,8 @@ export const useSelectSearch = ({
313
314
  return null;
314
315
  }
315
316
 
316
- if (child.props.children) {
317
- const filteredChildChildren = filterChildren(child.props.children);
317
+ if (childProps.children) {
318
+ const filteredChildChildren = filterChildren(childProps.children);
318
319
  if (React.Children.count(filteredChildChildren) > 0) {
319
320
  return React.cloneElement(child, {}, filteredChildChildren);
320
321
  }
@@ -359,8 +360,11 @@ const useSelectContext = () => {
359
360
  const getTextContent = (children: React.ReactNode): string => {
360
361
  if (typeof children === 'string') return children;
361
362
  if (typeof children === 'number') return children.toString();
362
- if (React.isValidElement(children) && children.props.children) {
363
- return getTextContent(children.props.children);
363
+ if (React.isValidElement(children)) {
364
+ const props = children.props as { children?: React.ReactNode; [key: string]: unknown };
365
+ if (props.children) {
366
+ return getTextContent(props.children);
367
+ }
364
368
  }
365
369
  if (Array.isArray(children)) {
366
370
  return children.map(getTextContent).join('');
@@ -443,7 +447,7 @@ export const Select = React.forwardRef<HTMLFormElement, SelectProps & UseSelectS
443
447
  const internalRef = React.useRef<HTMLFormElement>(null);
444
448
  const selectRef = React.useMemo(() => {
445
449
  if (ref && typeof ref === 'object' && 'current' in ref) {
446
- return ref as React.RefObject<HTMLFormElement>;
450
+ return ref as React.RefObject<HTMLFormElement | null>;
447
451
  }
448
452
  return internalRef;
449
453
  }, [ref]);
@@ -620,7 +624,7 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
620
624
  const opensUpward = direction === 'up';
621
625
 
622
626
  // Use ref to store the latest handleClick to avoid re-creating the effect
623
- const handleClickRef = React.useRef<(e: React.MouseEvent) => void>();
627
+ const handleClickRef = React.useRef<(e: React.MouseEvent) => void>(undefined);
624
628
 
625
629
  const handleClick = React.useCallback((e: React.MouseEvent) => {
626
630
  if (disabled) {
@@ -687,22 +691,26 @@ export const SelectTrigger = React.forwardRef<HTMLButtonElement, SelectTriggerPr
687
691
  };
688
692
 
689
693
  if (asChild) {
690
- const childChildren = React.Children.toArray((children as React.ReactElement).props.children);
691
- const hasChevron = childChildren.some(child =>
692
- React.isValidElement(child) &&
693
- (child.type === ChevronDown ||
694
- (child.type === 'svg' && child.props['data-testid'] === 'chevron-down') ||
695
- (typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown'))
696
- );
694
+ const childElement = children as React.ReactElement<{ children?: React.ReactNode; [key: string]: unknown }>;
695
+ const childChildren = React.Children.toArray(childElement.props.children);
696
+ const hasChevron = childChildren.some(child => {
697
+ if (React.isValidElement(child)) {
698
+ const childProps = child.props as { 'data-testid'?: string; [key: string]: unknown };
699
+ return child.type === ChevronDown ||
700
+ (child.type === 'svg' && childProps['data-testid'] === 'chevron-down') ||
701
+ (typeof child === 'object' && 'type' in child && typeof child.type === 'function' && child.type.name === 'ChevronDown');
702
+ }
703
+ return false;
704
+ });
697
705
 
698
706
  // Merge child's className with triggerProps className
699
- const childClassName = (children as React.ReactElement).props.className;
707
+ const childClassName = (children as React.ReactElement<any>).props.className;
700
708
  const mergedClassName = cn(
701
709
  triggerProps.className,
702
710
  childClassName
703
711
  );
704
712
 
705
- return React.cloneElement(children as React.ReactElement, {
713
+ return React.cloneElement(children as React.ReactElement<any>, {
706
714
  ...triggerProps,
707
715
  className: mergedClassName,
708
716
  children: hasChevron ? childChildren : [
@@ -915,8 +923,11 @@ export const SelectItem = React.forwardRef<HTMLLIElement, SelectItemProps>(
915
923
  const getTextContent = (children: React.ReactNode): string => {
916
924
  if (typeof children === 'string') return children;
917
925
  if (typeof children === 'number') return children.toString();
918
- if (React.isValidElement(children) && children.props.children) {
919
- return getTextContent(children.props.children);
926
+ if (React.isValidElement(children)) {
927
+ const props = children.props as { children?: React.ReactNode; [key: string]: unknown };
928
+ if (props.children) {
929
+ return getTextContent(props.children);
930
+ }
920
931
  }
921
932
  if (Array.isArray(children)) {
922
933
  return children.map(getTextContent).join('');
@@ -30,7 +30,7 @@
30
30
  * - High contrast support
31
31
  *
32
32
  * @dependencies
33
- * - React 18+ - Component framework
33
+ * - React 19+ - Component framework
34
34
  * - LoadingSpinner - Spinner component
35
35
  * - Tailwind CSS - Styling
36
36
  */
@@ -85,7 +85,7 @@
85
85
  * - Caption for table description
86
86
  *
87
87
  * @dependencies
88
- * - React 18+ - Hooks and refs
88
+ * - React 19+ - Hooks and refs
89
89
  * - Tailwind CSS - Styling
90
90
  */
91
91
 
@@ -93,35 +93,33 @@ export interface TextareaProps
93
93
  * />
94
94
  * ```
95
95
  */
96
- const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
97
- ({ className, variant = 'default', size = 'md', error, ...props }, ref) => {
98
- return (
99
- <textarea
100
- className={cn(
101
- // Base styles (matching Input component)
102
- 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
103
-
104
- // Variant styles
105
- {
106
- 'border-input': variant === 'default' && !error,
107
- 'border-destructive focus-visible:ring-destructive': variant === 'destructive' || error,
108
- },
109
-
110
- // Size styles
111
- {
112
- 'min-h-[60px] px-2 py-1 text-xs': size === 'sm',
113
- 'min-h-[80px] px-3 py-2 text-sm': size === 'md',
114
- 'min-h-[100px] px-4 py-3 text-base': size === 'lg',
115
- },
116
-
117
- className
118
- )}
119
- ref={ref}
120
- {...props}
121
- />
122
- );
123
- }
124
- );
96
+ function Textarea({ className, variant = 'default', size = 'md', error, ref, ...props }: TextareaProps & { ref?: React.Ref<HTMLTextAreaElement> }) {
97
+ return (
98
+ <textarea
99
+ className={cn(
100
+ // Base styles (matching Input component)
101
+ 'flex w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50',
102
+
103
+ // Variant styles
104
+ {
105
+ 'border-input': variant === 'default' && !error,
106
+ 'border-destructive focus-visible:ring-destructive': variant === 'destructive' || error,
107
+ },
108
+
109
+ // Size styles
110
+ {
111
+ 'min-h-[60px] px-2 py-1 text-xs': size === 'sm',
112
+ 'min-h-[80px] px-3 py-2 text-sm': size === 'md',
113
+ 'min-h-[100px] px-4 py-3 text-base': size === 'lg',
114
+ },
115
+
116
+ className
117
+ )}
118
+ ref={ref}
119
+ {...props}
120
+ />
121
+ );
122
+ }
125
123
 
126
124
  Textarea.displayName = 'Textarea';
127
125
 
@@ -81,7 +81,7 @@
81
81
  * @dependencies
82
82
  * - @radix-ui/react-toast - Core toast functionality
83
83
  * - lucide-react - Icons
84
- * - React 18+ - Hooks and refs
84
+ * - React 19+ - Hooks and refs
85
85
  * - Tailwind CSS - Styling
86
86
  */
87
87
 
@@ -67,7 +67,7 @@
67
67
  *
68
68
  * @dependencies
69
69
  * - @radix-ui/react-tooltip - Core tooltip functionality
70
- * - React 18+ - Hooks and refs
70
+ * - React 19+ - Hooks and refs
71
71
  * - Tailwind CSS - Styling and animations
72
72
  */
73
73
 
@@ -90,7 +90,7 @@
90
90
  * - Optimized avatar rendering
91
91
  *
92
92
  * @dependencies
93
- * - React 18+ - Hooks and memo
93
+ * - React 19+ - Hooks and memo
94
94
  * - @supabase/supabase-js - User type
95
95
  * - lucide-react - Icons
96
96
  * - DropdownMenu components