@carlonicora/nextjs-jsonapi 6.24.0 → 6.26.0

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 (250) hide show
  1. package/dist/{BlockNoteEditor-MONT74RQ.js → BlockNoteEditor-JHXDOCS5.js} +28 -25
  2. package/dist/BlockNoteEditor-JHXDOCS5.js.map +1 -0
  3. package/dist/{BlockNoteEditor-ENDOVTAW.mjs → BlockNoteEditor-RVDSB6TF.mjs} +13 -10
  4. package/dist/{BlockNoteEditor-ENDOVTAW.mjs.map → BlockNoteEditor-RVDSB6TF.mjs.map} +1 -1
  5. package/dist/{BlockNoteViewer-2TJZ6JET.mjs → BlockNoteViewer-76CKL6W5.mjs} +8 -6
  6. package/dist/{BlockNoteViewer-2TJZ6JET.mjs.map → BlockNoteViewer-76CKL6W5.mjs.map} +1 -1
  7. package/dist/{BlockNoteViewer-K2HBF3IP.js → BlockNoteViewer-B25WKYQP.js} +9 -7
  8. package/dist/BlockNoteViewer-B25WKYQP.js.map +1 -0
  9. package/dist/JsonApiRequest-OKNQZLS6.js +26 -0
  10. package/dist/{JsonApiRequest-KC6DOHKT.js.map → JsonApiRequest-OKNQZLS6.js.map} +1 -1
  11. package/dist/{JsonApiRequest-ADLBJ5RX.mjs → JsonApiRequest-WNPDS356.mjs} +2 -2
  12. package/dist/billing/index.js +504 -517
  13. package/dist/billing/index.js.map +1 -1
  14. package/dist/billing/index.mjs +20 -33
  15. package/dist/billing/index.mjs.map +1 -1
  16. package/dist/{chunk-VNUT2D27.mjs → chunk-2PWYG2WJ.mjs} +2 -2
  17. package/dist/{chunk-W4ML6D2X.mjs → chunk-3U7YJ73T.mjs} +7 -7
  18. package/dist/{chunk-BQ2WAG4K.js → chunk-3VNDFKT7.js} +19 -19
  19. package/dist/{chunk-BQ2WAG4K.js.map → chunk-3VNDFKT7.js.map} +1 -1
  20. package/dist/{chunk-BE6UMM5X.mjs → chunk-47HNJQ7U.mjs} +3 -3
  21. package/dist/chunk-5T2AQMQ2.mjs +90 -0
  22. package/dist/chunk-5T2AQMQ2.mjs.map +1 -0
  23. package/dist/{chunk-A5M3MORY.js → chunk-5WM72MLE.js} +3 -3
  24. package/dist/{chunk-A5M3MORY.js.map → chunk-5WM72MLE.js.map} +1 -1
  25. package/dist/{chunk-OZADFYE7.mjs → chunk-64REWDSK.mjs} +308 -241
  26. package/dist/chunk-64REWDSK.mjs.map +1 -0
  27. package/dist/{chunk-S7SPAC6X.mjs → chunk-73NKUBMP.mjs} +3 -3
  28. package/dist/chunk-743M4TIB.mjs +76 -0
  29. package/dist/chunk-743M4TIB.mjs.map +1 -0
  30. package/dist/{chunk-HN7L6RRW.js → chunk-77JC2UCF.js} +903 -955
  31. package/dist/chunk-77JC2UCF.js.map +1 -0
  32. package/dist/{chunk-7OIWOPM5.mjs → chunk-7MGXQIEP.mjs} +4 -3
  33. package/dist/chunk-7MGXQIEP.mjs.map +1 -0
  34. package/dist/{chunk-YTDGYI62.mjs → chunk-DWPHHUMV.mjs} +3 -3
  35. package/dist/{chunk-SO3YUCL6.js → chunk-F3EVYBMA.js} +6 -5
  36. package/dist/chunk-F3EVYBMA.js.map +1 -0
  37. package/dist/{chunk-53SD4YRF.js → chunk-FBHUUPRV.js} +5 -5
  38. package/dist/{chunk-53SD4YRF.js.map → chunk-FBHUUPRV.js.map} +1 -1
  39. package/dist/{chunk-T6G2F2BK.mjs → chunk-FJUKLVVX.mjs} +16 -68
  40. package/dist/chunk-FJUKLVVX.mjs.map +1 -0
  41. package/dist/chunk-HROKKUBQ.js +156 -0
  42. package/dist/chunk-HROKKUBQ.js.map +1 -0
  43. package/dist/{chunk-BDWV4KYR.js → chunk-JE6QWCNE.js} +1386 -1319
  44. package/dist/chunk-JE6QWCNE.js.map +1 -0
  45. package/dist/{chunk-RCOKQ3R4.js → chunk-LNO2JLU3.js} +2 -2
  46. package/dist/{chunk-RCOKQ3R4.js.map → chunk-LNO2JLU3.js.map} +1 -1
  47. package/dist/{chunk-TMV7TL7O.mjs → chunk-M7YK5CCE.mjs} +8 -6
  48. package/dist/{chunk-TMV7TL7O.mjs.map → chunk-M7YK5CCE.mjs.map} +1 -1
  49. package/dist/{chunk-GMMDXINC.js → chunk-N3NQS7X6.js} +12 -12
  50. package/dist/{chunk-GMMDXINC.js.map → chunk-N3NQS7X6.js.map} +1 -1
  51. package/dist/chunk-NP3AFULU.js +76 -0
  52. package/dist/chunk-NP3AFULU.js.map +1 -0
  53. package/dist/{chunk-TNTCLVUR.js → chunk-ONU4WCDG.js} +17 -15
  54. package/dist/chunk-ONU4WCDG.js.map +1 -0
  55. package/dist/{chunk-YSE2Z4RB.js → chunk-PQQFOONG.js} +12 -12
  56. package/dist/{chunk-YSE2Z4RB.js.map → chunk-PQQFOONG.js.map} +1 -1
  57. package/dist/chunk-RW6XZGZG.mjs +156 -0
  58. package/dist/chunk-RW6XZGZG.mjs.map +1 -0
  59. package/dist/chunk-SYMI4IDW.js +90 -0
  60. package/dist/chunk-SYMI4IDW.js.map +1 -0
  61. package/dist/{chunk-N2BFT7KI.mjs → chunk-W7U6O3OE.mjs} +2 -71
  62. package/dist/chunk-W7U6O3OE.mjs.map +1 -0
  63. package/dist/{chunk-YMCL3JXZ.mjs → chunk-YPBIPE2B.mjs} +2 -2
  64. package/dist/{chunk-P7L6OFX5.js → chunk-Z6T2575A.js} +12 -81
  65. package/dist/chunk-Z6T2575A.js.map +1 -0
  66. package/dist/client/index.js +13 -10
  67. package/dist/client/index.js.map +1 -1
  68. package/dist/client/index.mjs +17 -14
  69. package/dist/components/forms/DatePickerPopover.d.ts.map +1 -1
  70. package/dist/components/forms/DateRangeSelector.d.ts.map +1 -1
  71. package/dist/components/forms/FormDate.d.ts.map +1 -1
  72. package/dist/components/forms/FormDateTime.d.ts.map +1 -1
  73. package/dist/components/forms/time-cycle.d.ts +14 -0
  74. package/dist/components/forms/time-cycle.d.ts.map +1 -0
  75. package/dist/components/index.js +11 -8
  76. package/dist/components/index.js.map +1 -1
  77. package/dist/components/index.mjs +10 -7
  78. package/dist/components/tables/cells/cell.date.d.ts.map +1 -1
  79. package/dist/components/tables/cells/cell.dateTime.d.ts.map +1 -1
  80. package/dist/contexts/DateLocaleContext.d.ts +56 -0
  81. package/dist/contexts/DateLocaleContext.d.ts.map +1 -0
  82. package/dist/contexts/index.d.ts +1 -0
  83. package/dist/contexts/index.d.ts.map +1 -1
  84. package/dist/contexts/index.js +24 -8
  85. package/dist/contexts/index.js.map +1 -1
  86. package/dist/contexts/index.mjs +23 -7
  87. package/dist/core/index.js +17 -6
  88. package/dist/core/index.js.map +1 -1
  89. package/dist/core/index.mjs +19 -8
  90. package/dist/date-locale/index.d.ts +2 -0
  91. package/dist/date-locale/index.d.ts.map +1 -0
  92. package/dist/date-locale/index.js +21 -0
  93. package/dist/date-locale/index.js.map +1 -0
  94. package/dist/date-locale/index.mjs +21 -0
  95. package/dist/email-automations/index.js +131 -123
  96. package/dist/email-automations/index.js.map +1 -1
  97. package/dist/email-automations/index.mjs +23 -15
  98. package/dist/email-automations/index.mjs.map +1 -1
  99. package/dist/features/account-deletion/components/AccountDeletionAdminTrigger.d.ts.map +1 -1
  100. package/dist/features/analytics/components/AnalyticsJourneyContainer.d.ts.map +1 -1
  101. package/dist/features/analytics/components/AnalyticsSessionsTable.d.ts.map +1 -1
  102. package/dist/features/analytics/index.js +154 -147
  103. package/dist/features/analytics/index.js.map +1 -1
  104. package/dist/features/analytics/index.mjs +32 -25
  105. package/dist/features/analytics/index.mjs.map +1 -1
  106. package/dist/features/analytics/server.js +11 -9
  107. package/dist/features/analytics/server.js.map +1 -1
  108. package/dist/features/analytics/server.mjs +9 -7
  109. package/dist/features/analytics/server.mjs.map +1 -1
  110. package/dist/features/billing/components/utils/date.d.ts +3 -3
  111. package/dist/features/billing/components/utils/format-options.d.ts +6 -4
  112. package/dist/features/billing/components/utils/format-options.d.ts.map +1 -1
  113. package/dist/features/billing/components/utils/useBillingFormatOptions.d.ts +5 -1
  114. package/dist/features/billing/components/utils/useBillingFormatOptions.d.ts.map +1 -1
  115. package/dist/features/billing/stripe-usage/components/details/UsageSummaryCard.d.ts.map +1 -1
  116. package/dist/features/billing/stripe-usage/components/lists/UsageHistoryTable.d.ts.map +1 -1
  117. package/dist/features/blog/components/admin/BlogTranslationStatus.d.ts.map +1 -1
  118. package/dist/features/blog/components/public/BlogPostMeta.d.ts.map +1 -1
  119. package/dist/features/blog/hooks/useBlogPostTableStructure.d.ts.map +1 -1
  120. package/dist/features/blog/index.js +168 -162
  121. package/dist/features/blog/index.js.map +1 -1
  122. package/dist/features/blog/index.mjs +25 -19
  123. package/dist/features/blog/index.mjs.map +1 -1
  124. package/dist/features/blog/server.js +29 -27
  125. package/dist/features/blog/server.js.map +1 -1
  126. package/dist/features/blog/server.mjs +8 -6
  127. package/dist/features/blog/server.mjs.map +1 -1
  128. package/dist/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.d.ts.map +1 -1
  129. package/dist/features/handbook/components/parts/HandbookIndexStatus.d.ts.map +1 -1
  130. package/dist/features/help/components/HelpArticleBody.d.ts.map +1 -1
  131. package/dist/features/help/index.js +48 -43
  132. package/dist/features/help/index.js.map +1 -1
  133. package/dist/features/help/index.mjs +15 -10
  134. package/dist/features/help/index.mjs.map +1 -1
  135. package/dist/features/notification/components/notifications/Notification.d.ts +2 -1
  136. package/dist/features/notification/components/notifications/Notification.d.ts.map +1 -1
  137. package/dist/features/notification/contexts/NotificationContext.d.ts.map +1 -1
  138. package/dist/features/notification/hooks/useNotificationTableStructure.d.ts.map +1 -1
  139. package/dist/features/oauth/components/OAuthClientCard.d.ts.map +1 -1
  140. package/dist/features/oauth/components/OAuthClientDetail.d.ts.map +1 -1
  141. package/dist/features/tokenusage/index.js +133 -130
  142. package/dist/features/tokenusage/index.js.map +1 -1
  143. package/dist/features/tokenusage/index.mjs +10 -7
  144. package/dist/features/tokenusage/index.mjs.map +1 -1
  145. package/dist/features/tokenusage/lib/formatters.d.ts +6 -3
  146. package/dist/features/tokenusage/lib/formatters.d.ts.map +1 -1
  147. package/dist/index.js +19 -7
  148. package/dist/index.js.map +1 -1
  149. package/dist/index.mjs +23 -11
  150. package/dist/local-transport-AT4TE264.js +15 -0
  151. package/dist/{local-transport-MJPOKPTU.js.map → local-transport-AT4TE264.js.map} +1 -1
  152. package/dist/{local-transport-RVD4FZGA.mjs → local-transport-KWEUZ4GX.mjs} +5 -5
  153. package/dist/offline/index.js +56 -54
  154. package/dist/offline/index.js.map +1 -1
  155. package/dist/offline/index.mjs +23 -21
  156. package/dist/offline/index.mjs.map +1 -1
  157. package/dist/{request-4AWGPIGH.mjs → request-6GAIWWTG.mjs} +2 -2
  158. package/dist/{request-H5FZBZA2.js → request-BWO4Y434.js} +3 -3
  159. package/dist/{request-H5FZBZA2.js.map → request-BWO4Y434.js.map} +1 -1
  160. package/dist/server/ServerSession.d.ts +2 -0
  161. package/dist/server/ServerSession.d.ts.map +1 -1
  162. package/dist/server/index.js +10 -8
  163. package/dist/server/index.js.map +1 -1
  164. package/dist/server/index.mjs +9 -7
  165. package/dist/utils/date-formatter.d.ts +45 -4
  166. package/dist/utils/date-formatter.d.ts.map +1 -1
  167. package/dist/utils/index.d.ts +1 -1
  168. package/dist/utils/index.d.ts.map +1 -1
  169. package/dist/utils/toast.d.ts +2 -0
  170. package/dist/utils/toast.d.ts.map +1 -1
  171. package/dist/write-through-KNRI5L4V.mjs +15 -0
  172. package/dist/write-through-KNRI5L4V.mjs.map +1 -0
  173. package/dist/write-through-MYHQ6GJU.js +15 -0
  174. package/dist/{write-through-HE2QSOVI.js.map → write-through-MYHQ6GJU.js.map} +1 -1
  175. package/messages/en.json +1 -0
  176. package/messages/it.json +1 -0
  177. package/package.json +6 -1
  178. package/src/components/forms/DatePickerPopover.tsx +5 -3
  179. package/src/components/forms/DateRangeSelector.tsx +9 -7
  180. package/src/components/forms/FormDate.tsx +5 -3
  181. package/src/components/forms/FormDateTime.tsx +75 -39
  182. package/src/components/forms/__tests__/time-cycle.spec.ts +37 -0
  183. package/src/components/forms/time-cycle.ts +27 -0
  184. package/src/components/tables/cells/cell.date.tsx +3 -5
  185. package/src/components/tables/cells/cell.dateTime.tsx +3 -7
  186. package/src/contexts/DateLocaleContext.tsx +106 -0
  187. package/src/contexts/__tests__/DateLocaleContext.spec.tsx +175 -0
  188. package/src/contexts/index.ts +1 -0
  189. package/src/date-locale/index.ts +12 -0
  190. package/src/features/account-deletion/components/AccountDeletionAdminTrigger.tsx +3 -4
  191. package/src/features/analytics/components/AnalyticsJourneyContainer.tsx +8 -8
  192. package/src/features/analytics/components/AnalyticsSessionsTable.tsx +4 -6
  193. package/src/features/auth/components/two-factor/PasskeyList.tsx +3 -3
  194. package/src/features/auth/components/two-factor/TotpAuthenticatorList.tsx +3 -3
  195. package/src/features/billing/components/utils/__tests__/billing-formatters.spec.ts +14 -6
  196. package/src/features/billing/components/utils/date.ts +5 -5
  197. package/src/features/billing/components/utils/format-options.ts +6 -4
  198. package/src/features/billing/components/utils/useBillingFormatOptions.ts +8 -2
  199. package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +4 -12
  200. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +4 -17
  201. package/src/features/blog/components/admin/BlogTranslationStatus.tsx +3 -2
  202. package/src/features/blog/components/public/BlogPostMeta.tsx +5 -4
  203. package/src/features/blog/components/public/__tests__/BlogList.spec.tsx +1 -2
  204. package/src/features/blog/components/public/__tests__/BlogPostView.spec.tsx +2 -3
  205. package/src/features/blog/hooks/useBlogPostTableStructure.tsx +4 -8
  206. package/src/features/email-automation/hooks/useEmailAutomationEnrollmentTableStructure.tsx +11 -5
  207. package/src/features/email-blast/components/containers/EmailBlastContainer.tsx +3 -3
  208. package/src/features/email-blast/hooks/useEmailBlastTableStructure.tsx +4 -4
  209. package/src/features/handbook/components/parts/HandbookIndexStatus.tsx +4 -3
  210. package/src/features/help/components/HelpArticleBody.tsx +4 -3
  211. package/src/features/notification/components/notifications/Notification.tsx +38 -7
  212. package/src/features/notification/contexts/NotificationContext.tsx +6 -3
  213. package/src/features/notification/hooks/useNotificationTableStructure.tsx +6 -5
  214. package/src/features/oauth/components/OAuthClientCard.tsx +8 -5
  215. package/src/features/oauth/components/OAuthClientDetail.tsx +3 -6
  216. package/src/features/tokenusage/lib/__tests__/formatters.spec.ts +19 -0
  217. package/src/features/tokenusage/lib/formatters.ts +11 -6
  218. package/src/features/waitlist/hooks/useWaitlistTableStructure.tsx +5 -5
  219. package/src/server/ServerSession.ts +3 -0
  220. package/src/server/__tests__/ServerSession.read.spec.ts +18 -1
  221. package/src/utils/__tests__/date-formatter.spec.ts +134 -10
  222. package/src/utils/__tests__/toast.spec.ts +39 -0
  223. package/src/utils/date-formatter.ts +200 -44
  224. package/src/utils/index.ts +11 -1
  225. package/src/utils/toast.ts +2 -0
  226. package/dist/BlockNoteEditor-MONT74RQ.js.map +0 -1
  227. package/dist/BlockNoteViewer-K2HBF3IP.js.map +0 -1
  228. package/dist/JsonApiRequest-KC6DOHKT.js +0 -26
  229. package/dist/chunk-7OIWOPM5.mjs.map +0 -1
  230. package/dist/chunk-BDWV4KYR.js.map +0 -1
  231. package/dist/chunk-HN7L6RRW.js.map +0 -1
  232. package/dist/chunk-N2BFT7KI.mjs.map +0 -1
  233. package/dist/chunk-OZADFYE7.mjs.map +0 -1
  234. package/dist/chunk-P7L6OFX5.js.map +0 -1
  235. package/dist/chunk-SO3YUCL6.js.map +0 -1
  236. package/dist/chunk-T6G2F2BK.mjs.map +0 -1
  237. package/dist/chunk-TNTCLVUR.js.map +0 -1
  238. package/dist/local-transport-MJPOKPTU.js +0 -15
  239. package/dist/write-through-7I3LA2WN.mjs +0 -15
  240. package/dist/write-through-HE2QSOVI.js +0 -15
  241. /package/dist/{JsonApiRequest-ADLBJ5RX.mjs.map → JsonApiRequest-WNPDS356.mjs.map} +0 -0
  242. /package/dist/{chunk-VNUT2D27.mjs.map → chunk-2PWYG2WJ.mjs.map} +0 -0
  243. /package/dist/{chunk-W4ML6D2X.mjs.map → chunk-3U7YJ73T.mjs.map} +0 -0
  244. /package/dist/{chunk-BE6UMM5X.mjs.map → chunk-47HNJQ7U.mjs.map} +0 -0
  245. /package/dist/{chunk-S7SPAC6X.mjs.map → chunk-73NKUBMP.mjs.map} +0 -0
  246. /package/dist/{chunk-YTDGYI62.mjs.map → chunk-DWPHHUMV.mjs.map} +0 -0
  247. /package/dist/{chunk-YMCL3JXZ.mjs.map → chunk-YPBIPE2B.mjs.map} +0 -0
  248. /package/dist/{local-transport-RVD4FZGA.mjs.map → date-locale/index.mjs.map} +0 -0
  249. /package/dist/{request-4AWGPIGH.mjs.map → local-transport-KWEUZ4GX.mjs.map} +0 -0
  250. /package/dist/{write-through-7I3LA2WN.mjs.map → request-6GAIWWTG.mjs.map} +0 -0
@@ -11,12 +11,53 @@ type FormatOption = "date" | "time" | "dateTime" | "timeSince" | "default";
11
11
  * instant in time and the UTC shift is correct.
12
12
  */
13
13
  export declare const formatLocalDate: (d: Date) => string;
14
+ export type DateFormatStyle = "date" | "dateMedium" | "dateLong" | "dateTime" | "dateTimeMedium" | "dateTimeLong" | "dateTimeSeconds" | "time" | "weekdayDateTime" | "weekdayDay" | "monthYear" | "monthYearLong" | "month" | "monthLong" | "dayMonth" | "weekday" | "weekdayLong";
14
15
  /**
15
- * The locale `formatDate` should use for an app locale. English keeps "en-GB",
16
- * the formatter's historical default, so English output never changes; any
17
- * other app locale ("it") is used as is.
16
+ * The option set behind each named style. No style pins `hour12` / `hourCycle`:
17
+ * the clock (12 or 24 hours) follows the locale.
18
18
  */
19
+ export declare const DATE_STYLE_OPTIONS: Record<DateFormatStyle, Intl.DateTimeFormatOptions>;
20
+ /** Public pages never show an all-numeric date: the month is spelled out. */
21
+ export declare const PUBLIC_STYLE_MAP: Partial<Record<DateFormatStyle, DateFormatStyle>>;
22
+ /**
23
+ * The locale every real-world date is formatted in.
24
+ *
25
+ * The first `Accept-Language` entry (quality order) whose language equals the
26
+ * app language's and that `Intl` supports; otherwise the app language. A
27
+ * malformed entry (`*`, an unparseable or zero quality, an unknown tag) is
28
+ * skipped, never thrown on.
29
+ */
30
+ export declare function resolveDateLocale(params: {
31
+ appLocale: string;
32
+ acceptLanguage?: string;
33
+ }): string;
34
+ /**
35
+ * Formats a date in a named style. Returns "" for `undefined` or a value that
36
+ * does not parse. `isPublic` swaps the all-numeric styles for month-as-word.
37
+ */
38
+ export declare function formatDateStyle(params: {
39
+ date: Date | string | undefined;
40
+ style: DateFormatStyle;
41
+ locale: string;
42
+ timeZone?: string;
43
+ isPublic?: boolean;
44
+ }): string;
45
+ /** "3 hours ago" / "3 ore fa", always numeric ("1 day ago", never "yesterday"). */
46
+ export declare function formatRelativeDate(params: {
47
+ date: Date | string;
48
+ locale: string;
49
+ now?: Date;
50
+ }): string;
51
+ /** True when the locale's clock is 12-hour (hour cycle h11 / h12). */
52
+ export declare function usesTwelveHourClock(locale: string): boolean;
53
+ /** The locale to format dates in for an app locale: the app locale itself, English when missing. */
19
54
  export declare const dateFormatLocale: (appLocale?: string) => string;
20
- export declare const formatDate: (eventDate: Date, formatOption: FormatOption, locale?: string) => string;
55
+ /**
56
+ * @deprecated Use `formatDateStyle` (or `useDateFormatter().format`) and
57
+ * `formatRelativeDate` (or `useDateFormatter().relative`). Kept for other nja
58
+ * apps: same default locale and the same "date" / "time" / "dateTime" output.
59
+ * Pass `now` from render code; the `new Date()` fallback is for legacy callers.
60
+ */
61
+ export declare const formatDate: (eventDate: Date, formatOption: FormatOption, locale?: string, now?: Date) => string;
21
62
  export type { FormatOption };
22
63
  //# sourceMappingURL=date-formatter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"date-formatter.d.ts","sourceRoot":"","sources":["../../src/utils/date-formatter.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,GAAG,IAAI,KAAG,MAKzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,MACgB,CAAC;AAEvE,eAAO,MAAM,UAAU,GAAI,WAAW,IAAI,EAAE,cAAc,YAAY,EAAE,SAAQ,MAAgB,KAAG,MAsDlG,CAAC;AAEF,YAAY,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"date-formatter.d.ts","sourceRoot":"","sources":["../../src/utils/date-formatter.ts"],"names":[],"mappings":"AAAA,KAAK,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,GAAG,WAAW,GAAG,SAAS,CAAC;AAE3E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,eAAe,GAAI,GAAG,IAAI,KAAG,MAKzC,CAAC;AAEF,MAAM,MAAM,eAAe,GACvB,MAAM,GACN,YAAY,GACZ,UAAU,GACV,UAAU,GACV,gBAAgB,GAChB,cAAc,GACd,iBAAiB,GACjB,MAAM,GACN,iBAAiB,GACjB,YAAY,GACZ,WAAW,GACX,eAAe,GACf,OAAO,GACP,WAAW,GACX,UAAU,GACV,SAAS,GACT,aAAa,CAAC;AAIlB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,MAAM,CAAC,eAAe,EAAE,IAAI,CAAC,qBAAqB,CAkBlF,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,gBAAgB,EAAE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,CAI9E,CAAC;AAyCF;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CAqChG;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE;IACtC,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,SAAS,CAAC;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB,GAAG,MAAM,CAKT;AAED,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,MAAM,EAAE;IAAE,IAAI,EAAE,IAAI,GAAG,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,IAAI,CAAA;CAAE,GAAG,MAAM,CAgBtG;AAED,sEAAsE;AACtE,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAG3D;AAED,oGAAoG;AACpG,eAAO,MAAM,gBAAgB,GAAI,YAAY,MAAM,KAAG,MAA2B,CAAC;AAKlF;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,WAAW,IAAI,EACf,cAAc,YAAY,EAC1B,SAAQ,MAAgB,EACxB,MAAM,IAAI,KACT,MAqBF,CAAC;AAEF,YAAY,EAAE,YAAY,EAAE,CAAC"}
@@ -2,7 +2,7 @@ export type { ClassValue } from "clsx";
2
2
  export { cn } from "./cn";
3
3
  export { composeRefs, useComposedRefs } from "./compose-refs";
4
4
  export { useIsMobile, MOBILE_BREAKPOINT, VIEWPORT_COOKIE_NAME } from "./use-mobile";
5
- export { dateFormatLocale, formatDate, formatLocalDate, type FormatOption } from "./date-formatter";
5
+ export { dateFormatLocale, formatDate, formatDateStyle, formatLocalDate, formatRelativeDate, resolveDateLocale, usesTwelveHourClock, type DateFormatStyle, type FormatOption, } from "./date-formatter";
6
6
  export { exists } from "./exists";
7
7
  export { getTableComponents, getTableOptions, TableOptions } from "./table-options";
8
8
  export { entityObjectSchema, userObjectSchema, type EntityObject, type UserObject } from "./schemas";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGpF,OAAO,EAAE,gBAAgB,EAAE,UAAU,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AACpG,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAErG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,8BAA8B,GACpC,MAAM,sBAAsB,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qCAAqC,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,UAAU,EAAE,MAAM,MAAM,CAAC;AACvC,OAAO,EAAE,EAAE,EAAE,MAAM,MAAM,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGpF,OAAO,EACL,gBAAgB,EAChB,UAAU,EACV,eAAe,EACf,eAAe,EACf,kBAAkB,EAClB,iBAAiB,EACjB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,YAAY,GAClB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAGpF,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAErG,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EACL,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACtB,gBAAgB,EAChB,mBAAmB,EACnB,KAAK,8BAA8B,GACpC,MAAM,sBAAsB,CAAC;AAC9B,cAAc,uBAAuB,CAAC;AACtC,cAAc,qCAAqC,CAAC;AACpD,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC"}
@@ -6,6 +6,8 @@ export type ToastOptions = {
6
6
  id?: string;
7
7
  onDismiss?: () => void;
8
8
  action?: ExternalToast["action"];
9
+ /** sonner's second button, shown beside `action`. */
10
+ cancel?: ExternalToast["cancel"];
9
11
  dismissible?: boolean;
10
12
  };
11
13
  /** Standard toast - neutral/white */
@@ -1 +1 @@
1
- {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../src/utils/toast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,mBAEhE;AAED,oCAAoC;AACpC,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,mBAEhE;AAED,uBAAuB;AACvB,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,mBAErD;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC,EAC/F,OAAO,CAAC,EAAE;IACR,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,mBAGF"}
1
+ {"version":3,"file":"toast.d.ts","sourceRoot":"","sources":["../../src/utils/toast.ts"],"names":[],"mappings":"AAAA,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,QAAQ,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,qBAAqB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG;IACzB,WAAW,CAAC,EAAE,SAAS,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,qDAAqD;IACrD,MAAM,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,CAAC;AAEF,qCAAqC;AACrC,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,mBAEhE;AAED,oCAAoC;AACpC,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,mBAEhE;AAED,uBAAuB;AACvB,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,mBAErD;AAED,8DAA8D;AAC9D,wBAAgB,eAAe,CAC7B,MAAM,EAAE,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,KAAK,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,qBAAqB,CAAC,OAAO,CAAC,CAAC,EAC/F,OAAO,CAAC,EAAE;IACR,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB,mBAGF"}
@@ -0,0 +1,15 @@
1
+ import {
2
+ writeThrough
3
+ } from "./chunk-3U7YJ73T.mjs";
4
+ import "./chunk-DWPHHUMV.mjs";
5
+ import "./chunk-4RPGFQBA.mjs";
6
+ import "./chunk-47HNJQ7U.mjs";
7
+ import "./chunk-73NKUBMP.mjs";
8
+ import "./chunk-4ZJIUTIV.mjs";
9
+ import "./chunk-YPBIPE2B.mjs";
10
+ import "./chunk-AUXK7QSA.mjs";
11
+ import "./chunk-PAWJFY3S.mjs";
12
+ export {
13
+ writeThrough
14
+ };
15
+ //# sourceMappingURL=write-through-KNRI5L4V.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,15 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});
2
+
3
+ var _chunkPQQFOONGjs = require('./chunk-PQQFOONG.js');
4
+ require('./chunk-FBHUUPRV.js');
5
+ require('./chunk-6RCZ64BQ.js');
6
+ require('./chunk-3VNDFKT7.js');
7
+ require('./chunk-5WM72MLE.js');
8
+ require('./chunk-WUNSG3IN.js');
9
+ require('./chunk-LNO2JLU3.js');
10
+ require('./chunk-LXKSUWAV.js');
11
+ require('./chunk-7QVYU63E.js');
12
+
13
+
14
+ exports.writeThrough = _chunkPQQFOONGjs.writeThrough;
15
+ //# sourceMappingURL=write-through-MYHQ6GJU.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/write-through-HE2QSOVI.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B;AACE;AACF,qDAAC","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/write-through-HE2QSOVI.js"}
1
+ {"version":3,"sources":["/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/write-through-MYHQ6GJU.js"],"names":[],"mappings":"AAAA;AACE;AACF,sDAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B,+BAA4B;AAC5B;AACE;AACF,qDAAC","file":"/home/runner/work/nextjs-jsonapi/nextjs-jsonapi/dist/write-through-MYHQ6GJU.js"}
package/messages/en.json CHANGED
@@ -1102,6 +1102,7 @@
1102
1102
  },
1103
1103
  "hide_password": "Hide Password",
1104
1104
  "home": "Home",
1105
+ "dayPeriod": "AM/PM",
1105
1106
  "hours": "Hours",
1106
1107
  "loading": "Loading...",
1107
1108
  "minutes": "Minutes",
package/messages/it.json CHANGED
@@ -1102,6 +1102,7 @@
1102
1102
  },
1103
1103
  "hide_password": "Nascondi password",
1104
1104
  "home": "Home",
1105
+ "dayPeriod": "AM/PM",
1105
1106
  "hours": "Ore",
1106
1107
  "loading": "Caricamento...",
1107
1108
  "minutes": "Minuti",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@carlonicora/nextjs-jsonapi",
3
- "version": "6.24.0",
3
+ "version": "6.26.0",
4
4
  "description": "Next.js JSON:API client with server/client support and caching",
5
5
  "author": "Carlo Nicora",
6
6
  "license": "GPL-3.0-or-later",
@@ -53,6 +53,11 @@
53
53
  "import": "./dist/contexts/index.mjs",
54
54
  "require": "./dist/contexts/index.js"
55
55
  },
56
+ "./date-locale": {
57
+ "types": "./dist/date-locale/index.d.ts",
58
+ "import": "./dist/date-locale/index.mjs",
59
+ "require": "./dist/date-locale/index.js"
60
+ },
56
61
  "./contexts/styles.css": "./dist/contexts/index.css",
57
62
  "./blocknote/server": {
58
63
  "types": "./dist/components/editors/server-entry.d.ts",
@@ -3,7 +3,8 @@
3
3
  import { isValid, parse } from "date-fns";
4
4
  import { Calendar as CalendarIcon, CircleXIcon } from "lucide-react";
5
5
  import { ReactElement, useMemo, useState } from "react";
6
- import { useI18nDateFnsLocale, useI18nLocale } from "../../i18n";
6
+ import { useDateLocale } from "../../contexts/DateLocaleContext";
7
+ import { useI18nDateFnsLocale } from "../../i18n";
7
8
  import {
8
9
  Calendar,
9
10
  Input,
@@ -48,8 +49,9 @@ export const DatePickerPopover = ({
48
49
  align = "start",
49
50
  className,
50
51
  }: DatePickerPopoverProps) => {
51
- const locale = useI18nLocale();
52
+ const { locale } = useDateLocale();
52
53
  const dateFnsLocale = useI18nDateFnsLocale();
54
+ const weekStartsOn = (dateFnsLocale?.options?.weekStartsOn ?? 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
53
55
  const [isOpen, setIsOpen] = useState(false);
54
56
  const [displayMonth, setDisplayMonth] = useState<Date>(() => value || new Date());
55
57
 
@@ -239,7 +241,7 @@ export const DatePickerPopover = ({
239
241
  onSelect={handleCalendarSelect}
240
242
  disabled={(date) => (minDate && date < minDate ? true : false)}
241
243
  locale={dateFnsLocale}
242
- weekStartsOn={1}
244
+ weekStartsOn={weekStartsOn}
243
245
  month={displayMonth}
244
246
  onMonthChange={setDisplayMonth}
245
247
  />
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
- import { format } from "date-fns";
4
3
  import { CalendarIcon } from "lucide-react";
5
4
  import { useTranslations } from "next-intl";
6
5
  import { useEffect, useState } from "react";
7
6
  import { DateRange } from "react-day-picker";
7
+ import { useDateFormatter } from "../../contexts/DateLocaleContext";
8
8
  import { useI18nDateFnsLocale } from "../../i18n";
9
9
  import { Button, Calendar, Popover, PopoverContent, PopoverTrigger } from "../../shadcnui";
10
10
  import { cn } from "../../utils";
@@ -50,7 +50,9 @@ type DateRangeSelectorProps = {
50
50
 
51
51
  export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviousMonth }: DateRangeSelectorProps) {
52
52
  const t = useTranslations();
53
+ const { format } = useDateFormatter();
53
54
  const dateFnsLocale = useI18nDateFnsLocale();
55
+ const weekStartsOn = (dateFnsLocale?.options?.weekStartsOn ?? 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
54
56
  const [date, setDate] = useState<DateRange | undefined>(
55
57
  avoidSettingDates
56
58
  ? undefined
@@ -100,11 +102,10 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
100
102
  {date?.from ? (
101
103
  date.to ? (
102
104
  <>
103
- {format(date.from, "d MMM yyyy", { locale: dateFnsLocale })} -{" "}
104
- {format(date.to, "d MMM yyyy", { locale: dateFnsLocale })}
105
+ {format(date.from, "dateMedium")} - {format(date.to, "dateMedium")}
105
106
  </>
106
107
  ) : (
107
- format(date.from, "d MMM yyyy", { locale: dateFnsLocale })
108
+ format(date.from, "dateMedium")
108
109
  )
109
110
  ) : (
110
111
  <span>{t("ui.labels.pick_a_date")}</span>
@@ -130,11 +131,10 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
130
131
  {date?.from ? (
131
132
  date.to ? (
132
133
  <>
133
- {format(date.from, "d MMM yyyy", { locale: dateFnsLocale })} -{" "}
134
- {format(date.to, "d MMM yyyy", { locale: dateFnsLocale })}
134
+ {format(date.from, "dateMedium")} - {format(date.to, "dateMedium")}
135
135
  </>
136
136
  ) : (
137
- format(date.from, "d MMM yyyy", { locale: dateFnsLocale })
137
+ format(date.from, "dateMedium")
138
138
  )
139
139
  ) : (
140
140
  <span>{t("ui.labels.pick_a_date")}</span>
@@ -151,6 +151,8 @@ export function DateRangeSelector({ onDateChange, avoidSettingDates, showPreviou
151
151
  selected={date}
152
152
  onSelect={handleSelect}
153
153
  numberOfMonths={2}
154
+ locale={dateFnsLocale}
155
+ weekStartsOn={weekStartsOn}
154
156
  />
155
157
  <Button
156
158
  variant="secondary"
@@ -3,7 +3,8 @@
3
3
  import { isValid, parse } from "date-fns";
4
4
  import { Calendar as CalendarIcon, CircleXIcon } from "lucide-react";
5
5
  import { useMemo, useState } from "react";
6
- import { useI18nDateFnsLocale, useI18nLocale, useI18nTranslations } from "../../i18n";
6
+ import { useDateLocale } from "../../contexts/DateLocaleContext";
7
+ import { useI18nDateFnsLocale, useI18nTranslations } from "../../i18n";
7
8
  import {
8
9
  Button,
9
10
  Calendar,
@@ -42,8 +43,9 @@ export function FormDate({
42
43
  futureYears?: number;
43
44
  }) {
44
45
  const t = useI18nTranslations();
45
- const locale = useI18nLocale();
46
+ const { locale } = useDateLocale();
46
47
  const dateFnsLocale = useI18nDateFnsLocale();
48
+ const weekStartsOn = (dateFnsLocale?.options?.weekStartsOn ?? 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
47
49
  const [open, setOpen] = useState<boolean>(false);
48
50
  const [displayMonth, setDisplayMonth] = useState<Date>(() => {
49
51
  const currentValue = form.getValues(id);
@@ -162,7 +164,7 @@ export function FormDate({
162
164
  }}
163
165
  disabled={(date) => (minDate && date < minDate ? true : false)}
164
166
  locale={dateFnsLocale}
165
- weekStartsOn={1}
167
+ weekStartsOn={weekStartsOn}
166
168
  month={displayMonth}
167
169
  onMonthChange={setDisplayMonth}
168
170
  startMonth={new Date(1900, 0)}
@@ -2,7 +2,8 @@
2
2
 
3
3
  import { Calendar as CalendarIcon, CircleXIcon } from "lucide-react";
4
4
  import { useMemo, useState } from "react";
5
- import { useI18nDateFnsLocale, useI18nLocale, useI18nTranslations } from "../../i18n";
5
+ import { useDateFormatter } from "../../contexts/DateLocaleContext";
6
+ import { useI18nDateFnsLocale, useI18nTranslations } from "../../i18n";
6
7
  import {
7
8
  Button,
8
9
  Calendar,
@@ -18,6 +19,7 @@ import {
18
19
  } from "../../shadcnui";
19
20
  import { cn } from "../../utils";
20
21
  import { FormFieldWrapper } from "./FormFieldWrapper";
22
+ import { DayPeriod, dayPeriodLabels, from24Hour, to24Hour } from "./time-cycle";
21
23
 
22
24
  // Unwrapped rounding: setMinutes(60) carries into the hour, so 10:58 becomes 11:00
23
25
  function snapToFiveMinutes(date: Date): Date {
@@ -57,30 +59,20 @@ export function FormDateTime({
57
59
  }) {
58
60
  const [open, setOpen] = useState<boolean>(false);
59
61
  const t = useI18nTranslations();
60
- const locale = useI18nLocale();
62
+ const { format, locale, twelveHourClock } = useDateFormatter();
61
63
  const dateFnsLocale = useI18nDateFnsLocale();
64
+ const weekStartsOn = (dateFnsLocale?.options?.weekStartsOn ?? 1) as 0 | 1 | 2 | 3 | 4 | 5 | 6;
62
65
 
63
66
  const [displayMonth, setDisplayMonth] = useState<Date>(() => {
64
67
  const currentValue = form.getValues(id);
65
68
  return currentValue || defaultMonth || reference?.date || new Date();
66
69
  });
67
70
 
68
- // Locale-aware date-time formatter
69
- const dateTimeFormatter = useMemo(
70
- () =>
71
- new Intl.DateTimeFormat(locale, {
72
- year: "numeric",
73
- month: "long",
74
- day: "numeric",
75
- hour: "2-digit",
76
- minute: "2-digit",
77
- hour12: false,
78
- }),
79
- [locale],
80
- );
71
+ // Format date-time for display; the clock (12/24-hour) follows the resolved locale
72
+ const formatDateTime = (date: Date): string => format(date, "dateTimeLong");
81
73
 
82
- // Format date-time for display
83
- const formatDateTime = (date: Date): string => dateTimeFormatter.format(date);
74
+ // AM/PM labels from Intl, only needed on a 12-hour locale
75
+ const periodLabels = useMemo(() => dayPeriodLabels(locale), [locale]);
84
76
 
85
77
  // An empty field starts its time selects at the reference "now" when one is given
86
78
  // Snapped as an instant (not by wrapping minutes) so 10:58 starts at 11:00
@@ -100,13 +92,27 @@ export function FormDateTime({
100
92
  marked: "[&>button]:underline [&>button]:decoration-2 [&>button]:underline-offset-4",
101
93
  };
102
94
 
103
- const hoursOptions = Array.from({ length: 24 }, (_, i) => {
104
- const hour = i;
105
- return {
106
- value: hour,
107
- label: hour.toString().padStart(2, "0"),
108
- };
109
- });
95
+ // 12-hour locales list 1–12 (plus an AM/PM select); 24-hour locales list 0–23
96
+ const hoursOptions = twelveHourClock
97
+ ? Array.from({ length: 12 }, (_, i) => {
98
+ const hour = i + 1;
99
+ return {
100
+ value: hour,
101
+ label: hour.toString(),
102
+ };
103
+ })
104
+ : Array.from({ length: 24 }, (_, i) => {
105
+ const hour = i;
106
+ return {
107
+ value: hour,
108
+ label: hour.toString().padStart(2, "0"),
109
+ };
110
+ });
111
+
112
+ const periodOptions: { value: DayPeriod; label: string }[] = [
113
+ { value: "am", label: periodLabels.am },
114
+ { value: "pm", label: periodLabels.pm },
115
+ ];
110
116
 
111
117
  const minutesOptions = Array.from({ length: 12 }, (_, i) => {
112
118
  const minute = i * 5;
@@ -118,6 +124,7 @@ export function FormDateTime({
118
124
 
119
125
  const hoursItems = Object.fromEntries(hoursOptions.map((option) => [String(option.value), option.label]));
120
126
  const minutesItems = Object.fromEntries(minutesOptions.map((option) => [String(option.value), option.label]));
127
+ const periodItems = Object.fromEntries(periodOptions.map((option) => [option.value, option.label]));
121
128
 
122
129
  function roundToNearestFiveMinutes(minutes: number): number {
123
130
  return (Math.round(minutes / 5) * 5) % 60;
@@ -133,6 +140,11 @@ export function FormDateTime({
133
140
  setSelectedMinutes(referenceDate.getMinutes());
134
141
  };
135
142
 
143
+ // The time the selects show: the field's own time, else the pending selection (0–23 / 0–55)
144
+ const currentHours = (value: Date | undefined): number => (value ? new Date(value).getHours() : selectedHours);
145
+ const currentMinutes = (value: Date | undefined): number =>
146
+ value ? roundToNearestFiveMinutes(new Date(value).getMinutes()) : selectedMinutes;
147
+
136
148
  const handleTimeChange = (hours: number, minutes: number) => {
137
149
  const currentDate = form.getValues(id);
138
150
  if (currentDate) {
@@ -205,7 +217,7 @@ export function FormDateTime({
205
217
  }}
206
218
  disabled={(date) => (minDate && date < minDate ? true : false)}
207
219
  locale={dateFnsLocale}
208
- weekStartsOn={1}
220
+ weekStartsOn={weekStartsOn}
209
221
  startMonth={new Date(1900, 0)}
210
222
  endMonth={new Date(new Date().getFullYear() + 10, 11)}
211
223
  modifiers={dayModifiers}
@@ -228,20 +240,20 @@ export function FormDateTime({
228
240
  <Label htmlFor="hours-select">{t(`common.hours`)}</Label>
229
241
  <Select
230
242
  items={hoursItems}
231
- value={String(field.value ? new Date(field.value).getHours() : selectedHours)}
243
+ value={String(
244
+ twelveHourClock ? from24Hour(currentHours(field.value)).hour12 : currentHours(field.value),
245
+ )}
232
246
  onValueChange={(value) => {
233
247
  if (!value) return;
234
- const hours = parseInt(value);
248
+ const picked = parseInt(value);
249
+ const hours = twelveHourClock
250
+ ? to24Hour(picked, from24Hour(currentHours(field.value)).period)
251
+ : picked;
235
252
  setSelectedHours(hours);
236
- handleTimeChange(
237
- hours,
238
- field.value
239
- ? roundToNearestFiveMinutes(new Date(field.value).getMinutes())
240
- : selectedMinutes,
241
- );
253
+ handleTimeChange(hours, currentMinutes(field.value));
242
254
  }}
243
255
  >
244
- <SelectTrigger id="hours-select" className="w-[70px]">
256
+ <SelectTrigger id="hours-select" data-testid="form-datetime-hours" className="w-[70px]">
245
257
  <SelectValue />
246
258
  </SelectTrigger>
247
259
  <SelectContent>
@@ -258,17 +270,15 @@ export function FormDateTime({
258
270
  <Label htmlFor="minutes-select">{t(`common.minutes`)}</Label>
259
271
  <Select
260
272
  items={minutesItems}
261
- value={String(
262
- field.value ? roundToNearestFiveMinutes(new Date(field.value).getMinutes()) : selectedMinutes,
263
- )}
273
+ value={String(currentMinutes(field.value))}
264
274
  onValueChange={(value) => {
265
275
  if (!value) return;
266
276
  const minutes = parseInt(value);
267
277
  setSelectedMinutes(minutes);
268
- handleTimeChange(field.value ? new Date(field.value).getHours() : selectedHours, minutes);
278
+ handleTimeChange(currentHours(field.value), minutes);
269
279
  }}
270
280
  >
271
- <SelectTrigger id="minutes-select" className="w-[70px]">
281
+ <SelectTrigger id="minutes-select" data-testid="form-datetime-minutes" className="w-[70px]">
272
282
  <SelectValue />
273
283
  </SelectTrigger>
274
284
  <SelectContent>
@@ -280,6 +290,32 @@ export function FormDateTime({
280
290
  </SelectContent>
281
291
  </Select>
282
292
  </div>
293
+ {twelveHourClock && (
294
+ <div className="flex flex-col space-y-2">
295
+ <Label htmlFor="period-select">{t(`common.dayPeriod`)}</Label>
296
+ <Select
297
+ items={periodItems}
298
+ value={from24Hour(currentHours(field.value)).period}
299
+ onValueChange={(value) => {
300
+ if (!value) return;
301
+ const hours = to24Hour(from24Hour(currentHours(field.value)).hour12, value as DayPeriod);
302
+ setSelectedHours(hours);
303
+ handleTimeChange(hours, currentMinutes(field.value));
304
+ }}
305
+ >
306
+ <SelectTrigger id="period-select" data-testid="form-datetime-period" className="w-[70px]">
307
+ <SelectValue />
308
+ </SelectTrigger>
309
+ <SelectContent>
310
+ {periodOptions.map((option) => (
311
+ <SelectItem key={option.value} value={option.value}>
312
+ {option.label}
313
+ </SelectItem>
314
+ ))}
315
+ </SelectContent>
316
+ </Select>
317
+ </div>
318
+ )}
283
319
  </div>
284
320
  <div className="mt-2 flex flex-row gap-x-2">
285
321
  {allowEmpty !== false && (
@@ -0,0 +1,37 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { dayPeriodLabels, from24Hour, to24Hour } from "../time-cycle";
3
+
4
+ describe("to24Hour / from24Hour round-trip", () => {
5
+ it("maps 12-hour values around midnight and noon", () => {
6
+ expect(to24Hour(12, "am")).toBe(0);
7
+ expect(to24Hour(12, "pm")).toBe(12);
8
+ expect(to24Hour(11, "pm")).toBe(23);
9
+ expect(to24Hour(1, "am")).toBe(1);
10
+ });
11
+
12
+ it("maps 24-hour values to 12-hour values", () => {
13
+ expect(from24Hour(0)).toEqual({ hour12: 12, period: "am" });
14
+ expect(from24Hour(13)).toEqual({ hour12: 1, period: "pm" });
15
+ expect(from24Hour(12)).toEqual({ hour12: 12, period: "pm" });
16
+ });
17
+
18
+ it("round-trips every hour of the day", () => {
19
+ for (let hour = 0; hour < 24; hour++) {
20
+ const { hour12, period } = from24Hour(hour);
21
+ expect(to24Hour(hour12, period)).toBe(hour);
22
+ }
23
+ });
24
+ });
25
+
26
+ describe("dayPeriodLabels", () => {
27
+ it("reads the en-US labels from Intl", () => {
28
+ expect(dayPeriodLabels("en-US")).toEqual({ am: "AM", pm: "PM" });
29
+ });
30
+
31
+ it("returns two non-empty, different labels for it", () => {
32
+ const labels = dayPeriodLabels("it");
33
+ expect(labels.am.length).toBeGreaterThan(0);
34
+ expect(labels.pm.length).toBeGreaterThan(0);
35
+ expect(labels.am).not.toBe(labels.pm);
36
+ });
37
+ });
@@ -0,0 +1,27 @@
1
+ export type DayPeriod = "am" | "pm";
2
+
3
+ /** 12-hour clock value + period → 0–23 (12 am → 0, 12 pm → 12). */
4
+ export function to24Hour(hour12: number, period: DayPeriod): number {
5
+ const base = hour12 % 12;
6
+ return period === "pm" ? base + 12 : base;
7
+ }
8
+
9
+ /** 0–23 → 12-hour clock value + period (0 → 12 am, 12 → 12 pm). */
10
+ export function from24Hour(hour24: number): { hour12: number; period: DayPeriod } {
11
+ const period: DayPeriod = hour24 >= 12 ? "pm" : "am";
12
+ const hour12 = hour24 % 12 === 0 ? 12 : hour24 % 12;
13
+ return { hour12, period };
14
+ }
15
+
16
+ function dayPeriodOf(formatter: Intl.DateTimeFormat, date: Date): string {
17
+ return formatter.formatToParts(date).find((part) => part.type === "dayPeriod")?.value ?? "";
18
+ }
19
+
20
+ /** The locale's own AM/PM labels, read from Intl (never hard-coded). */
21
+ export function dayPeriodLabels(locale: string): { am: string; pm: string } {
22
+ const formatter = new Intl.DateTimeFormat(locale, { hour: "numeric", hourCycle: "h12" });
23
+ return {
24
+ am: dayPeriodOf(formatter, new Date(2000, 0, 1, 1, 0)),
25
+ pm: dayPeriodOf(formatter, new Date(2000, 0, 1, 13, 0)),
26
+ };
27
+ }
@@ -1,11 +1,9 @@
1
1
  import { ColumnDef } from "@tanstack/react-table";
2
- import { useI18nLocale } from "../../../i18n";
2
+ import { useDateFormatter } from "../../../contexts/DateLocaleContext";
3
3
 
4
4
  function DateCell({ date }: { date: Date }) {
5
- const locale = useI18nLocale();
6
- return (
7
- <span className="text-muted-foreground text-xs">{date.toLocaleDateString(locale, { dateStyle: "medium" })}</span>
8
- );
5
+ const { format } = useDateFormatter();
6
+ return <span className="text-muted-foreground text-xs">{format(date, "dateMedium")}</span>;
9
7
  }
10
8
 
11
9
  export const cellDate = (params: { name: string; title: string }): ColumnDef<any> => {
@@ -1,13 +1,9 @@
1
1
  import { ColumnDef } from "@tanstack/react-table";
2
- import { useI18nLocale } from "../../../i18n";
2
+ import { useDateFormatter } from "../../../contexts/DateLocaleContext";
3
3
 
4
4
  function DateTimeCell({ date }: { date: Date }) {
5
- const locale = useI18nLocale();
6
- return (
7
- <span className="text-muted-foreground text-xs">
8
- {date.toLocaleString(locale, { dateStyle: "medium", timeStyle: "short", hour12: false })}
9
- </span>
10
- );
5
+ const { format } = useDateFormatter();
6
+ return <span className="text-muted-foreground text-xs">{format(date, "dateTimeMedium")}</span>;
11
7
  }
12
8
 
13
9
  export const cellDateTime = (params: { name: string; title: string }): ColumnDef<any> => {