@carlonicora/nextjs-jsonapi 4.1.2 → 4.2.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 (285) hide show
  1. package/dist/{AssistantMessageInterface-CubxuBIJ.d.ts → AssistantMessageInterface-DHJJO_Z_.d.ts} +1 -1
  2. package/dist/{AssistantMessageInterface-sHjI2zPN.d.mts → AssistantMessageInterface-S7oyERTT.d.mts} +1 -1
  3. package/dist/{BlockNoteEditor-VOYKZOP5.mjs → BlockNoteEditor-HXAHDJA7.mjs} +34 -27
  4. package/dist/BlockNoteEditor-HXAHDJA7.mjs.map +1 -0
  5. package/dist/{BlockNoteEditor-BENYTTCZ.js → BlockNoteEditor-LLCZDFLZ.js} +50 -43
  6. package/dist/BlockNoteEditor-LLCZDFLZ.js.map +1 -0
  7. package/dist/{BlockNoteViewer-USLR4Y7C.js → BlockNoteViewer-77CCTC2U.js} +3 -3
  8. package/dist/{BlockNoteViewer-USLR4Y7C.js.map → BlockNoteViewer-77CCTC2U.js.map} +1 -1
  9. package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs → BlockNoteViewer-MDPO6BXN.mjs} +2 -2
  10. package/dist/{auth.interface-Df4vXbNz.d.mts → auth.interface-CeFCz9NU.d.mts} +1 -1
  11. package/dist/{auth.interface-BzI4onI_.d.ts → auth.interface-D1R7VVky.d.ts} +1 -1
  12. package/dist/billing/index.d.mts +40 -6
  13. package/dist/billing/index.d.ts +40 -6
  14. package/dist/billing/index.js +806 -774
  15. package/dist/billing/index.js.map +1 -1
  16. package/dist/billing/index.mjs +494 -462
  17. package/dist/billing/index.mjs.map +1 -1
  18. package/dist/{chunk-6ISF5KNQ.mjs → chunk-2VXDZRYY.mjs} +1103 -816
  19. package/dist/chunk-2VXDZRYY.mjs.map +1 -0
  20. package/dist/{chunk-4BZJ4LAB.js → chunk-4ORLPEK7.js} +136 -104
  21. package/dist/chunk-4ORLPEK7.js.map +1 -0
  22. package/dist/{chunk-ZZ6E33FO.mjs → chunk-6DRSSW5O.mjs} +36 -4
  23. package/dist/chunk-6DRSSW5O.mjs.map +1 -0
  24. package/dist/{chunk-KS6WZP2E.js → chunk-6SMJRAE3.js} +4 -4
  25. package/dist/{chunk-KS6WZP2E.js.map → chunk-6SMJRAE3.js.map} +1 -1
  26. package/dist/{chunk-WQ77CUUZ.js → chunk-GPXYQMOZ.js} +7 -7
  27. package/dist/{chunk-WQ77CUUZ.js.map → chunk-GPXYQMOZ.js.map} +1 -1
  28. package/dist/{chunk-46S3KPLU.mjs → chunk-ILFFC6BG.mjs} +2 -2
  29. package/dist/{chunk-HZVVRIJQ.mjs → chunk-IUS2RHCX.mjs} +2 -2
  30. package/dist/{chunk-F7RNBGHS.js → chunk-KJMMBUOA.js} +3 -3
  31. package/dist/{chunk-F7RNBGHS.js.map → chunk-KJMMBUOA.js.map} +1 -1
  32. package/dist/{chunk-ZO3QQ5CQ.mjs → chunk-MQDR6MEE.mjs} +2 -2
  33. package/dist/{chunk-ORU7KLCY.js → chunk-VDVIUDY5.js} +1445 -1158
  34. package/dist/chunk-VDVIUDY5.js.map +1 -0
  35. package/dist/client/index.d.mts +20 -6
  36. package/dist/client/index.d.ts +20 -6
  37. package/dist/client/index.js +6 -4
  38. package/dist/client/index.js.map +1 -1
  39. package/dist/client/index.mjs +5 -3
  40. package/dist/components/index.d.mts +59 -21
  41. package/dist/components/index.d.ts +59 -21
  42. package/dist/components/index.js +4 -4
  43. package/dist/components/index.mjs +3 -3
  44. package/dist/{config-CEAq4Ozy.d.mts → config-BP2vyddw.d.mts} +5 -0
  45. package/dist/{config-DydDHCfb.d.ts → config-BhW2QrRD.d.ts} +5 -0
  46. package/dist/contexts/index.d.mts +4 -4
  47. package/dist/contexts/index.d.ts +4 -4
  48. package/dist/contexts/index.js +4 -4
  49. package/dist/contexts/index.mjs +3 -3
  50. package/dist/core/index.d.mts +17 -9
  51. package/dist/core/index.d.ts +17 -9
  52. package/dist/core/index.js +4 -2
  53. package/dist/core/index.js.map +1 -1
  54. package/dist/core/index.mjs +3 -1
  55. package/dist/features/analytics/index.d.mts +1 -1
  56. package/dist/features/analytics/index.d.ts +1 -1
  57. package/dist/features/analytics/index.js +122 -122
  58. package/dist/features/analytics/index.mjs +4 -4
  59. package/dist/features/analytics/server.js +4 -4
  60. package/dist/features/analytics/server.mjs +2 -2
  61. package/dist/features/help/index.d.mts +1 -1
  62. package/dist/features/help/index.d.ts +1 -1
  63. package/dist/features/help/index.js +42 -40
  64. package/dist/features/help/index.js.map +1 -1
  65. package/dist/features/help/index.mjs +8 -6
  66. package/dist/features/help/index.mjs.map +1 -1
  67. package/dist/features/tokenusage/index.js +125 -125
  68. package/dist/features/tokenusage/index.mjs +4 -4
  69. package/dist/index.d.mts +7 -7
  70. package/dist/index.d.ts +7 -7
  71. package/dist/index.js +5 -3
  72. package/dist/index.js.map +1 -1
  73. package/dist/index.mjs +4 -2
  74. package/dist/{notification.interface-P_pNfzZQ.d.mts → notification.interface-BB6GPpZY.d.mts} +1 -1
  75. package/dist/{notification.interface-BNCvnVCW.d.ts → notification.interface-CmAGrijJ.d.ts} +1 -1
  76. package/dist/{s3.service-CbUa6a-8.d.ts → s3.service-C-4YpEMu.d.ts} +7 -3
  77. package/dist/{s3.service-DAp8FbuG.d.mts → s3.service-dVj-nxMl.d.mts} +7 -3
  78. package/dist/server/index.d.mts +4 -4
  79. package/dist/server/index.d.ts +4 -4
  80. package/dist/server/index.js +3 -3
  81. package/dist/server/index.mjs +2 -2
  82. package/dist/{useSocket-BHVIyigI.d.ts → useSocket-B0H9pmmH.d.ts} +1 -1
  83. package/dist/{useSocket-B9gTbuCj.d.mts → useSocket-B5RQb6v2.d.mts} +1 -1
  84. package/dist/{user.interface-CrmwJLYW.d.ts → user.interface-C0ZdsBZj.d.ts} +2 -0
  85. package/dist/{user.interface-CSaGWFbg.d.mts → user.interface-CIWGlbqM.d.mts} +2 -0
  86. package/package.json +1 -1
  87. package/src/components/containers/RoundPageContainer.tsx +3 -1
  88. package/src/components/containers/RoundPageContainerTitle.tsx +5 -2
  89. package/src/components/editors/BlockNoteEditor.tsx +31 -15
  90. package/src/components/editors/BlockNoteEditorAiActions.tsx +11 -7
  91. package/src/components/editors/__tests__/{BlockNoteEditorAiActions.test.ts → BlockNoteEditorAiActions.spec.ts} +10 -3
  92. package/src/components/errors/ErrorDetails.tsx +5 -2
  93. package/src/components/forms/CommonAssociationForm.tsx +1 -1
  94. package/src/components/forms/EntityMultiSelector.tsx +6 -2
  95. package/src/components/forms/EntitySelector.tsx +4 -1
  96. package/src/components/forms/FileUploader.tsx +3 -2
  97. package/src/components/forms/MultiFileUploader.tsx +3 -1
  98. package/src/components/navigations/Breadcrumb.tsx +3 -2
  99. package/src/components/navigations/Header.tsx +3 -1
  100. package/src/components/navigations/MobileNavigationBar.tsx +3 -1
  101. package/src/components/navigations/__tests__/Header.spec.tsx +2 -2
  102. package/src/components/tables/__tests__/ContentListTable.spec.tsx +1 -1
  103. package/src/components/tables/cells/cell.id.tsx +13 -2
  104. package/src/core/abstracts/AbstractService.ts +3 -3
  105. package/src/core/abstracts/ClientAbstractService.ts +3 -2
  106. package/src/features/assistant-action/components/ApprovalActionCard.tsx +74 -5
  107. package/src/features/assistant-action/components/__tests__/ApprovalActionCard.spec.tsx +81 -0
  108. package/src/features/assistant-message/components/parts/RelevanceMeter.tsx +4 -1
  109. package/src/features/assistant-message/components/parts/tabs/UsersTab.tsx +2 -1
  110. package/src/features/auth/components/buttons/GoogleSignInButton.tsx +4 -1
  111. package/src/features/auth/components/details/LandingComponent.tsx +1 -1
  112. package/src/features/auth/components/forms/ForgotPassword.tsx +1 -1
  113. package/src/features/auth/components/forms/Login.tsx +1 -1
  114. package/src/features/auth/components/forms/Register.tsx +1 -1
  115. package/src/features/auth/components/forms/ResetPassword.tsx +2 -2
  116. package/src/features/auth/components/two-factor/BackupCodesDialog.tsx +1 -1
  117. package/src/features/auth/components/two-factor/PasskeyButton.tsx +1 -1
  118. package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +21 -11
  119. package/src/features/billing/components/cards/CustomerInfoCard.tsx +16 -13
  120. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +33 -21
  121. package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +20 -10
  122. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +35 -22
  123. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +24 -24
  124. package/src/features/billing/components/utils/__tests__/billing-formatters.spec.ts +63 -0
  125. package/src/features/billing/components/utils/currency.ts +67 -10
  126. package/src/features/billing/components/utils/date.ts +9 -4
  127. package/src/features/billing/components/utils/format-options.ts +20 -0
  128. package/src/features/billing/components/utils/index.ts +3 -1
  129. package/src/features/billing/components/utils/useBillingFormatOptions.ts +11 -0
  130. package/src/features/billing/components/widgets/BillingAlertBanner.tsx +8 -9
  131. package/src/features/billing/contexts/PriceContext.tsx +18 -6
  132. package/src/features/billing/i18n-keys.ts +212 -0
  133. package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +8 -8
  134. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +11 -9
  135. package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +4 -4
  136. package/src/features/billing/stripe-customer/components/forms/PaymentMethodForm.tsx +9 -7
  137. package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +10 -8
  138. package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +34 -29
  139. package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +12 -9
  140. package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +9 -6
  141. package/src/features/billing/stripe-price/components/details/PriceDetails.tsx +4 -3
  142. package/src/features/billing/stripe-price/components/forms/PriceArchiver.tsx +3 -1
  143. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +3 -1
  144. package/src/features/billing/stripe-price/hooks/useStripePriceTableStructure.tsx +6 -5
  145. package/src/features/billing/stripe-promotion-code/components/PromoCodeInput.tsx +12 -7
  146. package/src/features/billing/stripe-promotion-code/data/stripe-promotion-code.service.ts +3 -1
  147. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +10 -6
  148. package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +44 -29
  149. package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +14 -11
  150. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +20 -17
  151. package/src/features/billing/stripe-subscription/components/widgets/IntervalToggle.tsx +4 -2
  152. package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +16 -7
  153. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingList.tsx +3 -1
  154. package/src/features/billing/stripe-subscription/components/widgets/ProductPricingRow.tsx +3 -1
  155. package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +12 -7
  156. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionConfirmation.tsx +11 -8
  157. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +13 -10
  158. package/src/features/billing/stripe-subscription/components/wizards/SubscriptionWizard.tsx +8 -6
  159. package/src/features/billing/stripe-subscription/components/wizards/WizardProgressIndicator.tsx +7 -4
  160. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPaymentMethod.tsx +4 -2
  161. package/src/features/billing/stripe-subscription/components/wizards/WizardStepPlanSelection.tsx +3 -1
  162. package/src/features/billing/stripe-subscription/components/wizards/WizardStepReview.tsx +37 -28
  163. package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +9 -7
  164. package/src/features/billing/stripe-subscription/hooks/useSubscriptionWizard.ts +20 -9
  165. package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +6 -6
  166. package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +24 -9
  167. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +16 -12
  168. package/src/features/company/components/details/TokenStatusIndicator.tsx +7 -5
  169. package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
  170. package/src/features/help/components/HelpArticleBody.tsx +1 -1
  171. package/src/features/help/components/HelpHeader.tsx +1 -1
  172. package/src/features/help/components/HelpSideNav.tsx +1 -1
  173. package/src/features/help/i18n-keys.ts +2 -0
  174. package/src/features/notification/components/common/NotificationErrorBoundary.tsx +17 -12
  175. package/src/features/notification/components/containers/NotificationsListContainer.tsx +4 -2
  176. package/src/features/notification/components/notifications/Notification.tsx +6 -3
  177. package/src/features/notification/contexts/NotificationContext.tsx +9 -4
  178. package/src/features/notification/contexts/__tests__/NotificationContext.spec.tsx +13 -0
  179. package/src/features/notification/hooks/useNotificationTableStructure.tsx +4 -2
  180. package/src/features/oauth/components/OAuthClientCard.tsx +13 -7
  181. package/src/features/oauth/components/OAuthClientDetail.tsx +34 -30
  182. package/src/features/oauth/components/OAuthClientForm.tsx +24 -24
  183. package/src/features/oauth/components/OAuthClientList.tsx +10 -6
  184. package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +10 -8
  185. package/src/features/oauth/components/OAuthRedirectUriInput.tsx +9 -13
  186. package/src/features/oauth/components/OAuthScopeSelector.tsx +15 -6
  187. package/src/features/oauth/components/__tests__/OAuthClientForm.spec.tsx +11 -9
  188. package/src/features/oauth/components/consent/OAuthScopeList.tsx +4 -2
  189. package/src/features/oauth/hooks/__tests__/useOAuthScopeText.spec.ts +30 -0
  190. package/src/features/oauth/hooks/index.ts +1 -0
  191. package/src/features/oauth/hooks/useOAuthClient.ts +11 -5
  192. package/src/features/oauth/hooks/useOAuthClients.ts +9 -3
  193. package/src/features/oauth/hooks/useOAuthConsent.ts +11 -5
  194. package/src/features/oauth/hooks/useOAuthScopeText.ts +33 -0
  195. package/src/features/rbac/components/RbacByRoleContainer.tsx +7 -6
  196. package/src/features/rbac/components/RbacContainer.tsx +7 -6
  197. package/src/features/rbac/contexts/RbacContext.tsx +3 -3
  198. package/src/features/user/components/containers/UserContainer.tsx +3 -1
  199. package/src/features/user/data/__tests__/user.spec.ts +66 -0
  200. package/src/features/user/data/user.interface.ts +2 -0
  201. package/src/features/user/data/user.service.ts +9 -0
  202. package/src/features/user/data/user.ts +7 -0
  203. package/src/features/waitlist/components/forms/WaitlistForm.tsx +1 -1
  204. package/src/features/waitlist/components/lists/WaitlistList.tsx +1 -1
  205. package/src/i18n/__tests__/{config.test.ts → config.spec.ts} +21 -1
  206. package/src/i18n/config.ts +13 -0
  207. package/src/i18n/index.ts +1 -0
  208. package/src/shadcnui/custom/multi-select.tsx +47 -14
  209. package/src/shadcnui/custom/multiple-selector.tsx +14 -2
  210. package/src/shadcnui/ui/__tests__/{confirm-dialog.test.tsx → confirm-dialog.spec.tsx} +1 -1
  211. package/src/shadcnui/ui/__tests__/{cursor-controls.test.tsx → cursor-controls.spec.tsx} +1 -1
  212. package/src/shadcnui/ui/__tests__/{cursor-menu-items.test.tsx → cursor-menu-items.spec.tsx} +1 -1
  213. package/src/shadcnui/ui/__tests__/{empty-state.test.tsx → empty-state.spec.tsx} +1 -1
  214. package/src/shadcnui/ui/__tests__/{spinner.test.tsx → spinner.spec.tsx} +1 -1
  215. package/src/shadcnui/ui/__tests__/use-ui-text.spec.tsx +76 -0
  216. package/src/shadcnui/ui/breadcrumb.tsx +9 -2
  217. package/src/shadcnui/ui/carousel.tsx +15 -4
  218. package/src/shadcnui/ui/dialog.tsx +15 -2
  219. package/src/shadcnui/ui/sheet.tsx +6 -1
  220. package/src/shadcnui/ui/sidebar.tsx +33 -7
  221. package/src/shadcnui/ui/spinner.tsx +6 -1
  222. package/src/shadcnui/ui/use-ui-text.ts +49 -0
  223. package/src/utils/__tests__/{date-formatter.test.ts → date-formatter.spec.ts} +24 -1
  224. package/src/utils/date-formatter.ts +16 -0
  225. package/src/utils/index.ts +1 -1
  226. package/dist/BlockNoteEditor-BENYTTCZ.js.map +0 -1
  227. package/dist/BlockNoteEditor-VOYKZOP5.mjs.map +0 -1
  228. package/dist/chunk-4BZJ4LAB.js.map +0 -1
  229. package/dist/chunk-6ISF5KNQ.mjs.map +0 -1
  230. package/dist/chunk-ORU7KLCY.js.map +0 -1
  231. package/dist/chunk-ZZ6E33FO.mjs.map +0 -1
  232. /package/dist/{BlockNoteViewer-Y2ZM4ESA.mjs.map → BlockNoteViewer-MDPO6BXN.mjs.map} +0 -0
  233. /package/dist/{chunk-46S3KPLU.mjs.map → chunk-ILFFC6BG.mjs.map} +0 -0
  234. /package/dist/{chunk-HZVVRIJQ.mjs.map → chunk-IUS2RHCX.mjs.map} +0 -0
  235. /package/dist/{chunk-ZO3QQ5CQ.mjs.map → chunk-MQDR6MEE.mjs.map} +0 -0
  236. /package/scripts/{check-rtl-classes.test.ts → check-rtl-classes.spec.ts} +0 -0
  237. /package/src/client/__tests__/{JsonApiClient.test.ts → JsonApiClient.spec.ts} +0 -0
  238. /package/src/client/__tests__/{request.test.ts → request.spec.ts} +0 -0
  239. /package/src/client/hooks/__tests__/{useJsonApiGet.test.tsx → useJsonApiGet.spec.tsx} +0 -0
  240. /package/src/client/hooks/__tests__/{useJsonApiMutation.test.tsx → useJsonApiMutation.spec.tsx} +0 -0
  241. /package/src/client/hooks/__tests__/{useRehydration.test.ts → useRehydration.spec.ts} +0 -0
  242. /package/src/components/editors/__tests__/{BlockNoteEditorMentionInlineContent.test.ts → BlockNoteEditorMentionInlineContent.spec.ts} +0 -0
  243. /package/src/components/forms/__tests__/{CommonEditorDiscardDialog.test.tsx → CommonEditorDiscardDialog.spec.tsx} +0 -0
  244. /package/src/components/forms/__tests__/{EditorSheet.test.tsx → EditorSheet.spec.tsx} +0 -0
  245. /package/src/components/forms/__tests__/{FormCheckbox.test.tsx → FormCheckbox.spec.tsx} +0 -0
  246. /package/src/components/forms/__tests__/{FormDate.test.tsx → FormDate.spec.tsx} +0 -0
  247. /package/src/components/forms/__tests__/{FormInput.test.tsx → FormInput.spec.tsx} +0 -0
  248. /package/src/components/forms/__tests__/{FormSelect.test.tsx → FormSelect.spec.tsx} +0 -0
  249. /package/src/components/forms/__tests__/{useEditorDialog.test.ts → useEditorDialog.spec.ts} +0 -0
  250. /package/src/components/grids/__tests__/{ContentListGrid.test.tsx → ContentListGrid.spec.tsx} +0 -0
  251. /package/src/components/tables/__tests__/{ContentListTable.test.tsx → ContentListTable.rendering.spec.tsx} +0 -0
  252. /package/src/contexts/__tests__/{DirectionContext.test.tsx → DirectionContext.spec.tsx} +0 -0
  253. /package/src/contexts/__tests__/{SessionContext.test.tsx → SessionContext.spec.tsx} +0 -0
  254. /package/src/contexts/__tests__/{SocketContext.session.test.tsx → SocketContext.session.spec.tsx} +0 -0
  255. /package/src/core/abstracts/__tests__/{AbstractService.locale.test.ts → AbstractService.locale.spec.ts} +0 -0
  256. /package/src/core/endpoint/__tests__/{EndpointCreator.test.ts → EndpointCreator.spec.ts} +0 -0
  257. /package/src/core/factories/__tests__/{JsonApiDataFactory.test.ts → JsonApiDataFactory.spec.ts} +0 -0
  258. /package/src/core/factories/__tests__/{RehydrationFactory.test.ts → RehydrationFactory.spec.ts} +0 -0
  259. /package/src/core/registry/__tests__/{DataClassRegistry.test.ts → DataClassRegistry.spec.ts} +0 -0
  260. /package/src/core/registry/__tests__/{ModuleRegistrar.test.ts → ModuleRegistrar.spec.ts} +0 -0
  261. /package/src/core/registry/__tests__/{viewportStore.test.ts → viewportStore.spec.ts} +0 -0
  262. /package/src/core/utils/__tests__/{translateResponse.test.ts → translateResponse.spec.ts} +0 -0
  263. /package/src/features/ai-connection/data/__tests__/{ai-connection.test.ts → ai-connection.spec.ts} +0 -0
  264. /package/src/features/company/data/{company.test.ts → company.spec.ts} +0 -0
  265. /package/src/features/company/hooks/{useSubscriptionStatus.test.ts → useSubscriptionStatus.spec.ts} +0 -0
  266. /package/src/hooks/__tests__/{useDataListRetriever.test.ts → useDataListRetriever.spec.ts} +0 -0
  267. /package/src/hooks/__tests__/{useDebounce.test.ts → useDebounce.spec.ts} +0 -0
  268. /package/src/hooks/__tests__/{useSocket.render.test.ts → useSocket.render.spec.ts} +0 -0
  269. /package/src/lib/__tests__/{direction.test.ts → direction.spec.ts} +0 -0
  270. /package/src/next-config/__tests__/{createNextConfig.test.ts → createNextConfig.spec.ts} +0 -0
  271. /package/src/server/__tests__/{JsonApiServer.test.ts → JsonApiServer.spec.ts} +0 -0
  272. /package/src/server/__tests__/{ServerSession.read.test.ts → ServerSession.read.spec.ts} +0 -0
  273. /package/src/server/__tests__/{cache.test.ts → cache.spec.ts} +0 -0
  274. /package/src/server/__tests__/{cachedGet.test.ts → cachedGet.spec.ts} +0 -0
  275. /package/src/server/__tests__/{request.test.ts → request.spec.ts} +0 -0
  276. /package/src/shadcnui/ui/__tests__/{sidebar.menu-skeleton.test.tsx → sidebar.menu-skeleton.spec.tsx} +0 -0
  277. /package/src/shadcnui/ui/__tests__/{sidebar.session.test.tsx → sidebar.session.spec.tsx} +0 -0
  278. /package/src/unified/__tests__/{JsonApiGet.cache.test.ts → JsonApiGet.cache.spec.ts} +0 -0
  279. /package/src/unified/__tests__/{JsonApiRequest.test.ts → JsonApiRequest.spec.ts} +0 -0
  280. /package/src/utils/__tests__/{exists.test.ts → exists.spec.ts} +0 -0
  281. /package/src/utils/__tests__/{italian-validators.test.ts → italian-validators.spec.ts} +0 -0
  282. /package/src/utils/__tests__/{map-thenable.test.ts → map-thenable.spec.ts} +0 -0
  283. /package/src/utils/__tests__/{use-mobile.test.tsx → use-mobile.spec.tsx} +0 -0
  284. /package/src/utils/{cn.test.ts → cn.spec.ts} +0 -0
  285. /package/src/utils/{isRemoteImageSrc.test.ts → isRemoteImageSrc.spec.ts} +0 -0
@@ -18,6 +18,7 @@ import {
18
18
  } from "../ui/command";
19
19
  import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
20
20
  import { Separator } from "../ui/separator";
21
+ import { useUiText } from "../ui/use-ui-text";
21
22
 
22
23
  /**
23
24
  * Variants for the multi-select component to handle different styles.
@@ -91,10 +92,25 @@ interface MultiSelectProps
91
92
 
92
93
  /**
93
94
  * Placeholder text to be displayed when no values are selected.
94
- * Optional, defaults to "Select options".
95
+ * Optional, defaults to `ui.labels.select_options`, else "Select options".
95
96
  */
96
97
  placeholder?: string;
97
98
 
99
+ /** Placeholder of the search input. Defaults to `ui.search.placeholder_global`, else "Search...". */
100
+ searchPlaceholder?: string;
101
+
102
+ /** Text of the select-all row. Defaults to `ui.labels.select_all`, else "(Select All)". */
103
+ selectAllLabel?: string;
104
+
105
+ /** Text of the clear row. Defaults to `ui.buttons.clear`, else "Clear". */
106
+ clearLabel?: string;
107
+
108
+ /** Text of the close row. Defaults to `ui.labels.close`, else "Close". */
109
+ closeLabel?: string;
110
+
111
+ /** Text of the overflow badge. Defaults to `ui.labels.more_count`, else "+ {count} more". */
112
+ formatMoreLabel?: (count: number) => string;
113
+
98
114
  /**
99
115
  * Animation duration in seconds for the visual effects (e.g., bouncing badges).
100
116
  * Optional, defaults to 0 (no animation).
@@ -134,13 +150,13 @@ interface MultiSelectProps
134
150
 
135
151
  /**
136
152
  * Text to display when loading.
137
- * Optional, defaults to "Searching...".
153
+ * Optional, defaults to `ui.labels.searching`, else "Searching...".
138
154
  */
139
155
  loadingText?: string;
140
156
 
141
157
  /**
142
158
  * Text to display when no results are found.
143
- * Optional, defaults to "No results found.".
159
+ * Optional, defaults to `ui.search.no_results_found`, else "No results found.".
144
160
  */
145
161
  emptyText?: string;
146
162
 
@@ -166,21 +182,38 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
166
182
  variant,
167
183
  defaultValue = [],
168
184
  value,
169
- placeholder = "Select options",
185
+ placeholder: placeholderProp,
170
186
  animation = 0,
171
187
  maxCount = 3,
172
188
  modalPopover = false,
173
189
  className,
174
190
  onSearchChange,
175
191
  loading = false,
176
- loadingText = "Searching...",
177
- emptyText = "No results found.",
192
+ loadingText: loadingTextProp,
193
+ emptyText: emptyTextProp,
194
+ searchPlaceholder: searchPlaceholderProp,
195
+ selectAllLabel: selectAllLabelProp,
196
+ clearLabel: clearLabelProp,
197
+ closeLabel: closeLabelProp,
198
+ formatMoreLabel,
178
199
  hideSelectAll = false,
179
200
  isOptionSelected,
180
201
  ...props
181
202
  },
182
203
  _ref,
183
204
  ) => {
205
+ // Chrome text: an explicit prop wins, else the app's translation, else English.
206
+ const uiText = useUiText();
207
+ const placeholder = placeholderProp ?? uiText("ui.labels.select_options", "Select options");
208
+ const loadingText = loadingTextProp ?? uiText("ui.labels.searching", "Searching...");
209
+ const emptyText = emptyTextProp ?? uiText("ui.search.no_results_found", "No results found.");
210
+ const searchPlaceholder = searchPlaceholderProp ?? uiText("ui.search.placeholder_global", "Search...");
211
+ const selectAllLabel = selectAllLabelProp ?? uiText("ui.labels.select_all", "(Select All)");
212
+ const clearLabel = clearLabelProp ?? uiText("ui.buttons.clear", "Clear");
213
+ const closeLabel = closeLabelProp ?? uiText("ui.labels.close", "Close");
214
+ const moreLabel = (count: number) =>
215
+ formatMoreLabel ? formatMoreLabel(count) : uiText("ui.labels.more_count", "+ {count} more", { count });
216
+
184
217
  // Using internal state only if value prop is not provided (uncontrolled mode)
185
218
  const [internalSelectedValues, setInternalSelectedValues] = React.useState<string[]>(defaultValue);
186
219
 
@@ -331,7 +364,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
331
364
  )}
332
365
  style={{ animationDuration: `${animation}s` }}
333
366
  >
334
- {`+ ${selectedValues.length - maxCount} more`}
367
+ {moreLabel(selectedValues.length - maxCount)}
335
368
  <XCircle
336
369
  data-remove-button
337
370
  className="ms-2 h-4 w-4 cursor-pointer"
@@ -381,7 +414,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
381
414
  <Command filter={commandFilterByKeywords}>
382
415
  <CommandInput
383
416
  autoFocus
384
- placeholder="Search..."
417
+ placeholder={searchPlaceholder}
385
418
  onKeyDown={handleInputKeyDown}
386
419
  onValueChange={onSearchChange}
387
420
  />
@@ -397,7 +430,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
397
430
  <CommandItem
398
431
  key="all"
399
432
  value={SELECT_ALL_VALUE}
400
- keywords={["(Select All)"]}
433
+ keywords={[selectAllLabel]}
401
434
  onSelect={toggleAll}
402
435
  className="cursor-pointer hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
403
436
  >
@@ -411,7 +444,7 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
411
444
  >
412
445
  <CheckIcon className="h-4 w-4" />
413
446
  </div>
414
- <span>(Select All)</span>
447
+ <span>{selectAllLabel}</span>
415
448
  </CommandItem>
416
449
  )}
417
450
  {options.map((option) => {
@@ -449,22 +482,22 @@ export const MultiSelect = React.forwardRef<HTMLButtonElement, MultiSelectProps>
449
482
  <>
450
483
  <CommandItem
451
484
  value={CLEAR_VALUE}
452
- keywords={["Clear"]}
485
+ keywords={[clearLabel]}
453
486
  onSelect={handleClear}
454
487
  className="flex-1 cursor-pointer justify-center"
455
488
  >
456
- Clear
489
+ {clearLabel}
457
490
  </CommandItem>
458
491
  <Separator orientation="vertical" className="flex h-full min-h-6" />
459
492
  </>
460
493
  )}
461
494
  <CommandItem
462
495
  value={CLOSE_VALUE}
463
- keywords={["Close"]}
496
+ keywords={[closeLabel]}
464
497
  onSelect={() => setIsPopoverOpen(false)}
465
498
  className="max-w-full flex-1 cursor-pointer justify-center hover:bg-muted data-selected:hover:bg-muted bg-transparent data-selected:bg-transparent"
466
499
  >
467
- Close
500
+ {closeLabel}
468
501
  </CommandItem>
469
502
  </div>
470
503
  </CommandGroup>
@@ -9,6 +9,7 @@ import { forwardRef, useEffect } from "react";
9
9
  import { cn } from "../../utils/cn";
10
10
  import { Badge } from "../ui/badge";
11
11
  import { Command, CommandGroup, CommandItem, CommandList, commandFilterByKeywords } from "../ui/command";
12
+ import { useUiText } from "../ui/use-ui-text";
12
13
 
13
14
  export interface Option {
14
15
  value: string;
@@ -58,6 +59,10 @@ export interface MultipleSelectorProps {
58
59
  onMaxSelected?: (maxLimit: number) => void;
59
60
  /** Hide the placeholder when there are options selected. */
60
61
  hidePlaceholderWhenSelected?: boolean;
62
+ /** Text of the creatable row. Defaults to `ui.labels.create_option`, else `Create "{value}"`. */
63
+ formatCreateLabel?: (value: string) => string;
64
+ /** Text of the overflow badge. Defaults to `ui.labels.more_count`, else "+{count} more". */
65
+ formatMoreLabel?: (count: number) => string;
61
66
  disabled?: boolean;
62
67
  /** Group the options base on provided key. */
63
68
  groupBy?: string;
@@ -204,6 +209,8 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
204
209
  maxSelected = Number.MAX_SAFE_INTEGER,
205
210
  onMaxSelected,
206
211
  hidePlaceholderWhenSelected,
212
+ formatCreateLabel,
213
+ formatMoreLabel,
207
214
  disabled,
208
215
  groupBy,
209
216
  className,
@@ -220,6 +227,7 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
220
227
  }: MultipleSelectorProps,
221
228
  ref: React.Ref<MultipleSelectorRef>,
222
229
  ) => {
230
+ const uiText = useUiText();
223
231
  const inputRef = React.useRef<HTMLInputElement>(null);
224
232
  const containerRef = React.useRef<HTMLDivElement>(null);
225
233
  const [open, setOpen] = React.useState(false);
@@ -431,7 +439,9 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
431
439
  onChange?.(newOptions);
432
440
  }}
433
441
  >
434
- {`Create "${inputValue}"`}
442
+ {formatCreateLabel
443
+ ? formatCreateLabel(inputValue)
444
+ : uiText("ui.labels.create_option", 'Create "{value}"', { value: inputValue })}
435
445
  </CommandItem>
436
446
  );
437
447
 
@@ -547,7 +557,9 @@ const MultipleSelector = React.forwardRef<MultipleSelectorRef, MultipleSelectorP
547
557
  })}
548
558
  {hiddenCount > 0 && (
549
559
  <Badge className={cn("bg-transparent text-foreground hover:bg-transparent", badgeClassName)}>
550
- {`+${hiddenCount} more`}
560
+ {formatMoreLabel
561
+ ? formatMoreLabel(hiddenCount)
562
+ : uiText("ui.labels.more_count", "+{count} more", { count: hiddenCount })}
551
563
  </Badge>
552
564
  )}
553
565
  <CommandPrimitive.Input
@@ -1,4 +1,4 @@
1
- // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/confirm-dialog.test.tsx
1
+ // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/confirm-dialog.spec.tsx
2
2
  import { describe, expect, it, vi } from "vitest";
3
3
  import { render, screen } from "@testing-library/react";
4
4
  import userEvent from "@testing-library/user-event";
@@ -1,4 +1,4 @@
1
- // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/cursor-controls.test.tsx
1
+ // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/cursor-controls.spec.tsx
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { render, screen } from "@testing-library/react";
4
4
 
@@ -1,4 +1,4 @@
1
- // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/cursor-menu-items.test.tsx
1
+ // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/cursor-menu-items.spec.tsx
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { render, screen } from "@testing-library/react";
4
4
 
@@ -1,4 +1,4 @@
1
- // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/empty-state.test.tsx
1
+ // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/empty-state.spec.tsx
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { render, screen } from "@testing-library/react";
4
4
  import { InboxIcon } from "lucide-react";
@@ -1,4 +1,4 @@
1
- // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/spinner.test.tsx
1
+ // packages/nextjs-jsonapi/src/shadcnui/ui/__tests__/spinner.spec.tsx
2
2
  import { describe, expect, it } from "vitest";
3
3
  import { render, screen } from "@testing-library/react";
4
4
  import { Spinner } from "../spinner";
@@ -0,0 +1,76 @@
1
+ import { render, renderHook, screen } from "@testing-library/react";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { configureI18n, type I18nConfig } from "../../../i18n/config";
4
+ import { Spinner } from "../spinner";
5
+ import { useUiText } from "../use-ui-text";
6
+
7
+ const catalogue: Record<string, string> = {
8
+ "ui.labels.loading": "Caricamento",
9
+ "ui.labels.more_count": "+{count} altri",
10
+ };
11
+
12
+ /** A next-intl-shaped `t`: `has` probes, missing keys come back as the key path. */
13
+ function useCatalogueTranslations() {
14
+ const t = (key: string, values?: Record<string, unknown>) =>
15
+ (catalogue[key] ?? key).replace(/\{(\w+)\}/g, (match, name: string) =>
16
+ values && name in values ? String(values[name]) : match,
17
+ );
18
+ (t as unknown as { has: (key: string) => boolean }).has = (key: string) => key in catalogue;
19
+ return t;
20
+ }
21
+
22
+ function configure(useTranslations: I18nConfig["useTranslations"]) {
23
+ configureI18n({ useTranslations } as unknown as I18nConfig);
24
+ }
25
+
26
+ afterEach(() => {
27
+ configureI18n(null as unknown as I18nConfig);
28
+ });
29
+
30
+ describe("useUiText", () => {
31
+ it("returns the English fallback when i18n is not configured", () => {
32
+ const { result } = renderHook(() => useUiText());
33
+ expect(result.current("ui.labels.close", "Close")).toBe("Close");
34
+ expect(result.current("ui.labels.more_count", "+ {count} more", { count: 2 })).toBe("+ 2 more");
35
+ });
36
+
37
+ it("returns the app's translation when the key exists", () => {
38
+ configure(useCatalogueTranslations);
39
+ const { result } = renderHook(() => useUiText());
40
+ expect(result.current("ui.labels.loading", "Loading")).toBe("Caricamento");
41
+ expect(result.current("ui.labels.more_count", "+ {count} more", { count: 3 })).toBe("+3 altri");
42
+ });
43
+
44
+ it("returns the English fallback when the app lacks the key", () => {
45
+ configure(useCatalogueTranslations);
46
+ const { result } = renderHook(() => useUiText());
47
+ expect(result.current("ui.labels.close", "Close")).toBe("Close");
48
+ });
49
+
50
+ it("returns the English fallback when a has-less t hands back the key path or throws", () => {
51
+ configure(() => (key: string) => key);
52
+ expect(renderHook(() => useUiText()).result.current("ui.labels.close", "Close")).toBe("Close");
53
+
54
+ configure(() => () => {
55
+ throw new Error("missing");
56
+ });
57
+ expect(renderHook(() => useUiText()).result.current("ui.labels.close", "Close")).toBe("Close");
58
+ });
59
+ });
60
+
61
+ describe("Spinner label", () => {
62
+ it("announces the translated label, and an explicit aria-label still wins", () => {
63
+ configure(useCatalogueTranslations);
64
+ const { unmount } = render(<Spinner />);
65
+ expect(screen.getByRole("status")).toHaveAttribute("aria-label", "Caricamento");
66
+ unmount();
67
+
68
+ render(<Spinner aria-label="Saving" />);
69
+ expect(screen.getByRole("status")).toHaveAttribute("aria-label", "Saving");
70
+ });
71
+
72
+ it("keeps the English label when i18n is not configured", () => {
73
+ render(<Spinner />);
74
+ expect(screen.getByRole("status")).toHaveAttribute("aria-label", "Loading");
75
+ });
76
+ });
@@ -66,7 +66,14 @@ function BreadcrumbSeparator({ children, className, ...props }: React.ComponentP
66
66
  );
67
67
  }
68
68
 
69
- function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span">) {
69
+ function BreadcrumbEllipsis({
70
+ className,
71
+ label = "More",
72
+ ...props
73
+ }: React.ComponentProps<"span"> & {
74
+ /** Screen-reader text. Plain prop (no lookup): this file has no "use client" and may render on the server. */
75
+ label?: string;
76
+ }) {
70
77
  return (
71
78
  <span
72
79
  data-slot="breadcrumb-ellipsis"
@@ -76,7 +83,7 @@ function BreadcrumbEllipsis({ className, ...props }: React.ComponentProps<"span"
76
83
  {...props}
77
84
  >
78
85
  <MoreHorizontalIcon />
79
- <span className="sr-only">More</span>
86
+ <span className="sr-only">{label}</span>
80
87
  </span>
81
88
  );
82
89
  }
@@ -7,6 +7,7 @@ import { cn } from "@/lib/utils";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
9
9
  import { useDir } from "../../contexts/DirectionContext";
10
+ import { useUiText } from "./use-ui-text";
10
11
 
11
12
  type CarouselApi = UseEmblaCarouselType[1];
12
13
  type UseCarouselParameters = Parameters<typeof useEmblaCarousel>;
@@ -162,9 +163,14 @@ function CarouselPrevious({
162
163
  className,
163
164
  variant = "outline",
164
165
  size = "icon-sm",
166
+ label,
165
167
  ...props
166
- }: React.ComponentProps<typeof Button>) {
168
+ }: React.ComponentProps<typeof Button> & {
169
+ /** Screen-reader name. Defaults to `ui.labels.previous_slide`, else "Previous slide". */
170
+ label?: string;
171
+ }) {
167
172
  const { orientation, scrollPrev, canScrollPrev } = useCarousel();
173
+ const uiText = useUiText();
168
174
 
169
175
  return (
170
176
  <Button
@@ -184,7 +190,7 @@ function CarouselPrevious({
184
190
  {...props}
185
191
  >
186
192
  <ChevronLeftIcon className={orientation === "horizontal" ? "rtl:rotate-180" : undefined} />
187
- <span className="sr-only">Previous slide</span>
193
+ <span className="sr-only">{label ?? uiText("ui.labels.previous_slide", "Previous slide")}</span>
188
194
  </Button>
189
195
  );
190
196
  }
@@ -193,9 +199,14 @@ function CarouselNext({
193
199
  className,
194
200
  variant = "outline",
195
201
  size = "icon-sm",
202
+ label,
196
203
  ...props
197
- }: React.ComponentProps<typeof Button>) {
204
+ }: React.ComponentProps<typeof Button> & {
205
+ /** Screen-reader name. Defaults to `ui.labels.next_slide`, else "Next slide". */
206
+ label?: string;
207
+ }) {
198
208
  const { orientation, scrollNext, canScrollNext } = useCarousel();
209
+ const uiText = useUiText();
199
210
 
200
211
  return (
201
212
  <Button
@@ -215,7 +226,7 @@ function CarouselNext({
215
226
  {...props}
216
227
  >
217
228
  <ChevronRightIcon className={orientation === "horizontal" ? "rtl:rotate-180" : undefined} />
218
- <span className="sr-only">Next slide</span>
229
+ <span className="sr-only">{label ?? uiText("ui.labels.next_slide", "Next slide")}</span>
219
230
  </Button>
220
231
  );
221
232
  }
@@ -6,6 +6,7 @@ import { Dialog as DialogPrimitive } from "@base-ui/react/dialog";
6
6
  import { cn } from "@/lib/utils";
7
7
  import { Button } from "@/components/ui/button";
8
8
  import { XIcon } from "lucide-react";
9
+ import { useUiText } from "./use-ui-text";
9
10
 
10
11
  function Dialog({ ...props }: DialogPrimitive.Root.Props) {
11
12
  return <DialogPrimitive.Root data-slot="dialog" {...props} />;
@@ -40,10 +41,14 @@ function DialogContent({
40
41
  className,
41
42
  children,
42
43
  showCloseButton = true,
44
+ closeLabel,
43
45
  ...props
44
46
  }: DialogPrimitive.Popup.Props & {
45
47
  showCloseButton?: boolean;
48
+ /** Screen-reader name of the close button. Defaults to `ui.labels.close`, else "Close". */
49
+ closeLabel?: string;
46
50
  }) {
51
+ const uiText = useUiText();
47
52
  return (
48
53
  <DialogPortal>
49
54
  <DialogOverlay />
@@ -65,7 +70,7 @@ function DialogContent({
65
70
  render={<Button variant="ghost" className="absolute top-2 end-2" size="icon-sm" />}
66
71
  >
67
72
  <XIcon />
68
- <span className="sr-only">Close</span>
73
+ <span className="sr-only">{closeLabel ?? uiText("ui.labels.close", "Close")}</span>
69
74
  </DialogPrimitive.Close>
70
75
  )}
71
76
  </DialogPrimitive.Popup>
@@ -80,11 +85,15 @@ function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
80
85
  function DialogFooter({
81
86
  className,
82
87
  showCloseButton = false,
88
+ closeLabel,
83
89
  children,
84
90
  ...props
85
91
  }: React.ComponentProps<"div"> & {
86
92
  showCloseButton?: boolean;
93
+ /** Text of the footer close button. Defaults to `ui.labels.close`, else "Close". */
94
+ closeLabel?: string;
87
95
  }) {
96
+ const uiText = useUiText();
88
97
  return (
89
98
  <div
90
99
  data-slot="dialog-footer"
@@ -92,7 +101,11 @@ function DialogFooter({
92
101
  {...props}
93
102
  >
94
103
  {children}
95
- {showCloseButton && <DialogPrimitive.Close render={<Button variant="outline" />}>Close</DialogPrimitive.Close>}
104
+ {showCloseButton && (
105
+ <DialogPrimitive.Close render={<Button variant="outline" />}>
106
+ {closeLabel ?? uiText("ui.labels.close", "Close")}
107
+ </DialogPrimitive.Close>
108
+ )}
96
109
  </div>
97
110
  );
98
111
  }
@@ -8,6 +8,7 @@ import { resolvePhysicalSide, type LogicalSide } from "@/lib/direction";
8
8
  import { Button } from "@/components/ui/button";
9
9
  import { XIcon } from "lucide-react";
10
10
  import { useDir } from "../../contexts/DirectionContext";
11
+ import { useUiText } from "./use-ui-text";
11
12
 
12
13
  function Sheet({ ...props }: SheetPrimitive.Root.Props) {
13
14
  return <SheetPrimitive.Root data-slot="sheet" {...props} />;
@@ -44,12 +45,16 @@ function SheetContent({
44
45
  // Logical default: identical to "right" in LTR, mirrors to the left in RTL.
45
46
  side = "end",
46
47
  showCloseButton = true,
48
+ closeLabel,
47
49
  ...props
48
50
  }: SheetPrimitive.Popup.Props & {
49
51
  side?: LogicalSide;
50
52
  showCloseButton?: boolean;
53
+ /** Screen-reader name of the close button. Defaults to `ui.labels.close`, else "Close". */
54
+ closeLabel?: string;
51
55
  }) {
52
56
  const dir = useDir();
57
+ const uiText = useUiText();
53
58
  const physicalSide = resolvePhysicalSide(side, dir);
54
59
 
55
60
  return (
@@ -85,7 +90,7 @@ function SheetContent({
85
90
  }
86
91
  >
87
92
  <XIcon />
88
- <span className="sr-only">Close</span>
93
+ <span className="sr-only">{closeLabel ?? uiText("ui.labels.close", "Close")}</span>
89
94
  </SheetPrimitive.Close>
90
95
  )}
91
96
  </SheetPrimitive.Popup>
@@ -16,6 +16,7 @@ import { resolvePhysicalSide } from "@/lib/direction";
16
16
  import { cn } from "@/lib/utils";
17
17
  import { PanelLeftIcon } from "lucide-react";
18
18
  import { useDir } from "../../contexts/DirectionContext";
19
+ import { useUiText } from "./use-ui-text";
19
20
  import type { SessionSnapshot } from "../../server/ServerSession";
20
21
  import { mapThenable } from "../../utils/map-thenable";
21
22
  import { getViewportContext } from "../../utils/use-mobile";
@@ -262,6 +263,7 @@ function Sidebar({
262
263
  }) {
263
264
  const { isMobile, state, openMobile, setOpenMobile } = useSidebar();
264
265
  const dir = useDir();
266
+ const uiText = useUiText();
265
267
  // Logical "start"/"end" is resolved once, here. Everything downstream
266
268
  // (data-side, the positioning ternaries, the mobile SheetContent) consumes
267
269
  // the resolved PHYSICAL side, so "left"/"right" callers are unchanged.
@@ -295,8 +297,10 @@ function Sidebar({
295
297
  side={side}
296
298
  >
297
299
  <SheetHeader className="sr-only">
298
- <SheetTitle>Sidebar</SheetTitle>
299
- <SheetDescription>Displays the mobile sidebar.</SheetDescription>
300
+ <SheetTitle>{uiText("ui.labels.sidebar", "Sidebar")}</SheetTitle>
301
+ <SheetDescription>
302
+ {uiText("ui.labels.sidebar_description", "Displays the mobile sidebar.")}
303
+ </SheetDescription>
300
304
  </SheetHeader>
301
305
  <div className="flex h-full w-full flex-col">{children}</div>
302
306
  </SheetContent>
@@ -352,8 +356,21 @@ function Sidebar({
352
356
  );
353
357
  }
354
358
 
355
- function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<typeof Button>) {
359
+ function SidebarTrigger({
360
+ className,
361
+ onClick,
362
+ label,
363
+ ...props
364
+ }: React.ComponentProps<typeof Button> & {
365
+ /**
366
+ * Accessible name of the toggle. Falls back to an explicit `aria-label`, then
367
+ * `ui.labels.toggle_sidebar`, then "Toggle Sidebar".
368
+ */
369
+ label?: string;
370
+ }) {
356
371
  const { toggleSidebar } = useSidebar();
372
+ const uiText = useUiText();
373
+ const text = label ?? props["aria-label"] ?? uiText("ui.labels.toggle_sidebar", "Toggle Sidebar");
357
374
 
358
375
  return (
359
376
  <Button
@@ -369,22 +386,31 @@ function SidebarTrigger({ className, onClick, ...props }: React.ComponentProps<t
369
386
  {...props}
370
387
  >
371
388
  <PanelLeftIcon className="rtl:rotate-180" />
372
- <span className="sr-only">Toggle Sidebar</span>
389
+ <span className="sr-only">{text}</span>
373
390
  </Button>
374
391
  );
375
392
  }
376
393
 
377
- function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
394
+ function SidebarRail({
395
+ className,
396
+ label,
397
+ ...props
398
+ }: React.ComponentProps<"button"> & {
399
+ /** Accessible name and tooltip of the rail. Defaults to `ui.labels.toggle_sidebar`, else "Toggle Sidebar". */
400
+ label?: string;
401
+ }) {
378
402
  const { toggleSidebar } = useSidebar();
403
+ const uiText = useUiText();
404
+ const text = label ?? uiText("ui.labels.toggle_sidebar", "Toggle Sidebar");
379
405
 
380
406
  return (
381
407
  <button
382
408
  data-sidebar="rail"
383
409
  data-slot="sidebar-rail"
384
- aria-label="Toggle Sidebar"
410
+ aria-label={text}
385
411
  tabIndex={-1}
386
412
  onClick={toggleSidebar}
387
- title="Toggle Sidebar"
413
+ title={text}
388
414
  className={cn(
389
415
  // rtl-ok: the whole rail block is keyed to the resolved physical side
390
416
  "hover:after:bg-sidebar-border absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] sm:flex", // rtl-ok: keyed to resolved physical side
@@ -1,8 +1,11 @@
1
+ "use client";
2
+
1
3
  import { cva, type VariantProps } from "class-variance-authority";
2
4
  import { Loader2Icon } from "lucide-react";
3
5
  import * as React from "react";
4
6
 
5
7
  import { cn } from "@/lib/utils";
8
+ import { useUiText } from "./use-ui-text";
6
9
 
7
10
  const spinnerVariants = cva("animate-spin", {
8
11
  variants: {
@@ -22,11 +25,13 @@ function Spinner({
22
25
  size,
23
26
  ...props
24
27
  }: React.ComponentProps<typeof Loader2Icon> & VariantProps<typeof spinnerVariants>) {
28
+ // An explicit `aria-label` in `props` is spread below and wins over this.
29
+ const uiText = useUiText();
25
30
  return (
26
31
  <Loader2Icon
27
32
  data-slot="spinner"
28
33
  role="status"
29
- aria-label="Loading"
34
+ aria-label={uiText("ui.labels.loading", "Loading")}
30
35
  className={cn(spinnerVariants({ size, className }))}
31
36
  {...props}
32
37
  />
@@ -0,0 +1,49 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { useI18nTranslations } from "../../i18n/config";
5
+
6
+ /** The injected `t`, plus next-intl's optional non-throwing `has` probe. */
7
+ type Translate = ((key: string, values?: Record<string, unknown>) => string) & {
8
+ has?: (key: string) => boolean;
9
+ };
10
+
11
+ /** Fills `{name}` placeholders in an English fallback. */
12
+ function interpolate(text: string, values?: Record<string, unknown>): string {
13
+ if (!values) return text;
14
+ return text.replace(/\{(\w+)\}/g, (match, name: string) => (name in values ? String(values[name]) : match));
15
+ }
16
+
17
+ /**
18
+ * Chrome text for the shadcnui primitives (the "Close" behind a dialog's X,
19
+ * the sidebar toggle's label, a multi-select's "Search..."), in the active
20
+ * language.
21
+ *
22
+ * The primitives stay framework-agnostic: they never import next-intl. This
23
+ * reads the `useTranslations` the host app passed to `configureI18n()`, and
24
+ * returns the English `fallback` whenever that cannot produce the text — i18n
25
+ * not configured, the app has no such key, or the lookup throws. An app that
26
+ * never defines the key therefore renders exactly what it rendered before.
27
+ *
28
+ * Every primitive that uses this also takes an explicit label prop; a value
29
+ * passed there always wins over the lookup.
30
+ */
31
+ export function useUiText(): (key: string, fallback: string, values?: Record<string, unknown>) => string {
32
+ const t = useI18nTranslations() as Translate;
33
+
34
+ return useCallback(
35
+ (key: string, fallback: string, values?: Record<string, unknown>): string => {
36
+ if (typeof t.has === "function" && !t.has(key)) return interpolate(fallback, values);
37
+ try {
38
+ const text = t(key, values);
39
+ // The unconfigured fallback, and next-intl's default for a missing
40
+ // key, both hand back the key path itself.
41
+ if (!text || text === key || text.endsWith(`.${key}`)) return interpolate(fallback, values);
42
+ return text;
43
+ } catch {
44
+ return interpolate(fallback, values);
45
+ }
46
+ },
47
+ [t],
48
+ );
49
+ }