@jmruthers/pace-core 0.5.183 → 0.5.185

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 (307) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +60 -1
  3. package/core-usage-manifest.json +312 -0
  4. package/dist/{DataTable-QAB34V6K.js → DataTable-IX2NBUTP.js} +6 -6
  5. package/dist/{DataTable-Bz8ffqyA.d.ts → DataTable-Z9NLVJh0.d.ts} +1 -1
  6. package/dist/{index-Bl--n7-T.d.ts → PublicPageProvider-BABf6JCh.d.ts} +21 -10
  7. package/dist/{UnifiedAuthProvider-7F6T4B6K.js → UnifiedAuthProvider-A4BCQRJY.js} +4 -2
  8. package/dist/{UnifiedAuthProvider-F86d7dSi.d.ts → UnifiedAuthProvider-BG0AL5eE.d.ts} +2 -1
  9. package/dist/{api-ROMBCNKU.js → api-BMFCXVQX.js} +2 -2
  10. package/dist/{chunk-RA3JUFMW.js → chunk-445GEP27.js} +154 -4
  11. package/dist/{chunk-RA3JUFMW.js.map → chunk-445GEP27.js.map} +1 -1
  12. package/dist/{chunk-CSOFYHAG.js → chunk-AISXLWGZ.js} +374 -60
  13. package/dist/chunk-AISXLWGZ.js.map +1 -0
  14. package/dist/{chunk-FUEYYMX5.js → chunk-FXFJRTKI.js} +24 -3
  15. package/dist/chunk-FXFJRTKI.js.map +1 -0
  16. package/dist/{chunk-QETLRQI6.js → chunk-HC67NW5K.js} +380 -360
  17. package/dist/chunk-HC67NW5K.js.map +1 -0
  18. package/dist/chunk-HESYZWZW.js +388 -0
  19. package/dist/chunk-HESYZWZW.js.map +1 -0
  20. package/dist/{chunk-QUVSNGIP.js → chunk-HGPQUCBC.js} +34 -9
  21. package/dist/{chunk-QUVSNGIP.js.map → chunk-HGPQUCBC.js.map} +1 -1
  22. package/dist/{chunk-UHNYIBXL.js → chunk-IXSNYUCT.js} +1 -1
  23. package/dist/chunk-IXSNYUCT.js.map +1 -0
  24. package/dist/{chunk-MI7HBHN3.js → chunk-MX3EIJGQ.js} +4 -3
  25. package/dist/{chunk-MI7HBHN3.js.map → chunk-MX3EIJGQ.js.map} +1 -1
  26. package/dist/{chunk-PWAHJW4G.js → chunk-OKI34GZD.js} +86 -33
  27. package/dist/chunk-OKI34GZD.js.map +1 -0
  28. package/dist/{chunk-W22JP75J.js → chunk-STTZQK2I.js} +3 -3
  29. package/dist/chunk-THRPYOFK.js +215 -0
  30. package/dist/chunk-THRPYOFK.js.map +1 -0
  31. package/dist/{chunk-M7W4CP3M.js → chunk-U6WNSFX5.js} +2 -1
  32. package/dist/chunk-U6WNSFX5.js.map +1 -0
  33. package/dist/{chunk-QCDXODCA.js → chunk-XAUHJD3L.js} +2 -2
  34. package/dist/components.d.ts +182 -6
  35. package/dist/components.js +157 -11
  36. package/dist/components.js.map +1 -1
  37. package/dist/eslint-rules/pace-core-compliance.cjs +406 -0
  38. package/dist/{file-reference-D06mEEWW.d.ts → file-reference-BjR39ktt.d.ts} +7 -1
  39. package/dist/hooks.d.ts +7 -14
  40. package/dist/hooks.js +10 -22
  41. package/dist/hooks.js.map +1 -1
  42. package/dist/index.d.ts +11 -11
  43. package/dist/index.js +79 -16
  44. package/dist/index.js.map +1 -1
  45. package/dist/providers.d.ts +1 -1
  46. package/dist/providers.js +3 -1
  47. package/dist/rbac/index.d.ts +205 -14
  48. package/dist/rbac/index.js +28 -6
  49. package/dist/timezone-_pgH8qrY.d.ts +530 -0
  50. package/dist/{types-_x1f4QBF.d.ts → types-DUyCRSTj.d.ts} +1 -1
  51. package/dist/types.d.ts +1 -1
  52. package/dist/types.js +1 -1
  53. package/dist/{usePublicRouteParams-JJczomYq.d.ts → usePublicRouteParams-CvnC3d-e.d.ts} +113 -2
  54. package/dist/utils.d.ts +109 -151
  55. package/dist/utils.js +128 -138
  56. package/dist/utils.js.map +1 -1
  57. package/docs/api/README.md +60 -1
  58. package/docs/api/classes/ColumnFactory.md +1 -1
  59. package/docs/api/classes/ErrorBoundary.md +1 -1
  60. package/docs/api/classes/InvalidScopeError.md +1 -1
  61. package/docs/api/classes/Logger.md +178 -0
  62. package/docs/api/classes/MissingUserContextError.md +1 -1
  63. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  64. package/docs/api/classes/PermissionDeniedError.md +1 -1
  65. package/docs/api/classes/RBACAuditManager.md +2 -2
  66. package/docs/api/classes/RBACCache.md +1 -1
  67. package/docs/api/classes/RBACEngine.md +2 -2
  68. package/docs/api/classes/RBACError.md +1 -1
  69. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  70. package/docs/api/classes/SecureSupabaseClient.md +5 -5
  71. package/docs/api/classes/StorageUtils.md +1 -1
  72. package/docs/api/enums/FileCategory.md +1 -1
  73. package/docs/api/enums/LogLevel.md +54 -0
  74. package/docs/api/enums/RBACErrorCode.md +1 -1
  75. package/docs/api/enums/RPCFunction.md +1 -1
  76. package/docs/api/interfaces/AggregateConfig.md +1 -1
  77. package/docs/api/interfaces/BadgeProps.md +1 -1
  78. package/docs/api/interfaces/ButtonProps.md +1 -1
  79. package/docs/api/interfaces/CalendarProps.md +18 -2
  80. package/docs/api/interfaces/CardProps.md +1 -1
  81. package/docs/api/interfaces/ColorPalette.md +1 -1
  82. package/docs/api/interfaces/ColorShade.md +1 -1
  83. package/docs/api/interfaces/ComplianceResult.md +30 -0
  84. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  85. package/docs/api/interfaces/DataRecord.md +1 -1
  86. package/docs/api/interfaces/DataTableAction.md +1 -1
  87. package/docs/api/interfaces/DataTableColumn.md +1 -1
  88. package/docs/api/interfaces/DataTableProps.md +1 -1
  89. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  90. package/docs/api/interfaces/DatabaseComplianceResult.md +85 -0
  91. package/docs/api/interfaces/DatabaseIssue.md +41 -0
  92. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  93. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  94. package/docs/api/interfaces/EventAppRoleData.md +6 -6
  95. package/docs/api/interfaces/ExportColumn.md +1 -1
  96. package/docs/api/interfaces/ExportOptions.md +1 -1
  97. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  98. package/docs/api/interfaces/FileMetadata.md +1 -1
  99. package/docs/api/interfaces/FileReference.md +1 -1
  100. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  101. package/docs/api/interfaces/FileUploadOptions.md +24 -8
  102. package/docs/api/interfaces/FileUploadProps.md +24 -13
  103. package/docs/api/interfaces/FooterProps.md +1 -1
  104. package/docs/api/interfaces/FormFieldProps.md +1 -1
  105. package/docs/api/interfaces/FormProps.md +1 -1
  106. package/docs/api/interfaces/GrantEventAppRoleParams.md +9 -9
  107. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  108. package/docs/api/interfaces/InputProps.md +1 -1
  109. package/docs/api/interfaces/LabelProps.md +1 -1
  110. package/docs/api/interfaces/LoggerConfig.md +62 -0
  111. package/docs/api/interfaces/LoginFormProps.md +1 -1
  112. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  113. package/docs/api/interfaces/NavigationContextType.md +1 -1
  114. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  115. package/docs/api/interfaces/NavigationItem.md +1 -1
  116. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  117. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  118. package/docs/api/interfaces/Organisation.md +1 -1
  119. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  120. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  121. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  122. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  123. package/docs/api/interfaces/PaceAppLayoutProps.md +36 -23
  124. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  125. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  126. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  127. package/docs/api/interfaces/PagePermissionGuardProps.md +11 -11
  128. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  129. package/docs/api/interfaces/PaletteData.md +1 -1
  130. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  131. package/docs/api/interfaces/ProgressProps.md +1 -1
  132. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  133. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  134. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  135. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  136. package/docs/api/interfaces/QuickFix.md +52 -0
  137. package/docs/api/interfaces/RBACAccessValidateParams.md +1 -1
  138. package/docs/api/interfaces/RBACAccessValidateResult.md +1 -1
  139. package/docs/api/interfaces/RBACAuditLogParams.md +1 -1
  140. package/docs/api/interfaces/RBACAuditLogResult.md +1 -1
  141. package/docs/api/interfaces/RBACConfig.md +4 -4
  142. package/docs/api/interfaces/RBACContext.md +1 -1
  143. package/docs/api/interfaces/RBACLogger.md +1 -1
  144. package/docs/api/interfaces/RBACPageAccessCheckParams.md +1 -1
  145. package/docs/api/interfaces/RBACPermissionCheckParams.md +1 -1
  146. package/docs/api/interfaces/RBACPermissionCheckResult.md +1 -1
  147. package/docs/api/interfaces/RBACPermissionsGetParams.md +1 -1
  148. package/docs/api/interfaces/RBACPermissionsGetResult.md +1 -1
  149. package/docs/api/interfaces/RBACResult.md +1 -1
  150. package/docs/api/interfaces/RBACRoleGrantParams.md +1 -1
  151. package/docs/api/interfaces/RBACRoleGrantResult.md +1 -1
  152. package/docs/api/interfaces/RBACRoleRevokeParams.md +1 -1
  153. package/docs/api/interfaces/RBACRoleRevokeResult.md +1 -1
  154. package/docs/api/interfaces/RBACRoleValidateParams.md +1 -1
  155. package/docs/api/interfaces/RBACRoleValidateResult.md +1 -1
  156. package/docs/api/interfaces/RBACRolesListParams.md +1 -1
  157. package/docs/api/interfaces/RBACRolesListResult.md +1 -1
  158. package/docs/api/interfaces/RBACSessionTrackParams.md +1 -1
  159. package/docs/api/interfaces/RBACSessionTrackResult.md +1 -1
  160. package/docs/api/interfaces/ResourcePermissions.md +1 -1
  161. package/docs/api/interfaces/RevokeEventAppRoleParams.md +7 -7
  162. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  163. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  164. package/docs/api/interfaces/RoleManagementResult.md +5 -5
  165. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  166. package/docs/api/interfaces/RouteConfig.md +1 -1
  167. package/docs/api/interfaces/RuntimeComplianceResult.md +55 -0
  168. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  169. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  170. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  171. package/docs/api/interfaces/SetupIssue.md +41 -0
  172. package/docs/api/interfaces/StorageConfig.md +1 -1
  173. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  174. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  175. package/docs/api/interfaces/StorageListOptions.md +1 -1
  176. package/docs/api/interfaces/StorageListResult.md +1 -1
  177. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  178. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  179. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  180. package/docs/api/interfaces/StyleImport.md +1 -1
  181. package/docs/api/interfaces/SwitchProps.md +1 -1
  182. package/docs/api/interfaces/TabsContentProps.md +1 -1
  183. package/docs/api/interfaces/TabsListProps.md +1 -1
  184. package/docs/api/interfaces/TabsProps.md +1 -1
  185. package/docs/api/interfaces/TabsTriggerProps.md +1 -1
  186. package/docs/api/interfaces/TextareaProps.md +1 -1
  187. package/docs/api/interfaces/ToastActionElement.md +1 -1
  188. package/docs/api/interfaces/ToastProps.md +1 -1
  189. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  190. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  191. package/docs/api/interfaces/UseFormDialogOptions.md +62 -0
  192. package/docs/api/interfaces/UseFormDialogReturn.md +117 -0
  193. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  194. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  195. package/docs/api/interfaces/UsePublicEventLogoOptions.md +2 -2
  196. package/docs/api/interfaces/UsePublicEventLogoReturn.md +1 -1
  197. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  198. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  199. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +2 -2
  200. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  201. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  202. package/docs/api/interfaces/UseResolvedScopeOptions.md +2 -2
  203. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  204. package/docs/api/interfaces/UseResourcePermissionsOptions.md +1 -1
  205. package/docs/api/interfaces/UserEventAccess.md +1 -1
  206. package/docs/api/interfaces/UserMenuProps.md +1 -1
  207. package/docs/api/interfaces/UserProfile.md +1 -1
  208. package/docs/api/modules.md +738 -42
  209. package/docs/api-reference/hooks.md +111 -0
  210. package/docs/api-reference/rpc-functions.md +1 -1
  211. package/docs/api-reference/utilities.md +184 -0
  212. package/docs/getting-started/installation-guide.md +75 -16
  213. package/docs/getting-started/quick-start.md +61 -11
  214. package/docs/implementation-guides/authentication.md +88 -12
  215. package/docs/implementation-guides/file-reference-system.md +2 -1
  216. package/docs/implementation-guides/file-upload-storage.md +21 -0
  217. package/docs/rbac/README.md +1 -0
  218. package/docs/rbac/compliance/compliance-guide.md +544 -0
  219. package/docs/rbac/getting-started.md +158 -33
  220. package/docs/standards/pace-core-compliance.md +432 -0
  221. package/eslint-config-pace-core.cjs +93 -0
  222. package/package.json +15 -3
  223. package/scripts/analyze-bundle.js +232 -0
  224. package/scripts/build-css.js +56 -0
  225. package/scripts/build-docs-incremental.js +1015 -0
  226. package/scripts/check-pace-core-compliance.cjs +2353 -0
  227. package/scripts/generate-docs.js +157 -0
  228. package/scripts/setup-build-cache.js +73 -0
  229. package/scripts/utils/command-runner.js +131 -0
  230. package/scripts/utils/env.js +33 -0
  231. package/scripts/utils/index.js +10 -0
  232. package/scripts/utils/logger.js +88 -0
  233. package/scripts/utils/path-helpers.js +37 -0
  234. package/scripts/validate-formats.js +133 -0
  235. package/scripts/validate-master.js +155 -0
  236. package/scripts/validate-pre-publish.js +140 -0
  237. package/scripts/validate-theme.js +142 -0
  238. package/src/components/Calendar/Calendar.tsx +8 -1
  239. package/src/components/Card/Card.tsx +47 -8
  240. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +314 -0
  241. package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +126 -0
  242. package/src/components/DatePickerWithTimezone/README.md +135 -0
  243. package/src/components/DatePickerWithTimezone/index.ts +10 -0
  244. package/src/components/DateTimeField/DateTimeField.test.tsx +358 -0
  245. package/src/components/DateTimeField/DateTimeField.tsx +232 -0
  246. package/src/components/DateTimeField/README.md +148 -0
  247. package/src/components/DateTimeField/index.ts +10 -0
  248. package/src/components/FileUpload/FileUpload.tsx +3 -0
  249. package/src/components/Header/Header.test.tsx +47 -18
  250. package/src/components/Header/Header.tsx +24 -6
  251. package/src/components/PaceAppLayout/PaceAppLayout.tsx +29 -20
  252. package/src/components/PaceAppLayout/README.md +9 -0
  253. package/src/components/PaceLoginPage/PaceLoginPage.tsx +1 -1
  254. package/src/components/ProtectedRoute/ProtectedRoute.test.tsx +37 -8
  255. package/src/components/ProtectedRoute/ProtectedRoute.tsx +12 -4
  256. package/src/components/index.ts +8 -0
  257. package/src/eslint-rules/pace-core-compliance.cjs +406 -0
  258. package/src/eslint-rules/pace-core-compliance.js +640 -0
  259. package/src/hooks/__tests__/useFormDialog.test.ts +478 -0
  260. package/src/hooks/index.ts +2 -0
  261. package/src/hooks/useFileReference.test.ts +1 -0
  262. package/src/hooks/useFormDialog.ts +147 -0
  263. package/src/index.ts +27 -0
  264. package/src/providers/services/OrganisationServiceProvider.tsx +6 -5
  265. package/src/providers/services/UnifiedAuthProvider.tsx +24 -3
  266. package/src/rbac/__tests__/scenarios.user-role.test.tsx +3 -0
  267. package/src/rbac/compliance/database-validator.ts +165 -0
  268. package/src/rbac/compliance/index.ts +38 -0
  269. package/src/rbac/compliance/quick-fix-suggestions.ts +209 -0
  270. package/src/rbac/compliance/runtime-compliance.ts +77 -0
  271. package/src/rbac/compliance/setup-validator.ts +131 -0
  272. package/src/rbac/components/PagePermissionGuard.tsx +8 -64
  273. package/src/rbac/components/__tests__/PagePermissionGuard.test.tsx +35 -21
  274. package/src/rbac/docs/event-based-apps.md +285 -0
  275. package/src/rbac/errors.ts +11 -0
  276. package/src/rbac/hooks/useRoleManagement.ts +292 -12
  277. package/src/rbac/index.ts +30 -0
  278. package/src/services/OrganisationService.ts +4 -0
  279. package/src/types/file-reference.ts +6 -0
  280. package/src/utils/__tests__/timezone.test.ts +345 -0
  281. package/src/utils/file-reference/__tests__/file-reference.test.ts +2 -0
  282. package/src/utils/file-reference/index.ts +1 -0
  283. package/src/utils/formatting/formatDateTimeTimezone.test.ts +167 -0
  284. package/src/utils/formatting/formatting.ts +179 -0
  285. package/src/utils/index.ts +27 -1
  286. package/src/utils/location/index.ts +16 -0
  287. package/src/utils/location/location.test.ts +286 -0
  288. package/src/utils/location/location.ts +175 -0
  289. package/src/utils/timezone/index.ts +17 -0
  290. package/src/utils/timezone/timezone.test.ts +349 -0
  291. package/src/utils/timezone/timezone.ts +281 -0
  292. package/dist/chunk-CSOFYHAG.js.map +0 -1
  293. package/dist/chunk-FUEYYMX5.js.map +0 -1
  294. package/dist/chunk-HKIT6O7W.js +0 -198
  295. package/dist/chunk-HKIT6O7W.js.map +0 -1
  296. package/dist/chunk-KUEN3HFB.js +0 -94
  297. package/dist/chunk-KUEN3HFB.js.map +0 -1
  298. package/dist/chunk-M7W4CP3M.js.map +0 -1
  299. package/dist/chunk-PWAHJW4G.js.map +0 -1
  300. package/dist/chunk-QETLRQI6.js.map +0 -1
  301. package/dist/chunk-UHNYIBXL.js.map +0 -1
  302. package/dist/formatting-5wETwiGF.d.ts +0 -162
  303. /package/dist/{DataTable-QAB34V6K.js.map → DataTable-IX2NBUTP.js.map} +0 -0
  304. /package/dist/{UnifiedAuthProvider-7F6T4B6K.js.map → UnifiedAuthProvider-A4BCQRJY.js.map} +0 -0
  305. /package/dist/{api-ROMBCNKU.js.map → api-BMFCXVQX.js.map} +0 -0
  306. /package/dist/{chunk-W22JP75J.js.map → chunk-STTZQK2I.js.map} +0 -0
  307. /package/dist/{chunk-QCDXODCA.js.map → chunk-XAUHJD3L.js.map} +0 -0
@@ -0,0 +1,314 @@
1
+ /**
2
+ * @file DatePickerWithTimezone Component Tests
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DatePickerWithTimezone/__tests__
5
+ * @since 0.1.0
6
+ *
7
+ * Comprehensive test suite for DatePickerWithTimezone component.
8
+ * Tests cover all major functionality, edge cases, and accessibility.
9
+ */
10
+
11
+ import React from 'react';
12
+ import { render, screen, fireEvent } from '@testing-library/react';
13
+ import userEvent from '@testing-library/user-event';
14
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
15
+ import { DatePickerWithTimezone } from './DatePickerWithTimezone';
16
+ import { Calendar } from '../Calendar';
17
+
18
+ // Mock timezone utilities
19
+ vi.mock('../../utils/timezone', () => ({
20
+ getUserTimeZone: vi.fn(() => 'America/New_York')
21
+ }));
22
+
23
+ // Mock Calendar component
24
+ vi.mock('../Calendar', () => ({
25
+ Calendar: vi.fn(({ selected, onSelect, ...props }) => (
26
+ <div data-testid="calendar" {...props}>
27
+ <button
28
+ data-testid="calendar-day"
29
+ onClick={() => onSelect?.(new Date('2024-01-15'))}
30
+ >
31
+ Day 15
32
+ </button>
33
+ {selected && <span data-testid="selected-date">{selected.toISOString()}</span>}
34
+ </div>
35
+ ))
36
+ }));
37
+
38
+ // Mock Button component
39
+ vi.mock('../Button', () => ({
40
+ Button: vi.fn(({ children, onClick, ...props }) => (
41
+ <button data-testid="done-button" onClick={onClick} {...props}>
42
+ {children}
43
+ </button>
44
+ ))
45
+ }));
46
+
47
+ // Mock lucide-react Clock icon
48
+ vi.mock('lucide-react', () => ({
49
+ Clock: ({ className, 'aria-hidden': ariaHidden, ...props }: any) => (
50
+ <svg
51
+ data-testid="clock-icon"
52
+ className={className}
53
+ aria-hidden={ariaHidden}
54
+ {...props}
55
+ >
56
+ <path d="M12 2v10l4 4" />
57
+ </svg>
58
+ )
59
+ }));
60
+
61
+ describe('DatePickerWithTimezone Component', () => {
62
+ beforeEach(() => {
63
+ vi.clearAllMocks();
64
+ });
65
+
66
+ describe('Rendering', () => {
67
+ it('renders with default props', () => {
68
+ const onSelect = vi.fn();
69
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
70
+
71
+ expect(screen.getByTestId('calendar')).toBeInTheDocument();
72
+ expect(screen.getByText(/Timezone:/)).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders with selected date', () => {
76
+ const onSelect = vi.fn();
77
+ const selected = new Date('2024-01-15');
78
+ render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
79
+
80
+ expect(screen.getByTestId('selected-date')).toBeInTheDocument();
81
+ });
82
+
83
+ it('renders with custom timezone', () => {
84
+ const onSelect = vi.fn();
85
+ render(
86
+ <DatePickerWithTimezone
87
+ selected={undefined}
88
+ onSelect={onSelect}
89
+ timezone="America/Los_Angeles"
90
+ />
91
+ );
92
+
93
+ expect(screen.getByText('America/Los_Angeles')).toBeInTheDocument();
94
+ });
95
+
96
+ it('displays "Local" when timezone matches user timezone', () => {
97
+ const onSelect = vi.fn();
98
+ render(
99
+ <DatePickerWithTimezone
100
+ selected={undefined}
101
+ onSelect={onSelect}
102
+ timezone="America/New_York"
103
+ />
104
+ );
105
+
106
+ expect(screen.getByText('Local')).toBeInTheDocument();
107
+ });
108
+
109
+ it('renders with Done button when onDone is provided', () => {
110
+ const onSelect = vi.fn();
111
+ const onDone = vi.fn();
112
+ render(
113
+ <DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
114
+ );
115
+
116
+ expect(screen.getByTestId('done-button')).toBeInTheDocument();
117
+ expect(screen.getByText('Done')).toBeInTheDocument();
118
+ });
119
+
120
+ it('does not render Done button when onDone is not provided', () => {
121
+ const onSelect = vi.fn();
122
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
123
+
124
+ expect(screen.queryByTestId('done-button')).not.toBeInTheDocument();
125
+ });
126
+
127
+ it('renders with custom className', () => {
128
+ const onSelect = vi.fn();
129
+ const { container } = render(
130
+ <DatePickerWithTimezone
131
+ selected={undefined}
132
+ onSelect={onSelect}
133
+ className="custom-class"
134
+ />
135
+ );
136
+
137
+ expect(container.firstChild).toHaveClass('custom-class');
138
+ });
139
+ });
140
+
141
+ describe('Date Selection', () => {
142
+ it('calls onSelect when date is selected', async () => {
143
+ const onSelect = vi.fn();
144
+ const user = userEvent.setup();
145
+
146
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
147
+
148
+ const dayButton = screen.getByTestId('calendar-day');
149
+ await user.click(dayButton);
150
+
151
+ expect(onSelect).toHaveBeenCalled();
152
+ expect(onSelect).toHaveBeenCalledWith(expect.any(Date));
153
+ });
154
+
155
+ it('handles date selection with existing selected date', async () => {
156
+ const onSelect = vi.fn();
157
+ const user = userEvent.setup();
158
+ const selected = new Date('2024-01-10');
159
+
160
+ render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
161
+
162
+ const dayButton = screen.getByTestId('calendar-day');
163
+ await user.click(dayButton);
164
+
165
+ expect(onSelect).toHaveBeenCalled();
166
+ });
167
+ });
168
+
169
+ describe('Done Button', () => {
170
+ it('calls onDone when Done button is clicked', async () => {
171
+ const onSelect = vi.fn();
172
+ const onDone = vi.fn();
173
+ const user = userEvent.setup();
174
+
175
+ render(
176
+ <DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
177
+ );
178
+
179
+ const doneButton = screen.getByTestId('done-button');
180
+ await user.click(doneButton);
181
+
182
+ expect(onDone).toHaveBeenCalledTimes(1);
183
+ });
184
+
185
+ it('does not call onSelect when Done button is clicked', async () => {
186
+ const onSelect = vi.fn();
187
+ const onDone = vi.fn();
188
+ const user = userEvent.setup();
189
+
190
+ render(
191
+ <DatePickerWithTimezone selected={undefined} onSelect={onSelect} onDone={onDone} />
192
+ );
193
+
194
+ const doneButton = screen.getByTestId('done-button');
195
+ await user.click(doneButton);
196
+
197
+ expect(onSelect).not.toHaveBeenCalled();
198
+ });
199
+ });
200
+
201
+ describe('Timezone Display', () => {
202
+ it('displays user timezone as "Local" when no timezone provided', () => {
203
+ const onSelect = vi.fn();
204
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
205
+
206
+ expect(screen.getByText('Local')).toBeInTheDocument();
207
+ });
208
+
209
+ it('displays timezone name when different from user timezone', () => {
210
+ const onSelect = vi.fn();
211
+ render(
212
+ <DatePickerWithTimezone
213
+ selected={undefined}
214
+ onSelect={onSelect}
215
+ timezone="Europe/London"
216
+ />
217
+ );
218
+
219
+ expect(screen.getByText('Europe/London')).toBeInTheDocument();
220
+ });
221
+
222
+ it('displays Clock icon', () => {
223
+ const onSelect = vi.fn();
224
+ render(
225
+ <DatePickerWithTimezone selected={undefined} onSelect={onSelect} />
226
+ );
227
+
228
+ expect(screen.getByTestId('clock-icon')).toBeInTheDocument();
229
+ });
230
+ });
231
+
232
+ describe('Edge Cases', () => {
233
+ it('handles undefined selected date', () => {
234
+ const onSelect = vi.fn();
235
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
236
+
237
+ expect(screen.getByTestId('calendar')).toBeInTheDocument();
238
+ expect(screen.queryByTestId('selected-date')).not.toBeInTheDocument();
239
+ });
240
+
241
+ it('handles null selected date', () => {
242
+ const onSelect = vi.fn();
243
+ // @ts-expect-error - Testing edge case
244
+ render(<DatePickerWithTimezone selected={null} onSelect={onSelect} />);
245
+
246
+ expect(screen.getByTestId('calendar')).toBeInTheDocument();
247
+ });
248
+ });
249
+
250
+ describe('Accessibility', () => {
251
+ it('has proper ARIA labels for timezone', () => {
252
+ const onSelect = vi.fn();
253
+ render(
254
+ <DatePickerWithTimezone
255
+ selected={undefined}
256
+ onSelect={onSelect}
257
+ timezone="America/Los_Angeles"
258
+ />
259
+ );
260
+
261
+ const timezoneLabel = screen.getByLabelText('Timezone America/Los_Angeles');
262
+ expect(timezoneLabel).toBeInTheDocument();
263
+ });
264
+
265
+ it('marks Clock icon as decorative', () => {
266
+ const onSelect = vi.fn();
267
+ render(
268
+ <DatePickerWithTimezone selected={undefined} onSelect={onSelect} />
269
+ );
270
+
271
+ const clockIcon = screen.getByTestId('clock-icon');
272
+ expect(clockIcon).toBeInTheDocument();
273
+ expect(clockIcon).toHaveAttribute('aria-hidden', 'true');
274
+ });
275
+
276
+ it('supports keyboard navigation', async () => {
277
+ const onSelect = vi.fn();
278
+ const user = userEvent.setup();
279
+
280
+ render(<DatePickerWithTimezone selected={undefined} onSelect={onSelect} />);
281
+
282
+ const dayButton = screen.getByTestId('calendar-day');
283
+ await user.tab();
284
+
285
+ // Calendar should be keyboard accessible
286
+ expect(dayButton).toBeInTheDocument();
287
+ });
288
+ });
289
+
290
+ describe('Calendar Integration', () => {
291
+ it('passes correct props to Calendar component', () => {
292
+ const onSelect = vi.fn();
293
+ const selected = new Date('2024-01-15');
294
+ const MockedCalendar = vi.mocked(Calendar);
295
+
296
+ render(<DatePickerWithTimezone selected={selected} onSelect={onSelect} />);
297
+
298
+ expect(MockedCalendar).toHaveBeenCalledWith(
299
+ expect.objectContaining({
300
+ mode: 'single',
301
+ selected: selected,
302
+ onSelect: onSelect,
303
+ initialFocus: true,
304
+ captionLayout: 'dropdown-buttons',
305
+ fromYear: 1900,
306
+ toYear: 2100,
307
+ className: 'p-0'
308
+ }),
309
+ expect.anything()
310
+ );
311
+ });
312
+ });
313
+ });
314
+
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @file DatePickerWithTimezone Component
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DatePickerWithTimezone
5
+ * @since 0.1.0
6
+ *
7
+ * Date picker component that displays timezone information alongside the calendar.
8
+ * Provides a calendar interface with timezone context for date selection.
9
+ *
10
+ * Features:
11
+ * - Calendar date selection
12
+ * - Timezone display (shows "Local" when matches user timezone)
13
+ * - Optional "Done" button
14
+ * - Accessible date selection
15
+ * - Keyboard navigation support
16
+ *
17
+ * @example
18
+ * ```tsx
19
+ * import { DatePickerWithTimezone } from '@jmruthers/pace-core/components';
20
+ * import { useState } from 'react';
21
+ *
22
+ * function DateSelector() {
23
+ * const [date, setDate] = useState<Date>();
24
+ *
25
+ * return (
26
+ * <DatePickerWithTimezone
27
+ * selected={date}
28
+ * onSelect={setDate}
29
+ * timezone="America/New_York"
30
+ * onDone={() => console.log('Date selected')}
31
+ * />
32
+ * );
33
+ * }
34
+ * ```
35
+ *
36
+ * @accessibility
37
+ * - WCAG 2.1 AA compliant
38
+ * - Keyboard navigation support
39
+ * - Screen reader friendly
40
+ * - Focus management
41
+ * - Proper ARIA attributes
42
+ */
43
+
44
+ import * as React from 'react';
45
+ import { Calendar } from '../Calendar';
46
+ import { Button } from '../Button';
47
+ import { Clock } from 'lucide-react';
48
+ import { getUserTimeZone } from '../../utils/timezone';
49
+ import { cn } from '../../utils/core/cn';
50
+
51
+ /**
52
+ * Props for the DatePickerWithTimezone component
53
+ */
54
+ export interface DatePickerWithTimezoneProps {
55
+ /**
56
+ * Currently selected date
57
+ */
58
+ selected?: Date;
59
+ /**
60
+ * Date selection handler
61
+ */
62
+ onSelect: (date: Date | undefined) => void;
63
+ /**
64
+ * Optional callback when "Done" button is clicked
65
+ */
66
+ onDone?: () => void;
67
+ /**
68
+ * Timezone to display (defaults to user's timezone)
69
+ */
70
+ timezone?: string;
71
+ /**
72
+ * Additional CSS classes
73
+ */
74
+ className?: string;
75
+ }
76
+
77
+ /**
78
+ * DatePickerWithTimezone component
79
+ * Date picker with timezone information display
80
+ *
81
+ * @param props - DatePickerWithTimezone configuration
82
+ * @returns JSX.Element - The rendered date picker with timezone
83
+ */
84
+ export function DatePickerWithTimezone({
85
+ selected,
86
+ onSelect,
87
+ onDone,
88
+ timezone,
89
+ className
90
+ }: DatePickerWithTimezoneProps) {
91
+ const userTimezone = getUserTimeZone();
92
+ const displayTimezone = timezone || userTimezone;
93
+ const timezoneDisplay = displayTimezone === userTimezone ? 'Local' : displayTimezone;
94
+
95
+ return (
96
+ <div className={cn('flex flex-col', className)}>
97
+ <div className="p-3">
98
+ <Calendar
99
+ mode="single"
100
+ selected={selected}
101
+ onSelect={onSelect}
102
+ initialFocus
103
+ captionLayout="dropdown-buttons"
104
+ fromYear={1900}
105
+ toYear={2100}
106
+ className="p-0"
107
+ />
108
+ </div>
109
+
110
+ <div className="flex items-center justify-between border-t border-border px-3 py-2">
111
+ <div className="flex items-center gap-2 text-sm text-muted-foreground">
112
+ <Clock className="h-4 w-4" aria-hidden="true" />
113
+ <span>
114
+ Timezone: <span aria-label={`Timezone ${timezoneDisplay}`}>{timezoneDisplay}</span>
115
+ </span>
116
+ </div>
117
+ {onDone && (
118
+ <Button onClick={onDone} size="sm" className="h-8">
119
+ Done
120
+ </Button>
121
+ )}
122
+ </div>
123
+ </div>
124
+ );
125
+ }
126
+
@@ -0,0 +1,135 @@
1
+ # DatePickerWithTimezone Component
2
+
3
+ Date picker component that displays timezone information alongside the calendar.
4
+
5
+ ## Overview
6
+
7
+ The `DatePickerWithTimezone` component provides a calendar interface for date selection with timezone context. It displays the timezone information below the calendar and optionally includes a "Done" button.
8
+
9
+ ## Features
10
+
11
+ - Calendar date selection
12
+ - Timezone display (shows "Local" when matches user timezone)
13
+ - Optional "Done" button
14
+ - Accessible date selection
15
+ - Keyboard navigation support
16
+ - Integration with pace-core Calendar component
17
+
18
+ ## Usage
19
+
20
+ ### Basic Example
21
+
22
+ ```tsx
23
+ import { DatePickerWithTimezone } from '@jmruthers/pace-core/components';
24
+ import { useState } from 'react';
25
+
26
+ function DateSelector() {
27
+ const [date, setDate] = useState<Date>();
28
+
29
+ return (
30
+ <DatePickerWithTimezone
31
+ selected={date}
32
+ onSelect={setDate}
33
+ timezone="America/New_York"
34
+ />
35
+ );
36
+ }
37
+ ```
38
+
39
+ ### With Done Button
40
+
41
+ ```tsx
42
+ <DatePickerWithTimezone
43
+ selected={date}
44
+ onSelect={setDate}
45
+ timezone="America/New_York"
46
+ onDone={() => {
47
+ console.log('Date selected:', date);
48
+ // Close dialog or perform other actions
49
+ }}
50
+ />
51
+ ```
52
+
53
+ ### Using User's Timezone
54
+
55
+ ```tsx
56
+ // When timezone is not provided, uses user's browser timezone
57
+ <DatePickerWithTimezone
58
+ selected={date}
59
+ onSelect={setDate}
60
+ />
61
+ ```
62
+
63
+ ## API
64
+
65
+ ### Props
66
+
67
+ | Prop | Type | Default | Description |
68
+ |------|------|---------|-------------|
69
+ | `selected` | `Date \| undefined` | - | Currently selected date |
70
+ | `onSelect` | `(date: Date \| undefined) => void` | **required** | Date selection handler |
71
+ | `onDone` | `() => void` | - | Optional callback when "Done" button is clicked |
72
+ | `timezone` | `string` | User's timezone | Timezone to display (IANA timezone string) |
73
+ | `className` | `string` | - | Additional CSS classes |
74
+
75
+ ## Behavior
76
+
77
+ ### Timezone Display
78
+
79
+ - When `timezone` is not provided: Uses the user's browser timezone
80
+ - When `timezone` matches user's timezone: Shows "Local"
81
+ - When `timezone` is different: Shows the timezone name (e.g., "America/New_York")
82
+
83
+ ### Date Selection
84
+
85
+ The component uses the pace-core `Calendar` component for date selection. Selected dates are passed directly to `onSelect` without timezone conversion (dates are timezone-agnostic).
86
+
87
+ ### Done Button
88
+
89
+ When `onDone` is provided, a "Done" button appears in the footer. Clicking it calls the `onDone` callback without affecting the selected date.
90
+
91
+ ## Accessibility
92
+
93
+ - WCAG 2.1 AA compliant
94
+ - Keyboard navigation support (via Calendar component)
95
+ - Screen reader friendly
96
+ - Proper ARIA labels for timezone information
97
+ - Focus management
98
+
99
+ ## Integration
100
+
101
+ ### With Dialog
102
+
103
+ ```tsx
104
+ import { Dialog, DatePickerWithTimezone } from '@jmruthers/pace-core/components';
105
+
106
+ function DatePickerDialog() {
107
+ const [date, setDate] = useState<Date>();
108
+ const [open, setOpen] = useState(false);
109
+
110
+ return (
111
+ <Dialog open={open} onOpenChange={setOpen}>
112
+ <DialogContent>
113
+ <DatePickerWithTimezone
114
+ selected={date}
115
+ onSelect={setDate}
116
+ onDone={() => setOpen(false)}
117
+ />
118
+ </DialogContent>
119
+ </Dialog>
120
+ );
121
+ }
122
+ ```
123
+
124
+ ## Edge Cases
125
+
126
+ - **Undefined selected date**: Calendar displays with no date selected
127
+ - **Null selected date**: Treated as undefined
128
+
129
+ ## Dependencies
130
+
131
+ - `@jmruthers/pace-core/components/Calendar` - Calendar component
132
+ - `@jmruthers/pace-core/components/Button` - Button component
133
+ - `lucide-react` - Clock icon
134
+ - `@jmruthers/pace-core/utils/timezone` - Timezone utilities
135
+
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @file DatePickerWithTimezone Component Exports
3
+ * @package @jmruthers/pace-core
4
+ * @module Components/DatePickerWithTimezone
5
+ * @since 0.1.0
6
+ */
7
+
8
+ export { DatePickerWithTimezone } from './DatePickerWithTimezone';
9
+ export type { DatePickerWithTimezoneProps } from './DatePickerWithTimezone';
10
+