@stackcraftmx/calcita 0.23.0 → 0.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 (403) hide show
  1. package/README.md +127 -22
  2. package/cli/projectFiles.js +5 -3
  3. package/cli/templates/project/README.md.tpl +28 -0
  4. package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
  5. package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
  6. package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
  7. package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
  8. package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
  9. package/cli/templates/project/src/core/index.js.tpl +1 -0
  10. package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
  11. package/cli/templates/project/vite.config.js.tpl +27 -1
  12. package/dist/DataTable/AuditTrailTable.js +5 -3
  13. package/dist/DataTable/AuditTrailTable.js.map +1 -1
  14. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
  15. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  16. package/dist/DataTable/EmptyState.js +2 -2
  17. package/dist/DataTable/EmptyState.js.map +1 -1
  18. package/dist/DataTable/FilterChips.js +2 -2
  19. package/dist/DataTable/FilterChips.js.map +1 -1
  20. package/dist/DataTable/GoToPage.js +4 -3
  21. package/dist/DataTable/GoToPage.js.map +1 -1
  22. package/dist/DataTable/ServerTable.js +153 -83
  23. package/dist/DataTable/ServerTable.js.map +1 -1
  24. package/dist/DataTable/rowSelection.js +61 -0
  25. package/dist/DataTable/rowSelection.js.map +1 -0
  26. package/dist/access/PermissionMatrix.js +3 -2
  27. package/dist/access/PermissionMatrix.js.map +1 -1
  28. package/dist/account/AccountSecuritySection.js +2 -2
  29. package/dist/account/AccountSecuritySection.js.map +1 -1
  30. package/dist/account/ForgotPasswordScreen.js +5 -4
  31. package/dist/account/ForgotPasswordScreen.js.map +1 -1
  32. package/dist/account/ProfileScreen.js +39 -13
  33. package/dist/account/ProfileScreen.js.map +1 -1
  34. package/dist/account/ResetPasswordScreen.js +3 -2
  35. package/dist/account/ResetPasswordScreen.js.map +1 -1
  36. package/dist/account/SignInScreen.js +7 -6
  37. package/dist/account/SignInScreen.js.map +1 -1
  38. package/dist/account/accountConnector.js.map +1 -1
  39. package/dist/account/restAccountConnector.js +2 -1
  40. package/dist/account/restAccountConnector.js.map +1 -1
  41. package/dist/adapters/reactRouterRoutes.js +7 -6
  42. package/dist/adapters/reactRouterRoutes.js.map +1 -1
  43. package/dist/api/createApiClient.js +40 -5
  44. package/dist/api/createApiClient.js.map +1 -1
  45. package/dist/appShell/AccountMenu.js +15 -23
  46. package/dist/appShell/AccountMenu.js.map +1 -1
  47. package/dist/appShell/AppLayout.js +42 -15
  48. package/dist/appShell/AppLayout.js.map +1 -1
  49. package/dist/appShell/AppearanceSettings.js +29 -6
  50. package/dist/appShell/AppearanceSettings.js.map +1 -1
  51. package/dist/appShell/AuthLayout.js +2 -0
  52. package/dist/appShell/AuthLayout.js.map +1 -1
  53. package/dist/appShell/BarActionButton.js +3 -3
  54. package/dist/appShell/BarActionButton.js.map +1 -1
  55. package/dist/appShell/BarActions.js +19 -8
  56. package/dist/appShell/BarActions.js.map +1 -1
  57. package/dist/appShell/BarCapsule.js +19 -4
  58. package/dist/appShell/BarCapsule.js.map +1 -1
  59. package/dist/appShell/BarSearch.js +92 -0
  60. package/dist/appShell/BarSearch.js.map +1 -0
  61. package/dist/appShell/HeaderTools.js +4 -2
  62. package/dist/appShell/HeaderTools.js.map +1 -1
  63. package/dist/appShell/MenuLinks.js +36 -0
  64. package/dist/appShell/MenuLinks.js.map +1 -0
  65. package/dist/appShell/MobileTabBar.js +6 -4
  66. package/dist/appShell/MobileTabBar.js.map +1 -1
  67. package/dist/appShell/SettingsMenu.js +10 -5
  68. package/dist/appShell/SettingsMenu.js.map +1 -1
  69. package/dist/appShell/SideMenu.js +5 -3
  70. package/dist/appShell/SideMenu.js.map +1 -1
  71. package/dist/appShell/SideMenuEntry.js +3 -3
  72. package/dist/appShell/SideMenuEntry.js.map +1 -1
  73. package/dist/appShell/SubmenuActionsMenu.js +3 -4
  74. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  75. package/dist/appShell/TopMenu.js +5 -6
  76. package/dist/appShell/TopMenu.js.map +1 -1
  77. package/dist/appShell/Wordmark.js +11 -0
  78. package/dist/appShell/Wordmark.js.map +1 -0
  79. package/dist/appShell/menuItemText.js +4 -0
  80. package/dist/appShell/menuItemText.js.map +1 -1
  81. package/dist/appShell/shellSizes.js +2 -0
  82. package/dist/appShell/shellSizes.js.map +1 -1
  83. package/dist/auth/PasswordChecklist.js +2 -2
  84. package/dist/auth/PasswordChecklist.js.map +1 -1
  85. package/dist/calendar/EventCalendar.js +16 -5
  86. package/dist/calendar/EventCalendar.js.map +1 -1
  87. package/dist/calendar/EventDetails.js +74 -0
  88. package/dist/calendar/EventDetails.js.map +1 -0
  89. package/dist/calendar/calendarEvents.js +31 -0
  90. package/dist/calendar/calendarEvents.js.map +1 -0
  91. package/dist/calendar.js +8 -1
  92. package/dist/calendar.js.map +1 -1
  93. package/dist/charts.js +10 -1
  94. package/dist/charts.js.map +1 -1
  95. package/dist/datePickers/DateInput.js +3 -2
  96. package/dist/datePickers/DateInput.js.map +1 -1
  97. package/dist/downloads/DownloadManager.js +14 -20
  98. package/dist/downloads/DownloadManager.js.map +1 -1
  99. package/dist/downloads/downloadQueue.js +4 -2
  100. package/dist/downloads/downloadQueue.js.map +1 -1
  101. package/dist/downloads/downloadsMachine.js +4 -1
  102. package/dist/downloads/downloadsMachine.js.map +1 -1
  103. package/dist/downloads/fileTypes.js +22 -0
  104. package/dist/downloads/fileTypes.js.map +1 -0
  105. package/dist/feedback/ConfirmDialog.js +3 -2
  106. package/dist/feedback/ConfirmDialog.js.map +1 -1
  107. package/dist/feedback/ModalWaiting.js +2 -2
  108. package/dist/feedback/ModalWaiting.js.map +1 -1
  109. package/dist/feedback/NewBuildNotice.js +2 -2
  110. package/dist/feedback/NewBuildNotice.js.map +1 -1
  111. package/dist/filepond/FileDropzone.js +5 -3
  112. package/dist/filepond/FileDropzone.js.map +1 -1
  113. package/dist/formatting.js +171 -143
  114. package/dist/formatting.js.map +1 -1
  115. package/dist/forms/FormWizard.js +18 -45
  116. package/dist/forms/FormWizard.js.map +1 -1
  117. package/dist/forms/auth/ChangePasswordForm.js +5 -4
  118. package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
  119. package/dist/forms/auth/ForgotPasswordForm.js +2 -2
  120. package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
  121. package/dist/forms/auth/SignInForm.js +2 -2
  122. package/dist/forms/auth/SignInForm.js.map +1 -1
  123. package/dist/forms/security/SecondStepForm.js +2 -2
  124. package/dist/forms/security/SecondStepForm.js.map +1 -1
  125. package/dist/forms/security/TwoStepVerificationCard.js +8 -6
  126. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  127. package/dist/hookForm/FormWizard.js +59 -0
  128. package/dist/hookForm/FormWizard.js.map +1 -0
  129. package/dist/inbox/InboxLayout.js +44 -8
  130. package/dist/inbox/InboxLayout.js.map +1 -1
  131. package/dist/inbox/MessageList.js +71 -21
  132. package/dist/inbox/MessageList.js.map +1 -1
  133. package/dist/inbox/MessageView.js +80 -29
  134. package/dist/inbox/MessageView.js.map +1 -1
  135. package/dist/inbox/inboxDates.js +4 -3
  136. package/dist/inbox/inboxDates.js.map +1 -1
  137. package/dist/index.js +163 -136
  138. package/dist/index.js.map +1 -1
  139. package/dist/inputs/CatalogInput.js +8 -4
  140. package/dist/inputs/CatalogInput.js.map +1 -1
  141. package/dist/inputs/ColorInput.js +3 -2
  142. package/dist/inputs/ColorInput.js.map +1 -1
  143. package/dist/inputs/FormSection.js +3 -3
  144. package/dist/inputs/FormSection.js.map +1 -1
  145. package/dist/inputs/NumberInput.js +3 -2
  146. package/dist/inputs/NumberInput.js.map +1 -1
  147. package/dist/inputs/SelectInput.js +29 -11
  148. package/dist/inputs/SelectInput.js.map +1 -1
  149. package/dist/inputs/TextInput.js +6 -2
  150. package/dist/inputs/TextInput.js.map +1 -1
  151. package/dist/inputs/WizardLayout.js +54 -0
  152. package/dist/inputs/WizardLayout.js.map +1 -0
  153. package/dist/kanban/KanbanBoard.js +239 -14
  154. package/dist/kanban/KanbanBoard.js.map +1 -1
  155. package/dist/kanban/kanbanCards.js +19 -0
  156. package/dist/kanban/kanbanCards.js.map +1 -0
  157. package/dist/kanban.js +7 -1
  158. package/dist/kanban.js.map +1 -1
  159. package/dist/locales/en.js +74 -1
  160. package/dist/locales/en.js.map +1 -1
  161. package/dist/locales/es.js +74 -1
  162. package/dist/locales/es.js.map +1 -1
  163. package/dist/maps/MapView.js +28 -8
  164. package/dist/maps/MapView.js.map +1 -1
  165. package/dist/navigation/appMenu.js +13 -1
  166. package/dist/navigation/appMenu.js.map +1 -1
  167. package/dist/observability/logger.js +18 -0
  168. package/dist/observability/logger.js.map +1 -1
  169. package/dist/observability/usePublishedBuildWatch.js +4 -3
  170. package/dist/observability/usePublishedBuildWatch.js.map +1 -1
  171. package/dist/pwa/InstallAppSetting.js +6 -5
  172. package/dist/pwa/InstallAppSetting.js.map +1 -1
  173. package/dist/pwa/InstallInstructionsDialog.js +2 -2
  174. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  175. package/dist/pwa/InstallPromptBanner.js +15 -9
  176. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  177. package/dist/pwa/useInstallPrompt.js +2 -1
  178. package/dist/pwa/useInstallPrompt.js.map +1 -1
  179. package/dist/react-hook-form.js +6 -4
  180. package/dist/react-hook-form.js.map +1 -1
  181. package/dist/security/ActivityLog.js +4 -3
  182. package/dist/security/ActivityLog.js.map +1 -1
  183. package/dist/security/RecoveryCodesDialog.js +4 -3
  184. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  185. package/dist/security/SessionsCard.js +8 -6
  186. package/dist/security/SessionsCard.js.map +1 -1
  187. package/dist/table.js +2 -0
  188. package/dist/table.js.map +1 -1
  189. package/dist/theme/components/MuiAppBar.js +6 -5
  190. package/dist/theme/components/MuiAppBar.js.map +1 -1
  191. package/dist/theme/components/MuiDialog.js +13 -0
  192. package/dist/theme/components/MuiDialog.js.map +1 -0
  193. package/dist/theme/components/glassComponents.js +32 -31
  194. package/dist/theme/components/glassComponents.js.map +1 -1
  195. package/dist/theme/components/minimalComponents.js +8 -4
  196. package/dist/theme/components/minimalComponents.js.map +1 -1
  197. package/dist/theme/createToolkitTheme.js.map +1 -1
  198. package/dist/theme/glassSurface.js +36 -38
  199. package/dist/theme/glassSurface.js.map +1 -1
  200. package/dist/theme/lightDarkStyles.js +48 -0
  201. package/dist/theme/lightDarkStyles.js.map +1 -0
  202. package/dist/theme/toolkitComponents.js +2 -0
  203. package/dist/theme/toolkitComponents.js.map +1 -1
  204. package/dist/toolkit/ComponentTexts.js +13 -0
  205. package/dist/toolkit/ComponentTexts.js.map +1 -0
  206. package/dist/toolkit/ToolkitProvider.js +36 -28
  207. package/dist/toolkit/ToolkitProvider.js.map +1 -1
  208. package/dist/toolkit/browserNavigation.js +19 -20
  209. package/dist/toolkit/browserNavigation.js.map +1 -1
  210. package/dist/toolkit/componentTextsContext.js +15 -0
  211. package/dist/toolkit/componentTextsContext.js.map +1 -0
  212. package/dist/toolkit/hashNavigation.js +40 -0
  213. package/dist/toolkit/hashNavigation.js.map +1 -0
  214. package/dist/toolkit/navigationPaths.js +49 -0
  215. package/dist/toolkit/navigationPaths.js.map +1 -0
  216. package/dist/toolkit/toolkitSettings.js +24 -4
  217. package/dist/toolkit/toolkitSettings.js.map +1 -1
  218. package/dist/toolkit/useFormatters.js +12 -0
  219. package/dist/toolkit/useFormatters.js.map +1 -0
  220. package/dist/toolkit/useToolkitTranslation.js +11 -7
  221. package/dist/toolkit/useToolkitTranslation.js.map +1 -1
  222. package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
  223. package/dist/types/DataTable/EmptyState.d.ts +7 -2
  224. package/dist/types/DataTable/FilterChips.d.ts +3 -1
  225. package/dist/types/DataTable/GoToPage.d.ts +7 -2
  226. package/dist/types/DataTable/SelectionBar.d.ts +32 -0
  227. package/dist/types/DataTable/ServerTable.d.ts +64 -3
  228. package/dist/types/DataTable/index.d.ts +1 -1
  229. package/dist/types/DataTable/rowSelection.d.ts +113 -0
  230. package/dist/types/access/PermissionMatrix.d.ts +3 -1
  231. package/dist/types/account/AccountSecuritySection.d.ts +3 -1
  232. package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
  233. package/dist/types/account/ProfileScreen.d.ts +31 -1
  234. package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
  235. package/dist/types/account/SignInScreen.d.ts +3 -1
  236. package/dist/types/account/accountConnector.d.ts +7 -2
  237. package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
  238. package/dist/types/api/createApiClient.d.ts +46 -1
  239. package/dist/types/appShell/AppLayout.d.ts +15 -1
  240. package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
  241. package/dist/types/appShell/BarActionButton.d.ts +2 -2
  242. package/dist/types/appShell/BarActions.d.ts +6 -1
  243. package/dist/types/appShell/BarCapsule.d.ts +8 -0
  244. package/dist/types/appShell/BarSearch.d.ts +24 -0
  245. package/dist/types/appShell/MenuLinks.d.ts +17 -0
  246. package/dist/types/appShell/MobileTabBar.d.ts +4 -1
  247. package/dist/types/appShell/SettingsMenu.d.ts +3 -1
  248. package/dist/types/appShell/SideMenu.d.ts +3 -1
  249. package/dist/types/appShell/Wordmark.d.ts +6 -0
  250. package/dist/types/appShell/menuItemText.d.ts +16 -0
  251. package/dist/types/appShell/shellSizes.d.ts +1 -0
  252. package/dist/types/auth/PasswordChecklist.d.ts +3 -1
  253. package/dist/types/calendar/EventCalendar.d.ts +18 -1
  254. package/dist/types/calendar/EventDetails.d.ts +36 -0
  255. package/dist/types/calendar/calendarEvents.d.ts +27 -0
  256. package/dist/types/calendar.d.ts +2 -0
  257. package/dist/types/charts.d.ts +4 -0
  258. package/dist/types/datePickers/DateInput.d.ts +3 -1
  259. package/dist/types/downloads/DownloadManager.d.ts +7 -1
  260. package/dist/types/downloads/downloadQueue.d.ts +8 -3
  261. package/dist/types/downloads/fileTypes.d.ts +39 -0
  262. package/dist/types/downloads/index.d.ts +1 -0
  263. package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
  264. package/dist/types/feedback/ModalWaiting.d.ts +7 -2
  265. package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
  266. package/dist/types/filepond/FileDropzone.d.ts +3 -1
  267. package/dist/types/formatting.d.ts +45 -90
  268. package/dist/types/forms/FormWizard.d.ts +3 -1
  269. package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
  270. package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
  271. package/dist/types/forms/auth/SignInForm.d.ts +3 -1
  272. package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
  273. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
  274. package/dist/types/hookForm/FormWizard.d.ts +22 -0
  275. package/dist/types/inbox/InboxLayout.d.ts +14 -2
  276. package/dist/types/inbox/MessageList.d.ts +30 -5
  277. package/dist/types/inbox/MessageView.d.ts +35 -3
  278. package/dist/types/inbox/inboxDates.d.ts +6 -1
  279. package/dist/types/index.d.ts +15 -8
  280. package/dist/types/inputs/CatalogInput.d.ts +9 -5
  281. package/dist/types/inputs/ColorInput.d.ts +3 -1
  282. package/dist/types/inputs/FormSection.d.ts +2 -2
  283. package/dist/types/inputs/NumberInput.d.ts +3 -1
  284. package/dist/types/inputs/SelectInput.d.ts +9 -5
  285. package/dist/types/inputs/TextInput.d.ts +3 -1
  286. package/dist/types/inputs/WizardLayout.d.ts +34 -0
  287. package/dist/types/kanban/KanbanBoard.d.ts +61 -6
  288. package/dist/types/kanban/kanbanCards.d.ts +22 -0
  289. package/dist/types/kanban.d.ts +1 -0
  290. package/dist/types/locales/en.d.ts +77 -0
  291. package/dist/types/locales/es.d.ts +77 -0
  292. package/dist/types/maps/MapView.d.ts +12 -3
  293. package/dist/types/navigation/appMenu.d.ts +8 -0
  294. package/dist/types/observability/logger.d.ts +13 -0
  295. package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
  296. package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
  297. package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
  298. package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
  299. package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
  300. package/dist/types/react-hook-form.d.ts +1 -0
  301. package/dist/types/security/ActivityLog.d.ts +3 -1
  302. package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
  303. package/dist/types/security/SessionsCard.d.ts +3 -1
  304. package/dist/types/table.d.ts +3 -1
  305. package/dist/types/theme/components/MuiAppBar.d.ts +3 -1
  306. package/dist/types/theme/components/MuiDialog.d.ts +9 -0
  307. package/dist/types/theme/components/glassComponents.d.ts +5 -0
  308. package/dist/types/theme/lightDarkStyles.d.ts +8 -0
  309. package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
  310. package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
  311. package/dist/types/toolkit/browserNavigation.d.ts +23 -28
  312. package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
  313. package/dist/types/toolkit/hashNavigation.d.ts +12 -0
  314. package/dist/types/toolkit/navigationPaths.d.ts +43 -0
  315. package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
  316. package/dist/types/toolkit/useFormatters.d.ts +6 -0
  317. package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
  318. package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
  319. package/dist/types/ui/actions/CopyButton.d.ts +3 -1
  320. package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
  321. package/dist/types/ui/actions/SecretField.d.ts +5 -1
  322. package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
  323. package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
  324. package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
  325. package/dist/types/ui/index.d.ts +1 -1
  326. package/dist/types/ui/labels/tagOf.d.ts +13 -0
  327. package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
  328. package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
  329. package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
  330. package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
  331. package/dist/types/ui/layout/PageHeading.d.ts +3 -1
  332. package/dist/types/ui/metrics/BarList.d.ts +7 -1
  333. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
  334. package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
  335. package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
  336. package/dist/types/ui/metrics/LineChart.d.ts +41 -0
  337. package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
  338. package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
  339. package/dist/types/ui/metrics/chartParts.d.ts +112 -0
  340. package/dist/types/ui/navigation/BackButton.d.ts +3 -1
  341. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
  342. package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
  343. package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
  344. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
  345. package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
  346. package/dist/types/upload/FileDropzone.d.ts +3 -1
  347. package/dist/ui/actions/ActionsMenu.js +51 -0
  348. package/dist/ui/actions/ActionsMenu.js.map +1 -0
  349. package/dist/ui/actions/CopyButton.js +2 -2
  350. package/dist/ui/actions/CopyButton.js.map +1 -1
  351. package/dist/ui/actions/DownloadMenu.js +11 -12
  352. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  353. package/dist/ui/actions/SecretField.js +8 -4
  354. package/dist/ui/actions/SecretField.js.map +1 -1
  355. package/dist/ui/actions/SelectionActions.js +142 -0
  356. package/dist/ui/actions/SelectionActions.js.map +1 -0
  357. package/dist/ui/filters/DateRangeFilter.js +4 -3
  358. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  359. package/dist/ui/filters/QuickFilters.js +2 -2
  360. package/dist/ui/filters/QuickFilters.js.map +1 -1
  361. package/dist/ui/labels/tagOf.js +7 -0
  362. package/dist/ui/labels/tagOf.js.map +1 -0
  363. package/dist/ui/layout/CollapsibleSection.js +6 -2
  364. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  365. package/dist/ui/layout/ContentDialog.js +2 -2
  366. package/dist/ui/layout/ContentDialog.js.map +1 -1
  367. package/dist/ui/layout/ErrorPage.js +21 -7
  368. package/dist/ui/layout/ErrorPage.js.map +1 -1
  369. package/dist/ui/layout/FullPageLoader.js +2 -2
  370. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  371. package/dist/ui/layout/PageHeading.js +2 -2
  372. package/dist/ui/layout/PageHeading.js.map +1 -1
  373. package/dist/ui/metrics/BarList.js +18 -13
  374. package/dist/ui/metrics/BarList.js.map +1 -1
  375. package/dist/ui/metrics/CalendarHeatmap.js +11 -6
  376. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  377. package/dist/ui/metrics/ChartTooltip.js +1 -1
  378. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  379. package/dist/ui/metrics/ColumnChart.js +59 -63
  380. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  381. package/dist/ui/metrics/LineChart.js +117 -0
  382. package/dist/ui/metrics/LineChart.js.map +1 -0
  383. package/dist/ui/metrics/MetricCard.js +41 -8
  384. package/dist/ui/metrics/MetricCard.js.map +1 -1
  385. package/dist/ui/metrics/TableToggle.js +4 -2
  386. package/dist/ui/metrics/TableToggle.js.map +1 -1
  387. package/dist/ui/metrics/chartParts.js +93 -0
  388. package/dist/ui/metrics/chartParts.js.map +1 -0
  389. package/dist/ui/navigation/BackButton.js +2 -2
  390. package/dist/ui/navigation/BackButton.js.map +1 -1
  391. package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
  392. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  393. package/dist/ui/navigation/FontSizeToggle.js +3 -2
  394. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  395. package/dist/ui/navigation/LanguageToggle.js +40 -19
  396. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  397. package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
  398. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  399. package/dist/ui/timeline/GroupedTimeline.js +10 -8
  400. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  401. package/dist/upload/FileDropzone.js +5 -3
  402. package/dist/upload/FileDropzone.js.map +1 -1
  403. package/package.json +16 -7
@@ -1 +1 @@
1
- {"version":3,"file":"SideMenu.js","sources":["../../src/appShell/SideMenu.jsx"],"sourcesContent":["// Lo de adentro del menú lateral (el fijo de escritorio y el cajón del teléfono): arriba el logo y el nombre de la app\n// (solo en el cajón del teléfono: en escritorio van en la barra), en medio las secciones del menú (composeMenu, con el\n// título de cada sección) y abajo lo que el proyecto quiera (sidebarFooter: una tarjeta de ayuda, el plan contratado…).\n//\n// Es una columna de toda la altura: el menú crece y se desliza solo, y el pie se queda abajo.\n// Encogido (solo íconos): el título de cada sección se vuelve una rayita del mismo alto (los huecos entre secciones se\n// quedan parejos y nada brinca al abrirlo), el pie se quita (escondido seguía ocupando su lugar y sacaba la barra de\n// desplazamiento). La barra de desplazamiento no se pinta nunca: se comía el ancho de los nombres (la rueda sigue).\n// Con el vidrio del tema, los títulos de sección van como se escriben («Operación») y no en mayúsculas: la guía de Liquid\n// Glass de Apple los dejó de poner en mayúsculas porque se leen mejor.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';\nimport KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';\n\nimport {menuItemLabelOf, menuSectionsOf} from '@/appShell/menuItemText.js';\nimport {APP_BAR_HEIGHT_PIXELS} from '@/appShell/shellSizes.js';\nimport SideMenuEntry from '@/appShell/SideMenuEntry.jsx';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\n\n// lo alto del desvanecido en la orilla del menú que tiene más (px)\nconst SCROLL_FADE_PIXELS = 28;\n// el estilo minimalista del tema (THEME_LOOK.minimal; se repite el texto para no importar el tema desde el menú)\nconst MINIMAL_LOOK = 'minimal';\n// la flecha: a cuánto de la orilla va y cuánto se repite al deslizar un tramo (px)\nconst SCROLL_ARROW_INSET_PIXELS = 4;\nconst SCROLL_STEP_OVERLAP_PIXELS = 44;\n\n/** La flecha de una orilla del menú: un botoncito de vidrio centrado que lo desliza un tramo. */\nfunction ScrollArrow({label, onClick, sx, children}) {\n return (\n <Tooltip title={label} placement=\"right\">\n <IconButton size=\"small\" aria-label={label} onClick={onClick}\n sx={[(theme) => ({\n position: 'absolute', left: '50%', transform: 'translateX(-50%)', zIndex: 1, width: 32, height: 24, borderRadius: '999px',\n ...(isGlassTheme(theme) ? glassStylesOf(theme, {isControl: true}) : {bgcolor: 'background.paper', boxShadow: 2}),\n }), sx]}>\n {children}\n </IconButton>\n </Tooltip>\n );\n}\n\n// el logo de 28 px queda centrado en el menú encogido de 64 px: (64 - 28) / 2\nconst BRAND_LEFT_PADDING = '18px';\n// la letra del título de sección (rem): en mayúsculas, más chica; con vidrio, como se escribe, un poco más grande. Con\n// alto de renglón 2, la mitad del renglón mide lo mismo que la letra: ahí va la rayita del encogido\nconst SECTION_TITLE_FONT_REM = Object.freeze({solid: 0.6875, glass: 0.75});\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), el que se queda con el menú encogido\n * @param {string} props.brandName el nombre de la app\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} props.homePath\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem\n * @param {boolean} props.collapsed\n * @param {import('react').ReactNode} [props.footer]\n * @param {boolean} [props.showBrand] el logo y el nombre arriba; por omisión sí (en escritorio van en la barra)\n * @param {() => void} [props.onNavigate]\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.railTop] arriba del menú encogido que hace de barra: la ☰ y el logo\n * @param {import('react').ReactNode} [props.railBottom] abajo de ese menú: los ajustes y la cuenta\n */\nexport default function SideMenu({\n logo, brandName, brandCaption, homePath, menuItems, activeItem, collapsed, footer, showBrand = true, onNavigate, railTop, railBottom, menuBadges,\n}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n // con vidrio y con el minimalista (los dos siguen a Apple) los títulos de sección van como se escriben, no en mayúsculas\n const isAppleStyle = isGlassTheme(theme) || theme.toolkit?.look === MINIMAL_LOOK;\n const sectionTitleFontRem = isAppleStyle ? SECTION_TITLE_FONT_REM.glass : SECTION_TITLE_FONT_REM.solid;\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n // el menú no lleva barra de desplazamiento (ver abajo): cuando no cabe, se desvanece en la orilla de donde hay más, para\n // que se entienda que se desliza (lo reportaron: «no se entiende que scrollea»)\n const listRef = useRef(null);\n const [hiddenEdges, setHiddenEdges] = useState({top: false, bottom: false});\n useEffect(() => {\n const list = listRef.current;\n if (!list) {\n return undefined;\n }\n const measure = () => setHiddenEdges({\n top: list.scrollTop > 1,\n bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,\n });\n measure();\n list.addEventListener('scroll', measure, {passive: true});\n const resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n resizeObserver?.observe(list);\n return () => {\n list.removeEventListener('scroll', measure);\n resizeObserver?.disconnect();\n };\n }, [menuItems, collapsed]);\n // la flecha desliza el menú lo que mide su parte visible, menos un renglón para no perder de vista dónde se iba\n const scrollMenu = (direction) => listRef.current?.scrollBy({top: direction * (listRef.current.clientHeight - SCROLL_STEP_OVERLAP_PIXELS), behavior: 'smooth'});\n const fadeMask = `linear-gradient(to bottom, ${hiddenEdges.top ? 'transparent' : 'black'}, black ${SCROLL_FADE_PIXELS}px, black calc(100% - ${SCROLL_FADE_PIXELS}px), ${hiddenEdges.bottom ? 'transparent' : 'black'})`;\n\n return (\n // OJO: flex y no height 100%: dentro de la hoja de «Más» (que solo tiene alto máximo) un 100 % no se resuelve, el menú\n // crecía sin límite y la hoja no se deslizaba (lo reportaron con el teléfono acostado)\n <Box sx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n {showBrand && (\n <>\n <ButtonBase component={RouterLink} to={homePath} onClick={onNavigate} aria-label={brandName}\n sx={{display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1.25, minHeight: APP_BAR_HEIGHT_PIXELS,\n pl: BRAND_LEFT_PADDING, pr: 2, flexShrink: 0}}>\n <Box sx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</Box>\n <Box sx={(theme) => ({minWidth: 0, textAlign: 'left', ...fadeWhenCollapsed(theme)})}>\n <Typography variant=\"subtitle2\" component=\"span\" noWrap sx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</Typography>\n {brandCaption && <Typography variant=\"caption\" component=\"span\" color=\"text.secondary\" noWrap sx={{display: 'block', lineHeight: 1.3}}>{brandCaption}</Typography>}\n </Box>\n </ButtonBase>\n <Divider />\n </>\n )}\n\n {railTop && <Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, pt: 1.25, pb: 0.5, flexShrink: 0}}>{railTop}</Box>}\n\n {/* la lista y sus flechas: cuando no cabe, una flecha en la orilla de donde hay más (como los menús largos de la Mac)\n que la desliza un tramo; el desvanecido la refuerza. Sin barra de desplazamiento no se entendía que había más */}\n <Box sx={{position: 'relative', flexGrow: 1, minHeight: 0, display: 'flex', flexDirection: 'column'}}>\n <List ref={listRef} component=\"nav\" aria-label={t('drawer.ariaLabel')} dense disablePadding\n sx={{\n px: 1, pt: 1, pb: 2, flexGrow: 1, minHeight: 0, height: '100%', overflowY: 'auto', overflowX: 'hidden',\n // sin barra de desplazamiento, abierto o encogido (se desliza igual con la rueda, el trackpad o el\n // teclado, como en la Mac, donde la barra solo sale al deslizar): a la vista se veía una raya junto a\n // los nombres, y aunque fuera transparente se comía su ancho y cortaba «Alta con react-hook-form»\n scrollbarWidth: 'none',\n maskImage: fadeMask,\n WebkitMaskImage: fadeMask,\n '&::-webkit-scrollbar': {display: 'none'},\n }}>\n {menuSectionsOf(menuItems, t).map((section, sectionIndex) => (\n <Box component=\"li\" key={section.title ?? `section-${sectionIndex}`} sx={{listStyle: 'none'}}>\n <List dense disablePadding\n subheader={section.title ? (\n <ListSubheader disableSticky component=\"div\"\n sx={(theme) => ({position: 'relative', bgcolor: 'transparent', px: 1.5, pt: 1.5, lineHeight: 2, fontSize: `${sectionTitleFontRem}rem`,\n fontWeight: 600, letterSpacing: isAppleStyle ? 0 : '0.08em', textTransform: isAppleStyle ? 'none' : 'uppercase',\n color: 'text.secondary', whiteSpace: 'nowrap',\n // encogido: en lugar del título, una rayita a media altura del renglón\n '&::after': {\n content: '\"\"', position: 'absolute', left: '25%', right: '25%', height: '1px',\n top: `calc(${theme.spacing(1.5)} + ${sectionTitleFontRem}rem)`, bgcolor: 'divider',\n opacity: collapsed ? 1 : 0, transition: theme.transitions.create('opacity'),\n }})}>\n <Box component=\"span\" sx={fadeWhenCollapsed}>{section.title}</Box>\n </ListSubheader>\n ) : null}>\n {section.menuItems.map((menuItem) => (\n <SideMenuEntry key={menuItem.to ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} />\n ))}\n </List>\n </Box>\n ))}\n {/* la tarjeta de abajo va al final de lo que se desliza, no fija: en una pantalla baja (el teléfono acostado) se\n comía el menú y solo se veía «Inicio» (lo reportaron) */}\n {footer && (\n <Box component=\"li\" sx={{listStyle: 'none', pt: 2, px: 0.5, display: collapsed ? 'none' : 'block'}}>{footer}</Box>\n )}\n </List>\n {hiddenEdges.top && (\n <ScrollArrow label={t('drawer.scrollUp')} onClick={() => scrollMenu(-1)} sx={{top: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowUpRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n {hiddenEdges.bottom && (\n <ScrollArrow label={t('drawer.scrollDown')} onClick={() => scrollMenu(1)} sx={{bottom: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowDownRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n </Box>\n\n {railBottom && <Box sx={{display: 'flex', justifyContent: 'center', pt: 1, pb: 1.25, flexShrink: 0, borderTop: 1, borderColor: 'divider'}}>{railBottom}</Box>}\n </Box>\n );\n}\n"],"names":["theme"],"mappings":"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"}
1
+ {"version":3,"file":"SideMenu.js","sources":["../../src/appShell/SideMenu.jsx"],"sourcesContent":["// Lo de adentro del menú lateral (el fijo de escritorio y el cajón del teléfono): arriba el logo y el nombre de la app\n// (solo en el cajón del teléfono: en escritorio van en la barra), en medio las secciones del menú (composeMenu, con el\n// título de cada sección) y abajo lo que el proyecto quiera (sidebarFooter: una tarjeta de ayuda, el plan contratado…).\n//\n// Es una columna de toda la altura: el menú crece y se desliza solo, y el pie se queda abajo.\n// Encogido (solo íconos): el título de cada sección se vuelve una rayita del mismo alto (los huecos entre secciones se\n// quedan parejos y nada brinca al abrirlo), el pie se quita (escondido seguía ocupando su lugar y sacaba la barra de\n// desplazamiento). La barra de desplazamiento no se pinta nunca: se comía el ancho de los nombres (la rueda sigue).\n// Con el vidrio del tema, los títulos de sección van como se escriben («Operación») y no en mayúsculas: la guía de Liquid\n// Glass de Apple los dejó de poner en mayúsculas porque se leen mejor.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport ButtonBase from '@mui/material/ButtonBase';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';\nimport KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';\n\nimport {menuItemLabelOf, menuSectionsOf} from '@/appShell/menuItemText.js';\nimport Wordmark from '@/appShell/Wordmark.jsx';\nimport {APP_BAR_HEIGHT_PIXELS} from '@/appShell/shellSizes.js';\nimport SideMenuEntry from '@/appShell/SideMenuEntry.jsx';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\n\n// lo alto del desvanecido en la orilla del menú que tiene más (px)\nconst SCROLL_FADE_PIXELS = 28;\n// el estilo minimalista del tema (THEME_LOOK.minimal; se repite el texto para no importar el tema desde el menú)\nconst MINIMAL_LOOK = 'minimal';\n// la flecha: a cuánto de la orilla va y cuánto se repite al deslizar un tramo (px)\nconst SCROLL_ARROW_INSET_PIXELS = 4;\nconst SCROLL_STEP_OVERLAP_PIXELS = 44;\n\n/** La flecha de una orilla del menú: un botoncito de vidrio centrado que lo desliza un tramo. */\nfunction ScrollArrow({label, onClick, sx, children}) {\n return (\n <Tooltip title={label} placement=\"right\">\n <IconButton size=\"small\" aria-label={label} onClick={onClick}\n sx={[(theme) => ({\n position: 'absolute', left: '50%', transform: 'translateX(-50%)', zIndex: 1, width: 32, height: 24, borderRadius: '999px',\n ...(isGlassTheme(theme) ? glassStylesOf(theme, {isControl: true}) : {bgcolor: 'background.paper', boxShadow: 2}),\n }), sx]}>\n {children}\n </IconButton>\n </Tooltip>\n );\n}\n\n// el logo de 28 px queda centrado en el menú encogido de 64 px: (64 - 28) / 2\nconst BRAND_LEFT_PADDING = '18px';\n// la letra del título de sección (rem): en mayúsculas, más chica; con vidrio, como se escribe, un poco más grande. Con\n// alto de renglón 2, la mitad del renglón mide lo mismo que la letra: ahí va la rayita del encogido\nconst SECTION_TITLE_FONT_REM = Object.freeze({solid: 0.6875, glass: 0.75});\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} [props.logo] el logo chico (28 px), el que se queda con el menú encogido\n * @param {import('react').ReactNode} [props.wordmark] el logo ancho (en lugar del logo y el nombre arriba)\n * @param {string} props.brandName el nombre de la app\n * @param {string} [props.brandCaption] el renglón chico debajo del nombre («Panel de administración»)\n * @param {string} props.homePath\n * @param {object[]} props.menuItems lo que regresa composeMenu\n * @param {object|null} props.activeItem\n * @param {boolean} props.collapsed\n * @param {import('react').ReactNode} [props.footer]\n * @param {boolean} [props.showBrand] el logo y el nombre arriba; por omisión sí (en escritorio van en la barra)\n * @param {() => void} [props.onNavigate]\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {import('react').ReactNode} [props.railTop] arriba del menú encogido que hace de barra: la ☰ y el logo\n * @param {import('react').ReactNode} [props.railBottom] abajo de ese menú: los ajustes y la cuenta\n */\nexport default function SideMenu({\n logo, wordmark, brandName, brandCaption, homePath, menuItems, activeItem, collapsed, footer, showBrand = true, onNavigate, railTop, railBottom, menuBadges,\n}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n // con vidrio y con el minimalista (los dos siguen a Apple) los títulos de sección van como se escriben, no en mayúsculas\n const isAppleStyle = isGlassTheme(theme) || theme.toolkit?.look === MINIMAL_LOOK;\n const sectionTitleFontRem = isAppleStyle ? SECTION_TITLE_FONT_REM.glass : SECTION_TITLE_FONT_REM.solid;\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n // el menú no lleva barra de desplazamiento (ver abajo): cuando no cabe, se desvanece en la orilla de donde hay más, para\n // que se entienda que se desliza (lo reportaron: «no se entiende que scrollea»)\n const listRef = useRef(null);\n const [hiddenEdges, setHiddenEdges] = useState({top: false, bottom: false});\n useEffect(() => {\n const list = listRef.current;\n if (!list) {\n return undefined;\n }\n const measure = () => setHiddenEdges({\n top: list.scrollTop > 1,\n bottom: list.scrollTop + list.clientHeight < list.scrollHeight - 1,\n });\n measure();\n list.addEventListener('scroll', measure, {passive: true});\n const resizeObserver = typeof ResizeObserver === 'undefined' ? null : new ResizeObserver(measure);\n resizeObserver?.observe(list);\n return () => {\n list.removeEventListener('scroll', measure);\n resizeObserver?.disconnect();\n };\n }, [menuItems, collapsed]);\n // la flecha desliza el menú lo que mide su parte visible, menos un renglón para no perder de vista dónde se iba\n const scrollMenu = (direction) => listRef.current?.scrollBy({top: direction * (listRef.current.clientHeight - SCROLL_STEP_OVERLAP_PIXELS), behavior: 'smooth'});\n const fadeMask = `linear-gradient(to bottom, ${hiddenEdges.top ? 'transparent' : 'black'}, black ${SCROLL_FADE_PIXELS}px, black calc(100% - ${SCROLL_FADE_PIXELS}px), ${hiddenEdges.bottom ? 'transparent' : 'black'})`;\n\n return (\n // OJO: flex y no height 100%: dentro de la hoja de «Más» (que solo tiene alto máximo) un 100 % no se resuelve, el menú\n // crecía sin límite y la hoja no se deslizaba (lo reportaron con el teléfono acostado)\n <Box sx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n {showBrand && (\n <>\n <ButtonBase component={RouterLink} to={homePath} onClick={onNavigate} aria-label={brandName}\n sx={{display: 'flex', justifyContent: 'flex-start', alignItems: 'center', gap: 1.25, minHeight: APP_BAR_HEIGHT_PIXELS,\n pl: BRAND_LEFT_PADDING, pr: 2, flexShrink: 0}}>\n {wordmark ? <Wordmark>{wordmark}</Wordmark> : (\n <>\n <Box sx={{width: 28, height: 28, flex: '0 0 auto', display: 'flex', alignItems: 'center', justifyContent: 'center'}}>{logo}</Box>\n <Box sx={(theme) => ({minWidth: 0, textAlign: 'left', ...fadeWhenCollapsed(theme)})}>\n <Typography variant=\"subtitle2\" component=\"span\" noWrap sx={{display: 'block', fontWeight: 600, lineHeight: 1.3}}>{brandName}</Typography>\n {brandCaption && <Typography variant=\"caption\" component=\"span\" color=\"text.secondary\" noWrap sx={{display: 'block', lineHeight: 1.3}}>{brandCaption}</Typography>}\n </Box>\n </>\n )}\n </ButtonBase>\n <Divider />\n </>\n )}\n\n {railTop && <Box sx={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 1, pt: 1.25, pb: 0.5, flexShrink: 0}}>{railTop}</Box>}\n\n {/* la lista y sus flechas: cuando no cabe, una flecha en la orilla de donde hay más (como los menús largos de la Mac)\n que la desliza un tramo; el desvanecido la refuerza. Sin barra de desplazamiento no se entendía que había más */}\n <Box sx={{position: 'relative', flexGrow: 1, minHeight: 0, display: 'flex', flexDirection: 'column'}}>\n <List ref={listRef} component=\"nav\" aria-label={t('drawer.ariaLabel')} dense disablePadding\n sx={{\n px: 1, pt: 1, pb: 2, flexGrow: 1, minHeight: 0, height: '100%', overflowY: 'auto', overflowX: 'hidden',\n // sin barra de desplazamiento, abierto o encogido (se desliza igual con la rueda, el trackpad o el\n // teclado, como en la Mac, donde la barra solo sale al deslizar): a la vista se veía una raya junto a\n // los nombres, y aunque fuera transparente se comía su ancho y cortaba «Alta con react-hook-form»\n scrollbarWidth: 'none',\n maskImage: fadeMask,\n WebkitMaskImage: fadeMask,\n '&::-webkit-scrollbar': {display: 'none'},\n }}>\n {menuSectionsOf(menuItems, t).map((section, sectionIndex) => (\n <Box component=\"li\" key={section.title ?? `section-${sectionIndex}`} sx={{listStyle: 'none'}}>\n <List dense disablePadding\n subheader={section.title ? (\n <ListSubheader disableSticky component=\"div\"\n sx={(theme) => ({position: 'relative', bgcolor: 'transparent', px: 1.5, pt: 1.5, lineHeight: 2, fontSize: `${sectionTitleFontRem}rem`,\n fontWeight: 600, letterSpacing: isAppleStyle ? 0 : '0.08em', textTransform: isAppleStyle ? 'none' : 'uppercase',\n color: 'text.secondary', whiteSpace: 'nowrap',\n // encogido: en lugar del título, una rayita a media altura del renglón\n '&::after': {\n content: '\"\"', position: 'absolute', left: '25%', right: '25%', height: '1px',\n top: `calc(${theme.spacing(1.5)} + ${sectionTitleFontRem}rem)`, bgcolor: 'divider',\n opacity: collapsed ? 1 : 0, transition: theme.transitions.create('opacity'),\n }})}>\n <Box component=\"span\" sx={fadeWhenCollapsed}>{section.title}</Box>\n </ListSubheader>\n ) : null}>\n {section.menuItems.map((menuItem) => (\n <SideMenuEntry key={menuItem.to ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} />\n ))}\n </List>\n </Box>\n ))}\n {/* la tarjeta de abajo va al final de lo que se desliza, no fija: en una pantalla baja (el teléfono acostado) se\n comía el menú y solo se veía «Inicio» (lo reportaron) */}\n {footer && (\n <Box component=\"li\" sx={{listStyle: 'none', pt: 2, px: 0.5, display: collapsed ? 'none' : 'block'}}>{footer}</Box>\n )}\n </List>\n {hiddenEdges.top && (\n <ScrollArrow label={t('drawer.scrollUp')} onClick={() => scrollMenu(-1)} sx={{top: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowUpRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n {hiddenEdges.bottom && (\n <ScrollArrow label={t('drawer.scrollDown')} onClick={() => scrollMenu(1)} sx={{bottom: SCROLL_ARROW_INSET_PIXELS}}>\n <KeyboardArrowDownRoundedIcon fontSize=\"small\" />\n </ScrollArrow>\n )}\n </Box>\n\n {railBottom && <Box sx={{display: 'flex', justifyContent: 'center', pt: 1, pb: 1.25, flexShrink: 0, borderTop: 1, borderColor: 'divider'}}>{railBottom}</Box>}\n </Box>\n );\n}\n"],"names":["theme"],"mappings":"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"}
@@ -15,7 +15,7 @@ import Tooltip from "@mui/material/Tooltip";
15
15
  import { useTheme } from "@mui/material/styles";
16
16
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
17
17
  import ChevronRightIcon from "@mui/icons-material/ChevronRight";
18
- import { menuItemLabelOf } from "./menuItemText.js";
18
+ import { menuItemLabelOf, menuLinkPropsOf } from "./menuItemText.js";
19
19
  import SubmenuActionsMenu, { SUBMENU_PLACEMENT } from "./SubmenuActionsMenu.js";
20
20
  import { menuBadgeOf } from "../navigation/appMenu.js";
21
21
  import { isGlassTheme, accentTextStylesOf, glassDropOf } from "../theme/glassSurface.js";
@@ -59,7 +59,7 @@ function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges
59
59
  setOpenedByPerson(!isOpen);
60
60
  };
61
61
  const closeFlyout = () => setFlyoutAnchor(null);
62
- const linkProps = hasChildren ? { onClick: toggleChildren, ...collapsed && { "aria-haspopup": "menu" } } : { component: RouterLink, to: menuItem.to, onClick: onNavigate };
62
+ const linkProps = hasChildren ? { onClick: toggleChildren, ...collapsed && { "aria-haspopup": "menu" } } : { ...menuLinkPropsOf(menuItem, RouterLink), onClick: onNavigate };
63
63
  const fadeWhenCollapsed = (theme2) => ({ opacity: collapsed ? 0 : 1, transition: theme2.transitions.create("opacity") });
64
64
  const isChild = depth > 0;
65
65
  const leftPadding = !isChild || Icon ? theme.spacing(ROW_LEFT_PADDING + depth * INDENT_PER_LEVEL) : `calc(${theme.spacing(ROW_LEFT_PADDING + ICON_GAP + (depth - 1) * INDENT_PER_LEVEL)} + ${ICON_SIZE})`;
@@ -130,7 +130,7 @@ function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges
130
130
  menuBadges,
131
131
  depth: depth + 1
132
132
  },
133
- child.to ?? menuItemLabelOf(child, t)
133
+ child.to ?? child.href ?? menuItemLabelOf(child, t)
134
134
  )) }) })
135
135
  ] });
136
136
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SideMenuEntry.js","sources":["../../src/appShell/SideMenuEntry.jsx"],"sourcesContent":["// Un renglón del menú lateral: ícono, nombre y (si trae) su numerito; si tiene submenú, se abre y se cierra en su lugar.\n// Con el menú encogido, el submenú sale como un menú flotante a la derecha del ícono (el de acciones de Apple: ícono y\n// nombre, la pantalla abierta marcada) en lugar de abrir todo el menú. Muestra un nivel: las hijas directas.\n//\n// Reglas que vienen del menú lateral de walk-ot, ya pagadas:\n// - La estructura es FIJA: al encoger el menú, el nombre y la flecha NO se desmontan, solo se vuelven transparentes. Si\n// se desmontaran, React los quita en el mismo cuadro en que el menú anima su ancho y el ojo lo lee como un salto.\n// - Encogido, el nombre sale en un tooltip a la derecha.\n// - Los tamaños van en rem, no en px: así crecen con el tamaño de letra que elige la persona (FontSizeToggle).\n// - El renglón es una liga de verdad (clic con la rueda, «abrir en otra pestaña»), no un onClick que navega.\n// - Una hija del submenú con ícono se mete un escalón; una sin ícono no deja hueco vacío: su nombre queda justo debajo\n// del de su madre (con el hueco, el nombre se cortaba: «Alta con react-hook-fo…»).\n//\n// Con el vidrio del tema, lo que pide la guía de Liquid Glass de Apple:\n// - lo elegido va en la «gota» (la misma de las pestañas), no en un fondo plano;\n// - encogido, el menú es un panel con las esquinas del expandido (20) y cada renglón un botón de 44 px (el mínimo para el\n// dedo) con esquina concéntrica: el radio del panel (20) menos el margen entre botón y orilla (10) da 10. Antes era una\n// cápsula con botones redondos y se veía demasiado redondo (lo reportaron).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Chip from '@mui/material/Chip';\nimport Collapse from '@mui/material/Collapse';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Tooltip from '@mui/material/Tooltip';\nimport {useTheme} from '@mui/material/styles';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\n\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {accentTextStylesOf, glassDropOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\n\n// las medidas del renglón, en unidades de spacing: los márgenes, el escalón de cada nivel de submenú y el espacio entre el\n// ícono y el nombre. Las hijas van más apretadas (ícono más chico, menos espacio, renglón más bajo): medido en Chrome, con\n// las medidas de las madres, «Alta con react-hook-form» en negritas pedía 160 px, tenía 154 y se cortaba.\nconst ROW_LEFT_PADDING = 1.5;\nconst ROW_RIGHT_PADDING = 1.5;\nconst CHILD_RIGHT_PADDING = 1;\nconst INDENT_PER_LEVEL = 1.5;\nconst ICON_GAP = 1.75;\nconst CHILD_ICON_GAP = 1;\nconst ICON_SIZE = '1.375rem';\nconst CHILD_ICON_SIZE = '1.25rem';\nconst ROW_MIN_HEIGHT_PIXELS = 40;\nconst CHILD_ROW_MIN_HEIGHT_PIXELS = 36;\n// encogido con vidrio: el botón de 44 px (lo mínimo para tocar con el dedo, WCAG 2.5.5) con su esquina concéntrica\nconst RAIL_BUTTON_PIXELS = 44;\n// el nombre del renglón y su numerito (px), con su espaciado de Apple (textSizeOf)\nconst MENU_TEXT_PIXELS = 13;\nconst MENU_BADGE_PIXELS = 11;\n// de ahí para arriba se lee «99+»\nconst BADGE_MAX = 99;\nconst RAIL_BUTTON_RADIUS_PIXELS = 10;\n\n/**\n * @param {object} props\n * @param {object} props.menuItem una entrada de composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {boolean} props.collapsed el menú está encogido (solo íconos)\n * @param {() => void} [props.onNavigate] al elegir una pantalla (el cajón del teléfono se cierra)\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey (menuBadgeOf)\n * @param {number} [props.depth]\n */\nexport default function SideMenuEntry({menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n const isRailButton = isGlass && collapsed;\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const containsActive = hasChildren && menuItem.children.includes(activeItem);\n const isActive = menuItem === activeItem || containsActive;\n // abierto si la persona lo abrió, o solo si ahí está la pantalla abierta\n const [openedByPerson, setOpenedByPerson] = useState(null);\n // encogido: el botón del que cuelga el menú flotante del submenú (null = cerrado)\n const [flyoutAnchor, setFlyoutAnchor] = useState(null);\n const isOpen = openedByPerson ?? containsActive;\n const label = menuItemLabelOf(menuItem, t);\n // el badge: el conteo del servidor, la suma de las hijas o el fijo del menu.js; encogido va sobre el ícono\n const badge = menuBadgeOf(menuItem, menuBadges);\n const Icon = menuItem.Icon;\n\n const toggleChildren = (event) => {\n if (collapsed) {\n setFlyoutAnchor(event.currentTarget);\n return;\n }\n setOpenedByPerson(!isOpen);\n };\n const closeFlyout = () => setFlyoutAnchor(null);\n\n const linkProps = hasChildren\n ? {onClick: toggleChildren, ...(collapsed && {'aria-haspopup': 'menu'})}\n : {component: RouterLink, to: menuItem.to, onClick: onNavigate};\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n\n const isChild = depth > 0;\n // una hija sin ícono empieza donde empieza el nombre de su madre: margen + ícono + espacio, más los escalones de en medio\n const leftPadding = !isChild || Icon\n ? theme.spacing(ROW_LEFT_PADDING + depth * INDENT_PER_LEVEL)\n : `calc(${theme.spacing(ROW_LEFT_PADDING + ICON_GAP + (depth - 1) * INDENT_PER_LEVEL)} + ${ICON_SIZE})`;\n // el renglón de la pantalla abierta va del color del tema, pero legible (accentTextStylesOf): el naranja tal cual sobre\n // blanco daba 2.78 : 1\n const activeText = accentTextStylesOf(theme);\n // OJO: se SUMAN con mergeStyles, no con {...a, ...b}: los dos traen su parte de modo oscuro y con la suma simple la segunda\n // borraba la primera (el elegido salía en oscuro con el fondo de claro, casi negro y con línea blanca; error mío)\n const selectedStyle = mergeStyles(isGlass ? glassDropOf(theme) : {bgcolor: 'action.selected'}, activeText);\n\n const entryButton = (\n <ListItemButton {...linkProps} selected={!hasChildren && isActive} aria-expanded={hasChildren ? (collapsed ? Boolean(flyoutAnchor) : isOpen) : undefined}\n sx={{\n borderRadius: isRailButton ? `${RAIL_BUTTON_RADIUS_PIXELS}px` : 1.5, pl: leftPadding, pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,\n minHeight: isRailButton ? RAIL_BUTTON_PIXELS : (isChild ? CHILD_ROW_MIN_HEIGHT_PIXELS : ROW_MIN_HEIGHT_PIXELS),\n transition: theme.transitions.create(['background-color', 'border-radius', 'min-height']),\n ...(isActive ? activeText : {color: 'text.primary'}),\n '&.Mui-selected, &.Mui-selected:hover': selectedStyle,\n }}>\n {(!isChild || Icon) && (\n <ListItemIcon sx={{minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: 'inherit'}}>\n <Badge invisible={!collapsed || !badge} color=\"primary\" max={BADGE_MAX} badgeContent={badge?.isCount ? badge.content : undefined}\n variant={badge?.isCount ? 'standard' : 'dot'} overlap=\"circular\">\n {Icon ? <Icon sx={{fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE}} /> : <Box sx={{width: ICON_SIZE, height: ICON_SIZE}} />}\n </Badge>\n </ListItemIcon>\n )}\n <ListItemText primary={label} sx={fadeWhenCollapsed}\n slotProps={{primary: {noWrap: true, sx: {...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400}}}} />\n {badge && (\n <Chip label={badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content} size=\"small\" color=\"primary\" sx={(theme) => ({height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme)})} />\n )}\n {hasChildren && (\n <Box sx={(theme) => ({display: 'flex', alignItems: 'center', ...fadeWhenCollapsed(theme)})}>\n {isOpen ? <ExpandMoreIcon fontSize=\"small\" /> : <ChevronRightIcon fontSize=\"small\" />}\n </Box>\n )}\n </ListItemButton>\n );\n\n return (\n <>\n {collapsed ? <Tooltip title={label} placement=\"right\">{entryButton}</Tooltip> : entryButton}\n {hasChildren && collapsed && (\n <SubmenuActionsMenu anchorElement={flyoutAnchor} onClose={closeFlyout} menuItem={menuItem} activeItem={activeItem} menuBadges={menuBadges}\n onNavigate={onNavigate} placement={SUBMENU_PLACEMENT.side} />\n )}\n {hasChildren && (\n <Collapse in={isOpen && !collapsed} unmountOnExit>\n <List dense disablePadding>\n {menuItem.children.map((child) => (\n <SideMenuEntry key={child.to ?? menuItemLabelOf(child, t)} menuItem={child} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} depth={depth + 1} />\n ))}\n </List>\n </Collapse>\n )}\n </>\n );\n}\n"],"names":["theme"],"mappings":"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"}
1
+ {"version":3,"file":"SideMenuEntry.js","sources":["../../src/appShell/SideMenuEntry.jsx"],"sourcesContent":["// Un renglón del menú lateral: ícono, nombre y (si trae) su numerito; si tiene submenú, se abre y se cierra en su lugar.\n// Con el menú encogido, el submenú sale como un menú flotante a la derecha del ícono (el de acciones de Apple: ícono y\n// nombre, la pantalla abierta marcada) en lugar de abrir todo el menú. Muestra un nivel: las hijas directas.\n//\n// Reglas que vienen del menú lateral de walk-ot, ya pagadas:\n// - La estructura es FIJA: al encoger el menú, el nombre y la flecha NO se desmontan, solo se vuelven transparentes. Si\n// se desmontaran, React los quita en el mismo cuadro en que el menú anima su ancho y el ojo lo lee como un salto.\n// - Encogido, el nombre sale en un tooltip a la derecha.\n// - Los tamaños van en rem, no en px: así crecen con el tamaño de letra que elige la persona (FontSizeToggle).\n// - El renglón es una liga de verdad (clic con la rueda, «abrir en otra pestaña»), no un onClick que navega.\n// - Una hija del submenú con ícono se mete un escalón; una sin ícono no deja hueco vacío: su nombre queda justo debajo\n// del de su madre (con el hueco, el nombre se cortaba: «Alta con react-hook-fo…»).\n//\n// Con el vidrio del tema, lo que pide la guía de Liquid Glass de Apple:\n// - lo elegido va en la «gota» (la misma de las pestañas), no en un fondo plano;\n// - encogido, el menú es un panel con las esquinas del expandido (20) y cada renglón un botón de 44 px (el mínimo para el\n// dedo) con esquina concéntrica: el radio del panel (20) menos el margen entre botón y orilla (10) da 10. Antes era una\n// cápsula con botones redondos y se veía demasiado redondo (lo reportaron).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Chip from '@mui/material/Chip';\nimport Collapse from '@mui/material/Collapse';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Tooltip from '@mui/material/Tooltip';\nimport {useTheme} from '@mui/material/styles';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\nimport ChevronRightIcon from '@mui/icons-material/ChevronRight';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport SubmenuActionsMenu, {SUBMENU_PLACEMENT} from '@/appShell/SubmenuActionsMenu.jsx';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {accentTextStylesOf, glassDropOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {mergeStyles} from '@/theme/mergeComponents.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\n\n// las medidas del renglón, en unidades de spacing: los márgenes, el escalón de cada nivel de submenú y el espacio entre el\n// ícono y el nombre. Las hijas van más apretadas (ícono más chico, menos espacio, renglón más bajo): medido en Chrome, con\n// las medidas de las madres, «Alta con react-hook-form» en negritas pedía 160 px, tenía 154 y se cortaba.\nconst ROW_LEFT_PADDING = 1.5;\nconst ROW_RIGHT_PADDING = 1.5;\nconst CHILD_RIGHT_PADDING = 1;\nconst INDENT_PER_LEVEL = 1.5;\nconst ICON_GAP = 1.75;\nconst CHILD_ICON_GAP = 1;\nconst ICON_SIZE = '1.375rem';\nconst CHILD_ICON_SIZE = '1.25rem';\nconst ROW_MIN_HEIGHT_PIXELS = 40;\nconst CHILD_ROW_MIN_HEIGHT_PIXELS = 36;\n// encogido con vidrio: el botón de 44 px (lo mínimo para tocar con el dedo, WCAG 2.5.5) con su esquina concéntrica\nconst RAIL_BUTTON_PIXELS = 44;\n// el nombre del renglón y su numerito (px), con su espaciado de Apple (textSizeOf)\nconst MENU_TEXT_PIXELS = 13;\nconst MENU_BADGE_PIXELS = 11;\n// de ahí para arriba se lee «99+»\nconst BADGE_MAX = 99;\nconst RAIL_BUTTON_RADIUS_PIXELS = 10;\n\n/**\n * @param {object} props\n * @param {object} props.menuItem una entrada de composeMenu\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {boolean} props.collapsed el menú está encogido (solo íconos)\n * @param {() => void} [props.onNavigate] al elegir una pantalla (el cajón del teléfono se cierra)\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey (menuBadgeOf)\n * @param {number} [props.depth]\n */\nexport default function SideMenuEntry({menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const theme = useTheme();\n const isGlass = isGlassTheme(theme);\n const isRailButton = isGlass && collapsed;\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const containsActive = hasChildren && menuItem.children.includes(activeItem);\n const isActive = menuItem === activeItem || containsActive;\n // abierto si la persona lo abrió, o solo si ahí está la pantalla abierta\n const [openedByPerson, setOpenedByPerson] = useState(null);\n // encogido: el botón del que cuelga el menú flotante del submenú (null = cerrado)\n const [flyoutAnchor, setFlyoutAnchor] = useState(null);\n const isOpen = openedByPerson ?? containsActive;\n const label = menuItemLabelOf(menuItem, t);\n // el badge: el conteo del servidor, la suma de las hijas o el fijo del menu.js; encogido va sobre el ícono\n const badge = menuBadgeOf(menuItem, menuBadges);\n const Icon = menuItem.Icon;\n\n const toggleChildren = (event) => {\n if (collapsed) {\n setFlyoutAnchor(event.currentTarget);\n return;\n }\n setOpenedByPerson(!isOpen);\n };\n const closeFlyout = () => setFlyoutAnchor(null);\n\n const linkProps = hasChildren\n ? {onClick: toggleChildren, ...(collapsed && {'aria-haspopup': 'menu'})}\n : {...menuLinkPropsOf(menuItem, RouterLink), onClick: onNavigate};\n const fadeWhenCollapsed = (theme) => ({opacity: collapsed ? 0 : 1, transition: theme.transitions.create('opacity')});\n\n const isChild = depth > 0;\n // una hija sin ícono empieza donde empieza el nombre de su madre: margen + ícono + espacio, más los escalones de en medio\n const leftPadding = !isChild || Icon\n ? theme.spacing(ROW_LEFT_PADDING + depth * INDENT_PER_LEVEL)\n : `calc(${theme.spacing(ROW_LEFT_PADDING + ICON_GAP + (depth - 1) * INDENT_PER_LEVEL)} + ${ICON_SIZE})`;\n // el renglón de la pantalla abierta va del color del tema, pero legible (accentTextStylesOf): el naranja tal cual sobre\n // blanco daba 2.78 : 1\n const activeText = accentTextStylesOf(theme);\n // OJO: se SUMAN con mergeStyles, no con {...a, ...b}: los dos traen su parte de modo oscuro y con la suma simple la segunda\n // borraba la primera (el elegido salía en oscuro con el fondo de claro, casi negro y con línea blanca; error mío)\n const selectedStyle = mergeStyles(isGlass ? glassDropOf(theme) : {bgcolor: 'action.selected'}, activeText);\n\n const entryButton = (\n <ListItemButton {...linkProps} selected={!hasChildren && isActive} aria-expanded={hasChildren ? (collapsed ? Boolean(flyoutAnchor) : isOpen) : undefined}\n sx={{\n borderRadius: isRailButton ? `${RAIL_BUTTON_RADIUS_PIXELS}px` : 1.5, pl: leftPadding, pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,\n minHeight: isRailButton ? RAIL_BUTTON_PIXELS : (isChild ? CHILD_ROW_MIN_HEIGHT_PIXELS : ROW_MIN_HEIGHT_PIXELS),\n transition: theme.transitions.create(['background-color', 'border-radius', 'min-height']),\n ...(isActive ? activeText : {color: 'text.primary'}),\n '&.Mui-selected, &.Mui-selected:hover': selectedStyle,\n }}>\n {(!isChild || Icon) && (\n <ListItemIcon sx={{minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: 'inherit'}}>\n <Badge invisible={!collapsed || !badge} color=\"primary\" max={BADGE_MAX} badgeContent={badge?.isCount ? badge.content : undefined}\n variant={badge?.isCount ? 'standard' : 'dot'} overlap=\"circular\">\n {Icon ? <Icon sx={{fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE}} /> : <Box sx={{width: ICON_SIZE, height: ICON_SIZE}} />}\n </Badge>\n </ListItemIcon>\n )}\n <ListItemText primary={label} sx={fadeWhenCollapsed}\n slotProps={{primary: {noWrap: true, sx: {...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400}}}} />\n {badge && (\n <Chip label={badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content} size=\"small\" color=\"primary\" sx={(theme) => ({height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme)})} />\n )}\n {hasChildren && (\n <Box sx={(theme) => ({display: 'flex', alignItems: 'center', ...fadeWhenCollapsed(theme)})}>\n {isOpen ? <ExpandMoreIcon fontSize=\"small\" /> : <ChevronRightIcon fontSize=\"small\" />}\n </Box>\n )}\n </ListItemButton>\n );\n\n return (\n <>\n {collapsed ? <Tooltip title={label} placement=\"right\">{entryButton}</Tooltip> : entryButton}\n {hasChildren && collapsed && (\n <SubmenuActionsMenu anchorElement={flyoutAnchor} onClose={closeFlyout} menuItem={menuItem} activeItem={activeItem} menuBadges={menuBadges}\n onNavigate={onNavigate} placement={SUBMENU_PLACEMENT.side} />\n )}\n {hasChildren && (\n <Collapse in={isOpen && !collapsed} unmountOnExit>\n <List dense disablePadding>\n {menuItem.children.map((child) => (\n <SideMenuEntry key={child.to ?? child.href ?? menuItemLabelOf(child, t)} menuItem={child} activeItem={activeItem} collapsed={collapsed}\n onNavigate={onNavigate} menuBadges={menuBadges} depth={depth + 1} />\n ))}\n </List>\n </Collapse>\n )}\n </>\n );\n}\n"],"names":["theme"],"mappings":"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"}
@@ -5,7 +5,7 @@ import ListItemIcon from "@mui/material/ListItemIcon";
5
5
  import ListItemText from "@mui/material/ListItemText";
6
6
  import Menu from "@mui/material/Menu";
7
7
  import MenuItem from "@mui/material/MenuItem";
8
- import { menuItemLabelOf } from "./menuItemText.js";
8
+ import { menuItemLabelOf, menuLinkPropsOf } from "./menuItemText.js";
9
9
  import { menuBadgeOf } from "../navigation/appMenu.js";
10
10
  import { textSizeOf } from "../theme/appleTracking.js";
11
11
  import { useToolkitNavigation } from "../toolkit/toolkitContext.js";
@@ -45,8 +45,7 @@ function SubmenuActionsMenu({ anchorElement, onClose, menuItem, activeItem, menu
45
45
  return /* @__PURE__ */ jsxs(
46
46
  MenuItem,
47
47
  {
48
- component: RouterLink,
49
- to: child.to,
48
+ ...menuLinkPropsOf(child, RouterLink),
50
49
  selected: child === activeItem,
51
50
  onClick: () => {
52
51
  onClose();
@@ -58,7 +57,7 @@ function SubmenuActionsMenu({ anchorElement, onClose, menuItem, activeItem, menu
58
57
  badge && /* @__PURE__ */ jsx(Chip, { label: badge.content, size: "small", color: "primary", sx: { height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS) } })
59
58
  ]
60
59
  },
61
- child.to ?? menuItemLabelOf(child, t)
60
+ child.to ?? child.href ?? menuItemLabelOf(child, t)
62
61
  );
63
62
  })
64
63
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SubmenuActionsMenu.js","sources":["../../src/appShell/SubmenuActionsMenu.jsx"],"sourcesContent":["// El submenú de una entrada como menú de acciones de Apple: un menú flotante con las hijas (ícono, nombre y su badge) y\n// la pantalla abierta marcada. Lo usan el menú lateral encogido (sale a la DERECHA del ícono) y la barra de pestañas del\n// teléfono (sale ARRIBA de la pestaña): un solo lugar para los dos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Chip from '@mui/material/Chip';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\n\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/** Hacia dónde sale el menú: a la derecha del botón (menú lateral) o arriba de él (pestañas del teléfono). */\nexport const SUBMENU_PLACEMENT = Object.freeze({side: 'side', above: 'above'});\n\n// lo que se separa el menú de su botón, lo ancho mínimo y el tamaño del badge (px)\nconst GAP_PIXELS = 12;\nconst MIN_WIDTH_PIXELS = 220;\nconst BADGE_PIXELS = 11;\n\nconst PLACEMENTS = Object.freeze({\n [SUBMENU_PLACEMENT.side]: {\n anchorOrigin: {vertical: 'top', horizontal: 'right'}, transformOrigin: {vertical: 'top', horizontal: 'left'}, gapSx: {ml: `${GAP_PIXELS}px`},\n },\n [SUBMENU_PLACEMENT.above]: {\n anchorOrigin: {vertical: 'top', horizontal: 'center'}, transformOrigin: {vertical: 'bottom', horizontal: 'center'}, gapSx: {mt: `-${GAP_PIXELS}px`},\n },\n});\n\n/**\n * @param {object} props\n * @param {Element|null} props.anchorElement el botón del que cuelga (null = cerrado)\n * @param {() => void} props.onClose\n * @param {object} props.menuItem la entrada con sus hijas (children)\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {() => void} [props.onNavigate] al elegir una hija\n * @param {'side'|'above'} [props.placement] SUBMENU_PLACEMENT; por omisión a la derecha\n */\nexport default function SubmenuActionsMenu({anchorElement, onClose, menuItem, activeItem, menuBadges, onNavigate, placement = SUBMENU_PLACEMENT.side}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const position = PLACEMENTS[placement] ?? PLACEMENTS[SUBMENU_PLACEMENT.side];\n\n return (\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={onClose}\n anchorOrigin={position.anchorOrigin} transformOrigin={position.transformOrigin}\n slotProps={{paper: {sx: {minWidth: MIN_WIDTH_PIXELS, ...position.gapSx}}}}>\n {(menuItem.children ?? []).map((child) => {\n const ChildIcon = child.Icon;\n const badge = menuBadgeOf(child, menuBadges);\n return (\n <MenuItem key={child.to ?? menuItemLabelOf(child, t)} component={RouterLink} to={child.to} selected={child === activeItem}\n onClick={() => {\n onClose();\n onNavigate?.();\n }}>\n {ChildIcon && <ListItemIcon><ChildIcon fontSize=\"small\" /></ListItemIcon>}\n <ListItemText>{menuItemLabelOf(child, t)}</ListItemText>\n {badge && <Chip label={badge.content} size=\"small\" color=\"primary\" sx={{height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS)}} />}\n </MenuItem>\n );\n })}\n </Menu>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAkBO,MAAM,oBAAoB,OAAO,OAAO,EAAC,MAAM,QAAQ,OAAO,SAAQ;AAG7E,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAErB,MAAM,aAAa,OAAO,OAAO;AAAA,EAC7B,CAAC,kBAAkB,IAAI,GAAG;AAAA,IACtB,cAAc,EAAC,UAAU,OAAO,YAAY,QAAA;AAAA,IAAU,iBAAiB,EAAC,UAAU,OAAO,YAAY,OAAA;AAAA,IAAS,OAAO,EAAC,IAAI,GAAG,UAAU,KAAA;AAAA,EAAI;AAAA,EAE/I,CAAC,kBAAkB,KAAK,GAAG;AAAA,IACvB,cAAc,EAAC,UAAU,OAAO,YAAY,SAAA;AAAA,IAAW,iBAAiB,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,IAAW,OAAO,EAAC,IAAI,IAAI,UAAU,KAAA;AAAA,EAAI;AAE1J,CAAC;AAYD,SAAwB,mBAAmB,EAAC,eAAe,SAAS,UAAU,YAAY,YAAY,YAAY,YAAY,kBAAkB,KAAA,GAAO;AACnJ,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAC3B,QAAM,WAAW,WAAW,SAAS,KAAK,WAAW,kBAAkB,IAAI;AAE3E,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAK,UAAU;AAAA,MAAe,MAAM,QAAQ,aAAa;AAAA,MAAG;AAAA,MACvD,cAAc,SAAS;AAAA,MAAc,iBAAiB,SAAS;AAAA,MAC/D,WAAW,EAAC,OAAO,EAAC,IAAI,EAAC,UAAU,kBAAkB,GAAG,SAAS,MAAA,IAAM;AAAA,MACvE,oBAAS,YAAY,CAAA,GAAI,IAAI,CAAC,UAAU;AACtC,cAAM,YAAY,MAAM;AACxB,cAAM,QAAQ,YAAY,OAAO,UAAU;AAC3C,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAAqD,WAAW;AAAA,YAAY,IAAI,MAAM;AAAA,YAAI,UAAU,UAAU;AAAA,YACrG,SAAS,MAAM;AACX,sBAAA;AACA,2BAAA;AAAA,YACJ;AAAA,YACL,UAAA;AAAA,cAAA,iCAAc,cAAA,EAAa,UAAA,oBAAC,WAAA,EAAU,UAAS,SAAQ,GAAE;AAAA,cAC1D,oBAAC,cAAA,EAAc,UAAA,gBAAgB,OAAO,CAAC,GAAE;AAAA,cACxC,6BAAU,MAAA,EAAK,OAAO,MAAM,SAAS,MAAK,SAAQ,OAAM,WAAU,IAAI,EAAC,QAAQ,IAAI,IAAI,KAAK,GAAG,WAAW,YAAY,IAAC,CAAG;AAAA,YAAA;AAAA,UAAA;AAAA,UAPhH,MAAM,MAAM,gBAAgB,OAAO,CAAC;AAAA,QAAA;AAAA,MAU3D,CAAC;AAAA,IAAA;AAAA,EAAA;AAGb;"}
1
+ {"version":3,"file":"SubmenuActionsMenu.js","sources":["../../src/appShell/SubmenuActionsMenu.jsx"],"sourcesContent":["// El submenú de una entrada como menú de acciones de Apple: un menú flotante con las hijas (ícono, nombre y su badge) y\n// la pantalla abierta marcada. Lo usan el menú lateral encogido (sale a la DERECHA del ícono) y la barra de pestañas del\n// teléfono (sale ARRIBA de la pestaña): un solo lugar para los dos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Chip from '@mui/material/Chip';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\nimport {menuBadgeOf} from '@/navigation/appMenu.js';\nimport {textSizeOf} from '@/theme/appleTracking.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/** Hacia dónde sale el menú: a la derecha del botón (menú lateral) o arriba de él (pestañas del teléfono). */\nexport const SUBMENU_PLACEMENT = Object.freeze({side: 'side', above: 'above'});\n\n// lo que se separa el menú de su botón, lo ancho mínimo y el tamaño del badge (px)\nconst GAP_PIXELS = 12;\nconst MIN_WIDTH_PIXELS = 220;\nconst BADGE_PIXELS = 11;\n\nconst PLACEMENTS = Object.freeze({\n [SUBMENU_PLACEMENT.side]: {\n anchorOrigin: {vertical: 'top', horizontal: 'right'}, transformOrigin: {vertical: 'top', horizontal: 'left'}, gapSx: {ml: `${GAP_PIXELS}px`},\n },\n [SUBMENU_PLACEMENT.above]: {\n anchorOrigin: {vertical: 'top', horizontal: 'center'}, transformOrigin: {vertical: 'bottom', horizontal: 'center'}, gapSx: {mt: `-${GAP_PIXELS}px`},\n },\n});\n\n/**\n * @param {object} props\n * @param {Element|null} props.anchorElement el botón del que cuelga (null = cerrado)\n * @param {() => void} props.onClose\n * @param {object} props.menuItem la entrada con sus hijas (children)\n * @param {object|null} props.activeItem la entrada de la pantalla abierta\n * @param {Record<string, number>} [props.menuBadges] los conteos del servidor por badgeKey\n * @param {() => void} [props.onNavigate] al elegir una hija\n * @param {'side'|'above'} [props.placement] SUBMENU_PLACEMENT; por omisión a la derecha\n */\nexport default function SubmenuActionsMenu({anchorElement, onClose, menuItem, activeItem, menuBadges, onNavigate, placement = SUBMENU_PLACEMENT.side}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const position = PLACEMENTS[placement] ?? PLACEMENTS[SUBMENU_PLACEMENT.side];\n\n return (\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={onClose}\n anchorOrigin={position.anchorOrigin} transformOrigin={position.transformOrigin}\n slotProps={{paper: {sx: {minWidth: MIN_WIDTH_PIXELS, ...position.gapSx}}}}>\n {(menuItem.children ?? []).map((child) => {\n const ChildIcon = child.Icon;\n const badge = menuBadgeOf(child, menuBadges);\n return (\n <MenuItem key={child.to ?? child.href ?? menuItemLabelOf(child, t)} {...menuLinkPropsOf(child, RouterLink)} selected={child === activeItem}\n onClick={() => {\n onClose();\n onNavigate?.();\n }}>\n {ChildIcon && <ListItemIcon><ChildIcon fontSize=\"small\" /></ListItemIcon>}\n <ListItemText>{menuItemLabelOf(child, t)}</ListItemText>\n {badge && <Chip label={badge.content} size=\"small\" color=\"primary\" sx={{height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS)}} />}\n </MenuItem>\n );\n })}\n </Menu>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAkBO,MAAM,oBAAoB,OAAO,OAAO,EAAC,MAAM,QAAQ,OAAO,SAAQ;AAG7E,MAAM,aAAa;AACnB,MAAM,mBAAmB;AACzB,MAAM,eAAe;AAErB,MAAM,aAAa,OAAO,OAAO;AAAA,EAC7B,CAAC,kBAAkB,IAAI,GAAG;AAAA,IACtB,cAAc,EAAC,UAAU,OAAO,YAAY,QAAA;AAAA,IAAU,iBAAiB,EAAC,UAAU,OAAO,YAAY,OAAA;AAAA,IAAS,OAAO,EAAC,IAAI,GAAG,UAAU,KAAA;AAAA,EAAI;AAAA,EAE/I,CAAC,kBAAkB,KAAK,GAAG;AAAA,IACvB,cAAc,EAAC,UAAU,OAAO,YAAY,SAAA;AAAA,IAAW,iBAAiB,EAAC,UAAU,UAAU,YAAY,SAAA;AAAA,IAAW,OAAO,EAAC,IAAI,IAAI,UAAU,KAAA;AAAA,EAAI;AAE1J,CAAC;AAYD,SAAwB,mBAAmB,EAAC,eAAe,SAAS,UAAU,YAAY,YAAY,YAAY,YAAY,kBAAkB,KAAA,GAAO;AACnJ,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAC3B,QAAM,WAAW,WAAW,SAAS,KAAK,WAAW,kBAAkB,IAAI;AAE3E,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAK,UAAU;AAAA,MAAe,MAAM,QAAQ,aAAa;AAAA,MAAG;AAAA,MACvD,cAAc,SAAS;AAAA,MAAc,iBAAiB,SAAS;AAAA,MAC/D,WAAW,EAAC,OAAO,EAAC,IAAI,EAAC,UAAU,kBAAkB,GAAG,SAAS,MAAA,IAAM;AAAA,MACvE,oBAAS,YAAY,CAAA,GAAI,IAAI,CAAC,UAAU;AACtC,cAAM,YAAY,MAAM;AACxB,cAAM,QAAQ,YAAY,OAAO,UAAU;AAC3C,eACI;AAAA,UAAC;AAAA,UAAA;AAAA,YAAoE,GAAG,gBAAgB,OAAO,UAAU;AAAA,YAAG,UAAU,UAAU;AAAA,YACtH,SAAS,MAAM;AACX,sBAAA;AACA,2BAAA;AAAA,YACJ;AAAA,YACL,UAAA;AAAA,cAAA,iCAAc,cAAA,EAAa,UAAA,oBAAC,WAAA,EAAU,UAAS,SAAQ,GAAE;AAAA,cAC1D,oBAAC,cAAA,EAAc,UAAA,gBAAgB,OAAO,CAAC,GAAE;AAAA,cACxC,6BAAU,MAAA,EAAK,OAAO,MAAM,SAAS,MAAK,SAAQ,OAAM,WAAU,IAAI,EAAC,QAAQ,IAAI,IAAI,KAAK,GAAG,WAAW,YAAY,IAAC,CAAG;AAAA,YAAA;AAAA,UAAA;AAAA,UAPhH,MAAM,MAAM,MAAM,QAAQ,gBAAgB,OAAO,CAAC;AAAA,QAAA;AAAA,MAUzE,CAAC;AAAA,IAAA;AAAA,EAAA;AAGb;"}
@@ -10,7 +10,7 @@ import ListItemText from "@mui/material/ListItemText";
10
10
  import Menu from "@mui/material/Menu";
11
11
  import MenuItem from "@mui/material/MenuItem";
12
12
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
13
- import { menuItemLabelOf } from "./menuItemText.js";
13
+ import { menuItemLabelOf, menuLinkPropsOf } from "./menuItemText.js";
14
14
  const ACTIVE_UNDERLINE_PIXELS = 2;
15
15
  function TopMenuEntry({ menuItem, activeItem }) {
16
16
  const { t } = useToolkitTranslation("layout");
@@ -31,8 +31,7 @@ function TopMenuEntry({ menuItem, activeItem }) {
31
31
  return /* @__PURE__ */ jsx(
32
32
  Button,
33
33
  {
34
- component: RouterLink,
35
- to: menuItem.to,
34
+ ...menuLinkPropsOf(menuItem, RouterLink),
36
35
  startIcon: Icon ? /* @__PURE__ */ jsx(Icon, { fontSize: "small" }) : void 0,
37
36
  "aria-current": isActive ? "page" : void 0,
38
37
  sx: buttonSx,
@@ -55,10 +54,10 @@ function TopMenuEntry({ menuItem, activeItem }) {
55
54
  ),
56
55
  /* @__PURE__ */ jsx(Menu, { anchorEl: anchorElement, open: Boolean(anchorElement), onClose: () => setAnchorElement(null), slotProps: { paper: { sx: { minWidth: 220, mt: 0.5 } } }, children: menuItem.children.map((child) => {
57
56
  const ChildIcon = child.Icon;
58
- return /* @__PURE__ */ jsxs(MenuItem, { component: RouterLink, to: child.to, selected: child === activeItem, onClick: () => setAnchorElement(null), children: [
57
+ return /* @__PURE__ */ jsxs(MenuItem, { ...menuLinkPropsOf(child, RouterLink), selected: child === activeItem, onClick: () => setAnchorElement(null), children: [
59
58
  ChildIcon && /* @__PURE__ */ jsx(ListItemIcon, { children: /* @__PURE__ */ jsx(ChildIcon, { fontSize: "small" }) }),
60
59
  /* @__PURE__ */ jsx(ListItemText, { primary: menuItemLabelOf(child, t) })
61
- ] }, child.to);
60
+ ] }, child.to ?? child.href);
62
61
  }) })
63
62
  ] });
64
63
  }
@@ -70,7 +69,7 @@ function TopMenu({ menuItems, activeItem }) {
70
69
  component: "nav",
71
70
  "aria-label": t("drawer.ariaLabel"),
72
71
  sx: { display: { xs: "none", md: "flex" }, alignItems: "stretch", alignSelf: "stretch", gap: 0.5, minWidth: 0, overflowX: "auto" },
73
- children: menuItems.map((menuItem) => /* @__PURE__ */ jsx(TopMenuEntry, { menuItem, activeItem }, menuItem.to ?? menuItemLabelOf(menuItem, t)))
72
+ children: menuItems.map((menuItem) => /* @__PURE__ */ jsx(TopMenuEntry, { menuItem, activeItem }, menuItem.to ?? menuItem.href ?? menuItemLabelOf(menuItem, t)))
74
73
  }
75
74
  );
76
75
  }
@@ -1 +1 @@
1
- {"version":3,"file":"TopMenu.js","sources":["../../src/appShell/TopMenu.jsx"],"sourcesContent":["// El menú dentro de la barra (menuPosition=\"top\"), para apps de pocos módulos: cada entrada es un botón; la que tiene\n// submenú abre una lista debajo. Las secciones no se pintan aquí (en una fila no hay dónde); en el teléfono el menú es\n// el mismo cajón del estilo lateral, con sus secciones.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\n\n// la entrada abierta se marca con una raya abajo, del color principal (en la barra naranja, del color del texto)\nconst ACTIVE_UNDERLINE_PIXELS = 2;\n\n/**\n * Un botón del menú de arriba, con su lista si tiene submenú.\n *\n * @param {{menuItem: object, activeItem: object|null}} props\n */\nfunction TopMenuEntry({menuItem, activeItem}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const [anchorElement, setAnchorElement] = useState(null);\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const isActive = menuItem === activeItem || (hasChildren && menuItem.children.includes(activeItem));\n const Icon = menuItem.Icon;\n const buttonSx = {\n color: 'inherit', borderRadius: 1, px: 1.5, whiteSpace: 'nowrap', fontWeight: isActive ? 600 : 500,\n boxShadow: isActive ? `inset 0 -${ACTIVE_UNDERLINE_PIXELS}px 0 currentColor` : 'none',\n };\n\n if (!hasChildren) {\n return (\n <Button component={RouterLink} to={menuItem.to} startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined}\n aria-current={isActive ? 'page' : undefined} sx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </Button>\n );\n }\n return (\n <>\n <Button onClick={(event) => setAnchorElement(event.currentTarget)} aria-haspopup=\"menu\" aria-expanded={Boolean(anchorElement)}\n startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined} endIcon={<ExpandMoreIcon fontSize=\"small\" />} sx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </Button>\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={() => setAnchorElement(null)} slotProps={{paper: {sx: {minWidth: 220, mt: 0.5}}}}>\n {menuItem.children.map((child) => {\n const ChildIcon = child.Icon;\n return (\n <MenuItem key={child.to} component={RouterLink} to={child.to} selected={child === activeItem} onClick={() => setAnchorElement(null)}>\n {ChildIcon && <ListItemIcon><ChildIcon fontSize=\"small\" /></ListItemIcon>}\n <ListItemText primary={menuItemLabelOf(child, t)} />\n </MenuItem>\n );\n })}\n </Menu>\n </>\n );\n}\n\n/**\n * @param {{menuItems: object[], activeItem: object|null}} props\n */\nexport default function TopMenu({menuItems, activeItem}) {\n const {t} = useToolkitTranslation('layout');\n return (\n <Box component=\"nav\" aria-label={t('drawer.ariaLabel')}\n sx={{display: {xs: 'none', md: 'flex'}, alignItems: 'stretch', alignSelf: 'stretch', gap: 0.5, minWidth: 0, overflowX: 'auto'}}>\n {menuItems.map((menuItem) => (\n <TopMenuEntry key={menuItem.to ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} />\n ))}\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAmBA,MAAM,0BAA0B;AAOhC,SAAS,aAAa,EAAC,UAAU,cAAa;AAC1C,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAC3B,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,cAAc,MAAM,QAAQ,SAAS,QAAQ,KAAK,SAAS,SAAS,SAAS;AACnF,QAAM,WAAW,aAAa,cAAe,eAAe,SAAS,SAAS,SAAS,UAAU;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW;AAAA,IACb,OAAO;AAAA,IAAW,cAAc;AAAA,IAAG,IAAI;AAAA,IAAK,YAAY;AAAA,IAAU,YAAY,WAAW,MAAM;AAAA,IAC/F,WAAW,WAAW,YAAY,uBAAuB,sBAAsB;AAAA,EAAA;AAGnF,MAAI,CAAC,aAAa;AACd,WACI;AAAA,MAAC;AAAA,MAAA;AAAA,QAAO,WAAW;AAAA,QAAY,IAAI,SAAS;AAAA,QAAI,WAAW,OAAO,oBAAC,MAAA,EAAK,UAAS,SAAQ,IAAK;AAAA,QACtF,gBAAc,WAAW,SAAS;AAAA,QAAW,IAAI;AAAA,QACpD,UAAA,gBAAgB,UAAU,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EAGxC;AACA,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAO,SAAS,CAAC,UAAU,iBAAiB,MAAM,aAAa;AAAA,QAAG,iBAAc;AAAA,QAAO,iBAAe,QAAQ,aAAa;AAAA,QACpH,WAAW,OAAO,oBAAC,MAAA,EAAK,UAAS,SAAQ,IAAK;AAAA,QAAW,SAAS,oBAAC,gBAAA,EAAe,UAAS,QAAA,CAAQ;AAAA,QAAI,IAAI;AAAA,QAC9G,UAAA,gBAAgB,UAAU,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAEhC,oBAAC,MAAA,EAAK,UAAU,eAAe,MAAM,QAAQ,aAAa,GAAG,SAAS,MAAM,iBAAiB,IAAI,GAAG,WAAW,EAAC,OAAO,EAAC,IAAI,EAAC,UAAU,KAAK,IAAI,IAAA,IAAI,GAC/I,UAAA,SAAS,SAAS,IAAI,CAAC,UAAU;AAC9B,YAAM,YAAY,MAAM;AACxB,aACI,qBAAC,UAAA,EAAwB,WAAW,YAAY,IAAI,MAAM,IAAI,UAAU,UAAU,YAAY,SAAS,MAAM,iBAAiB,IAAI,GAC7H,UAAA;AAAA,QAAA,iCAAc,cAAA,EAAa,UAAA,oBAAC,WAAA,EAAU,UAAS,SAAQ,GAAE;AAAA,4BACzD,cAAA,EAAa,SAAS,gBAAgB,OAAO,CAAC,EAAA,CAAG;AAAA,MAAA,EAAA,GAFvC,MAAM,EAGrB;AAAA,IAER,CAAC,EAAA,CACL;AAAA,EAAA,GACJ;AAER;AAKA,SAAwB,QAAQ,EAAC,WAAW,cAAa;AACrD,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAI,WAAU;AAAA,MAAM,cAAY,EAAE,kBAAkB;AAAA,MAChD,IAAI,EAAC,SAAS,EAAC,IAAI,QAAQ,IAAI,OAAA,GAAS,YAAY,WAAW,WAAW,WAAW,KAAK,KAAK,UAAU,GAAG,WAAW,OAAA;AAAA,MACvH,UAAA,UAAU,IAAI,CAAC,iCACX,cAAA,EAA+D,UAAoB,WAAA,GAAjE,SAAS,MAAM,gBAAgB,UAAU,CAAC,CAA+C,CAC/G;AAAA,IAAA;AAAA,EAAA;AAGb;"}
1
+ {"version":3,"file":"TopMenu.js","sources":["../../src/appShell/TopMenu.jsx"],"sourcesContent":["// El menú dentro de la barra (menuPosition=\"top\"), para apps de pocos módulos: cada entrada es un botón; la que tiene\n// submenú abre una lista debajo. Las secciones no se pintan aquí (en una fila no hay dónde); en el teléfono el menú es\n// el mismo cajón del estilo lateral, con sus secciones.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport ExpandMoreIcon from '@mui/icons-material/ExpandMore';\n\nimport {menuItemLabelOf, menuLinkPropsOf} from '@/appShell/menuItemText.js';\n\n// la entrada abierta se marca con una raya abajo, del color principal (en la barra naranja, del color del texto)\nconst ACTIVE_UNDERLINE_PIXELS = 2;\n\n/**\n * Un botón del menú de arriba, con su lista si tiene submenú.\n *\n * @param {{menuItem: object, activeItem: object|null}} props\n */\nfunction TopMenuEntry({menuItem, activeItem}) {\n const {t} = useToolkitTranslation('layout');\n const {Link: RouterLink} = useToolkitNavigation();\n const [anchorElement, setAnchorElement] = useState(null);\n const hasChildren = Array.isArray(menuItem.children) && menuItem.children.length > 0;\n const isActive = menuItem === activeItem || (hasChildren && menuItem.children.includes(activeItem));\n const Icon = menuItem.Icon;\n const buttonSx = {\n color: 'inherit', borderRadius: 1, px: 1.5, whiteSpace: 'nowrap', fontWeight: isActive ? 600 : 500,\n boxShadow: isActive ? `inset 0 -${ACTIVE_UNDERLINE_PIXELS}px 0 currentColor` : 'none',\n };\n\n if (!hasChildren) {\n return (\n <Button {...menuLinkPropsOf(menuItem, RouterLink)} startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined}\n aria-current={isActive ? 'page' : undefined} sx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </Button>\n );\n }\n return (\n <>\n <Button onClick={(event) => setAnchorElement(event.currentTarget)} aria-haspopup=\"menu\" aria-expanded={Boolean(anchorElement)}\n startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined} endIcon={<ExpandMoreIcon fontSize=\"small\" />} sx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </Button>\n <Menu anchorEl={anchorElement} open={Boolean(anchorElement)} onClose={() => setAnchorElement(null)} slotProps={{paper: {sx: {minWidth: 220, mt: 0.5}}}}>\n {menuItem.children.map((child) => {\n const ChildIcon = child.Icon;\n return (\n <MenuItem key={child.to ?? child.href} {...menuLinkPropsOf(child, RouterLink)} selected={child === activeItem} onClick={() => setAnchorElement(null)}>\n {ChildIcon && <ListItemIcon><ChildIcon fontSize=\"small\" /></ListItemIcon>}\n <ListItemText primary={menuItemLabelOf(child, t)} />\n </MenuItem>\n );\n })}\n </Menu>\n </>\n );\n}\n\n/**\n * @param {{menuItems: object[], activeItem: object|null}} props\n */\nexport default function TopMenu({menuItems, activeItem}) {\n const {t} = useToolkitTranslation('layout');\n return (\n <Box component=\"nav\" aria-label={t('drawer.ariaLabel')}\n sx={{display: {xs: 'none', md: 'flex'}, alignItems: 'stretch', alignSelf: 'stretch', gap: 0.5, minWidth: 0, overflowX: 'auto'}}>\n {menuItems.map((menuItem) => (\n <TopMenuEntry key={menuItem.to ?? menuItem.href ?? menuItemLabelOf(menuItem, t)} menuItem={menuItem} activeItem={activeItem} />\n ))}\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAmBA,MAAM,0BAA0B;AAOhC,SAAS,aAAa,EAAC,UAAU,cAAa;AAC1C,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,EAAC,MAAM,WAAA,IAAc,qBAAA;AAC3B,QAAM,CAAC,eAAe,gBAAgB,IAAI,SAAS,IAAI;AACvD,QAAM,cAAc,MAAM,QAAQ,SAAS,QAAQ,KAAK,SAAS,SAAS,SAAS;AACnF,QAAM,WAAW,aAAa,cAAe,eAAe,SAAS,SAAS,SAAS,UAAU;AACjG,QAAM,OAAO,SAAS;AACtB,QAAM,WAAW;AAAA,IACb,OAAO;AAAA,IAAW,cAAc;AAAA,IAAG,IAAI;AAAA,IAAK,YAAY;AAAA,IAAU,YAAY,WAAW,MAAM;AAAA,IAC/F,WAAW,WAAW,YAAY,uBAAuB,sBAAsB;AAAA,EAAA;AAGnF,MAAI,CAAC,aAAa;AACd,WACI;AAAA,MAAC;AAAA,MAAA;AAAA,QAAQ,GAAG,gBAAgB,UAAU,UAAU;AAAA,QAAG,WAAW,OAAO,oBAAC,MAAA,EAAK,UAAS,SAAQ,IAAK;AAAA,QACzF,gBAAc,WAAW,SAAS;AAAA,QAAW,IAAI;AAAA,QACpD,UAAA,gBAAgB,UAAU,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,EAGxC;AACA,SACI,qBAAA,UAAA,EACI,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAO,SAAS,CAAC,UAAU,iBAAiB,MAAM,aAAa;AAAA,QAAG,iBAAc;AAAA,QAAO,iBAAe,QAAQ,aAAa;AAAA,QACpH,WAAW,OAAO,oBAAC,MAAA,EAAK,UAAS,SAAQ,IAAK;AAAA,QAAW,SAAS,oBAAC,gBAAA,EAAe,UAAS,QAAA,CAAQ;AAAA,QAAI,IAAI;AAAA,QAC9G,UAAA,gBAAgB,UAAU,CAAC;AAAA,MAAA;AAAA,IAAA;AAAA,IAEhC,oBAAC,MAAA,EAAK,UAAU,eAAe,MAAM,QAAQ,aAAa,GAAG,SAAS,MAAM,iBAAiB,IAAI,GAAG,WAAW,EAAC,OAAO,EAAC,IAAI,EAAC,UAAU,KAAK,IAAI,IAAA,IAAI,GAC/I,UAAA,SAAS,SAAS,IAAI,CAAC,UAAU;AAC9B,YAAM,YAAY,MAAM;AACxB,aACI,qBAAC,UAAA,EAAuC,GAAG,gBAAgB,OAAO,UAAU,GAAG,UAAU,UAAU,YAAY,SAAS,MAAM,iBAAiB,IAAI,GAC9I,UAAA;AAAA,QAAA,iCAAc,cAAA,EAAa,UAAA,oBAAC,WAAA,EAAU,UAAS,SAAQ,GAAE;AAAA,4BACzD,cAAA,EAAa,SAAS,gBAAgB,OAAO,CAAC,EAAA,CAAG;AAAA,MAAA,EAAA,GAFvC,MAAM,MAAM,MAAM,IAGjC;AAAA,IAER,CAAC,EAAA,CACL;AAAA,EAAA,GACJ;AAER;AAKA,SAAwB,QAAQ,EAAC,WAAW,cAAa;AACrD,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,SACI;AAAA,IAAC;AAAA,IAAA;AAAA,MAAI,WAAU;AAAA,MAAM,cAAY,EAAE,kBAAkB;AAAA,MAChD,IAAI,EAAC,SAAS,EAAC,IAAI,QAAQ,IAAI,OAAA,GAAS,YAAY,WAAW,WAAW,WAAW,KAAK,KAAK,UAAU,GAAG,WAAW,OAAA;AAAA,MACvH,oBAAU,IAAI,CAAC,aACZ,oBAAC,gBAAgF,UAAoB,WAAA,GAAlF,SAAS,MAAM,SAAS,QAAQ,gBAAgB,UAAU,CAAC,CAA+C,CAChI;AAAA,IAAA;AAAA,EAAA;AAGb;"}
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ import { jsx } from "react/jsx-runtime";
3
+ import Box from "@mui/material/Box";
4
+ const WORDMARK_HEIGHT_PIXELS = 24;
5
+ function Wordmark({ children }) {
6
+ return /* @__PURE__ */ jsx(Box, { sx: { height: WORDMARK_HEIGHT_PIXELS, display: "flex", alignItems: "center", flex: "0 0 auto", "& > img, & > svg": { height: "100%", width: "auto", display: "block" } }, children });
7
+ }
8
+ export {
9
+ Wordmark as default
10
+ };
11
+ //# sourceMappingURL=Wordmark.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Wordmark.js","sources":["../../src/appShell/Wordmark.jsx"],"sourcesContent":["// El logo ancho de la app (la marca con su nombre en una sola imagen, como el de muchas empresas) en la barra y arriba del\n// menú del teléfono: a 24 px de alto y lo ancho que pida, sin deformarse. Una imagen o un svg sueltos toman ese alto solos.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Box from '@mui/material/Box';\n\n// el alto del logo ancho: el de la letra del nombre con su renglón, para que la barra no crezca\nconst WORDMARK_HEIGHT_PIXELS = 24;\n\n/**\n * @param {{children: import('react').ReactNode}} props el logo (una <img>, un <svg> o lo que pinte el proyecto)\n */\nexport default function Wordmark({children}) {\n return (\n <Box sx={{height: WORDMARK_HEIGHT_PIXELS, display: 'flex', alignItems: 'center', flex: '0 0 auto', '& > img, & > svg': {height: '100%', width: 'auto', display: 'block'}}}>\n {children}\n </Box>\n );\n}\n"],"names":[],"mappings":";;;AAOA,MAAM,yBAAyB;AAK/B,SAAwB,SAAS,EAAC,YAAW;AACzC,SACI,oBAAC,OAAI,IAAI,EAAC,QAAQ,wBAAwB,SAAS,QAAQ,YAAY,UAAU,MAAM,YAAY,oBAAoB,EAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,QAAA,KAC3J,UACL;AAER;"}
@@ -23,11 +23,15 @@ function initialsOf(fullName) {
23
23
  const words = String(fullName ?? "").trim().split(/\s+/).filter((word) => Boolean(word) && !NAME_PARTICLES.has(word.toLowerCase()));
24
24
  return words.slice(0, 2).map((word) => word[0]?.toUpperCase() ?? "").join("") || "?";
25
25
  }
26
+ function menuLinkPropsOf(menuItem, RouterLink) {
27
+ return menuItem.href ? { component: "a", href: menuItem.href } : { component: RouterLink, to: menuItem.to };
28
+ }
26
29
  export {
27
30
  initialsOf,
28
31
  menuItemDescriptionOf,
29
32
  menuItemLabelOf,
30
33
  menuItemSectionOf,
34
+ menuLinkPropsOf,
31
35
  menuSectionsOf
32
36
  };
33
37
  //# sourceMappingURL=menuItemText.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"menuItemText.js","sources":["../../src/appShell/menuItemText.js"],"sourcesContent":["// Los textos de una entrada del menú y de la persona en la barra, ya listos para pintarse.\n//\n// Una entrada del menú puede traer su texto escrito (label, description, section) o la llave para traducirlo (labelKey,\n// descriptionKey, sectionKey): con llave, cambia de idioma junto con la app.\n//\n// Funciones puras, sin React: function (regla de la casa). Se prueban con node --test (menuItemText.test.js).\n\n/**\n * El nombre de una entrada del menú.\n *\n * @param {{label?: string, labelKey?: string}} menuItem\n * @param {(key: string) => string} t el traductor de la pantalla\n * @returns {string}\n */\nexport function menuItemLabelOf(menuItem, t) {\n return menuItem.label ?? (menuItem.labelKey ? t(menuItem.labelKey) : '');\n}\n\n/**\n * Para qué sirve una entrada (para las tarjetas de un inicio).\n *\n * @param {{description?: string, descriptionKey?: string}} menuItem\n * @param {(key: string) => string} t\n * @returns {string|undefined}\n */\nexport function menuItemDescriptionOf(menuItem, t) {\n return menuItem.description ?? (menuItem.descriptionKey ? t(menuItem.descriptionKey) : undefined);\n}\n\n/**\n * El título de la sección del menú lateral donde va la entrada («Operación», «Administración»).\n *\n * @param {{section?: string, sectionKey?: string}} menuItem\n * @param {(key: string) => string} t\n * @returns {string|undefined}\n */\nexport function menuItemSectionOf(menuItem, t) {\n return menuItem.section ?? (menuItem.sectionKey ? t(menuItem.sectionKey) : undefined);\n}\n\n/**\n * Las entradas juntas por sección: las secciones salen en el orden en que aparece su primera entrada, y adentro cada\n * entrada en su orden. Las entradas sin sección forman su propio bloque sin título.\n *\n * Por qué por título y no por vecindad: con vecindad, dos módulos de la misma sección con un `order` de otra en medio\n * partían la sección en dos («Interfaz» salía dos veces). Así cada módulo dice su sección sin coordinar números.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {(key: string) => string} t\n * @returns {Array<{title: string|undefined, menuItems: object[]}>}\n */\nexport function menuSectionsOf(menuItems, t) {\n const sectionsByTitle = new Map();\n menuItems.forEach((menuItem) => {\n const title = menuItemSectionOf(menuItem, t);\n if (!sectionsByTitle.has(title)) {\n sectionsByTitle.set(title, {title, menuItems: []});\n }\n sectionsByTitle.get(title).menuItems.push(menuItem);\n });\n return [...sectionsByTitle.values()];\n}\n\n// las partículas de los nombres («María de la Luz», «Juan van Dijk»): no son iniciales. Con ellas, «Usuario de Ejemplo»\n// salía «UD» en el avatar.\nconst NAME_PARTICLES = new Set(['de', 'del', 'la', 'las', 'los', 'y', 'e', 'da', 'das', 'do', 'dos', 'di', 'du', 'van', 'von', 'der', 'le']);\n\n/**\n * Las iniciales para el avatar: «Ana María López» → «AM», «Usuario de Ejemplo» → «UE». Sin nombre, «?».\n *\n * @param {string|null|undefined} fullName\n * @returns {string}\n */\nexport function initialsOf(fullName) {\n const words = String(fullName ?? '').trim().split(/\\s+/).filter((word) => Boolean(word) && !NAME_PARTICLES.has(word.toLowerCase()));\n return words.slice(0, 2).map((word) => word[0]?.toUpperCase() ?? '').join('') || '?';\n}\n"],"names":[],"mappings":"AAcO,SAAS,gBAAgB,UAAU,GAAG;AACzC,SAAO,SAAS,UAAU,SAAS,WAAW,EAAE,SAAS,QAAQ,IAAI;AACzE;AASO,SAAS,sBAAsB,UAAU,GAAG;AAC/C,SAAO,SAAS,gBAAgB,SAAS,iBAAiB,EAAE,SAAS,cAAc,IAAI;AAC3F;AASO,SAAS,kBAAkB,UAAU,GAAG;AAC3C,SAAO,SAAS,YAAY,SAAS,aAAa,EAAE,SAAS,UAAU,IAAI;AAC/E;AAaO,SAAS,eAAe,WAAW,GAAG;AACzC,QAAM,kBAAkB,oBAAI,IAAG;AAC/B,YAAU,QAAQ,CAAC,aAAa;AAC5B,UAAM,QAAQ,kBAAkB,UAAU,CAAC;AAC3C,QAAI,CAAC,gBAAgB,IAAI,KAAK,GAAG;AAC7B,sBAAgB,IAAI,OAAO,EAAC,OAAO,WAAW,CAAA,EAAE,CAAC;AAAA,IACrD;AACA,oBAAgB,IAAI,KAAK,EAAE,UAAU,KAAK,QAAQ;AAAA,EACtD,CAAC;AACD,SAAO,CAAC,GAAG,gBAAgB,QAAQ;AACvC;AAIA,MAAM,iBAAiB,oBAAI,IAAI,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,OAAO,OAAO,OAAO,IAAI,CAAC;AAQpI,SAAS,WAAW,UAAU;AACjC,QAAM,QAAQ,OAAO,YAAY,EAAE,EAAE,KAAI,EAAG,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,QAAQ,IAAI,KAAK,CAAC,eAAe,IAAI,KAAK,YAAW,CAAE,CAAC;AAClI,SAAO,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,CAAC,GAAG,iBAAiB,EAAE,EAAE,KAAK,EAAE,KAAK;AACrF;"}
1
+ {"version":3,"file":"menuItemText.js","sources":["../../src/appShell/menuItemText.js"],"sourcesContent":["// Los textos de una entrada del menú y de la persona en la barra, ya listos para pintarse.\n//\n// Una entrada del menú puede traer su texto escrito (label, description, section) o la llave para traducirlo (labelKey,\n// descriptionKey, sectionKey): con llave, cambia de idioma junto con la app.\n//\n// Funciones puras, sin React: function (regla de la casa). Se prueban con node --test (menuItemText.test.js).\n\n/**\n * El nombre de una entrada del menú.\n *\n * @param {{label?: string, labelKey?: string}} menuItem\n * @param {(key: string) => string} t el traductor de la pantalla\n * @returns {string}\n */\nexport function menuItemLabelOf(menuItem, t) {\n return menuItem.label ?? (menuItem.labelKey ? t(menuItem.labelKey) : '');\n}\n\n/**\n * Para qué sirve una entrada (para las tarjetas de un inicio).\n *\n * @param {{description?: string, descriptionKey?: string}} menuItem\n * @param {(key: string) => string} t\n * @returns {string|undefined}\n */\nexport function menuItemDescriptionOf(menuItem, t) {\n return menuItem.description ?? (menuItem.descriptionKey ? t(menuItem.descriptionKey) : undefined);\n}\n\n/**\n * El título de la sección del menú lateral donde va la entrada («Operación», «Administración»).\n *\n * @param {{section?: string, sectionKey?: string}} menuItem\n * @param {(key: string) => string} t\n * @returns {string|undefined}\n */\nexport function menuItemSectionOf(menuItem, t) {\n return menuItem.section ?? (menuItem.sectionKey ? t(menuItem.sectionKey) : undefined);\n}\n\n/**\n * Las entradas juntas por sección: las secciones salen en el orden en que aparece su primera entrada, y adentro cada\n * entrada en su orden. Las entradas sin sección forman su propio bloque sin título.\n *\n * Por qué por título y no por vecindad: con vecindad, dos módulos de la misma sección con un `order` de otra en medio\n * partían la sección en dos («Interfaz» salía dos veces). Así cada módulo dice su sección sin coordinar números.\n *\n * @param {object[]} menuItems lo que regresa composeMenu\n * @param {(key: string) => string} t\n * @returns {Array<{title: string|undefined, menuItems: object[]}>}\n */\nexport function menuSectionsOf(menuItems, t) {\n const sectionsByTitle = new Map();\n menuItems.forEach((menuItem) => {\n const title = menuItemSectionOf(menuItem, t);\n if (!sectionsByTitle.has(title)) {\n sectionsByTitle.set(title, {title, menuItems: []});\n }\n sectionsByTitle.get(title).menuItems.push(menuItem);\n });\n return [...sectionsByTitle.values()];\n}\n\n// las partículas de los nombres («María de la Luz», «Juan van Dijk»): no son iniciales. Con ellas, «Usuario de Ejemplo»\n// salía «UD» en el avatar.\nconst NAME_PARTICLES = new Set(['de', 'del', 'la', 'las', 'los', 'y', 'e', 'da', 'das', 'do', 'dos', 'di', 'du', 'van', 'von', 'der', 'le']);\n\n/**\n * Las iniciales para el avatar: «Ana María López» → «AM», «Usuario de Ejemplo» → «UE». Sin nombre, «?».\n *\n * @param {string|null|undefined} fullName\n * @returns {string}\n */\nexport function initialsOf(fullName) {\n const words = String(fullName ?? '').trim().split(/\\s+/).filter((word) => Boolean(word) && !NAME_PARTICLES.has(word.toLowerCase()));\n return words.slice(0, 2).map((word) => word[0]?.toUpperCase() ?? '').join('') || '?';\n}\n\n/**\n * Las props de la liga de una entrada: el router para una pantalla de la app (to) y una dirección normal para otro sistema\n * (href: el sistema que ya existía, otra app de la empresa). Con href la página se carga completa, como cualquier liga.\n *\n * @param {{to?: string, href?: string}} menuItem\n * @param {import('react').ElementType} RouterLink el Link de la navegación del proyecto\n * @returns {{component: import('react').ElementType|string, to?: string, href?: string}}\n */\nexport function menuLinkPropsOf(menuItem, RouterLink) {\n return menuItem.href ? {component: 'a', href: menuItem.href} : {component: RouterLink, to: menuItem.to};\n}\n"],"names":[],"mappings":"AAcO,SAAS,gBAAgB,UAAU,GAAG;AACzC,SAAO,SAAS,UAAU,SAAS,WAAW,EAAE,SAAS,QAAQ,IAAI;AACzE;AASO,SAAS,sBAAsB,UAAU,GAAG;AAC/C,SAAO,SAAS,gBAAgB,SAAS,iBAAiB,EAAE,SAAS,cAAc,IAAI;AAC3F;AASO,SAAS,kBAAkB,UAAU,GAAG;AAC3C,SAAO,SAAS,YAAY,SAAS,aAAa,EAAE,SAAS,UAAU,IAAI;AAC/E;AAaO,SAAS,eAAe,WAAW,GAAG;AACzC,QAAM,kBAAkB,oBAAI,IAAG;AAC/B,YAAU,QAAQ,CAAC,aAAa;AAC5B,UAAM,QAAQ,kBAAkB,UAAU,CAAC;AAC3C,QAAI,CAAC,gBAAgB,IAAI,KAAK,GAAG;AAC7B,sBAAgB,IAAI,OAAO,EAAC,OAAO,WAAW,CAAA,EAAE,CAAC;AAAA,IACrD;AACA,oBAAgB,IAAI,KAAK,EAAE,UAAU,KAAK,QAAQ;AAAA,EACtD,CAAC;AACD,SAAO,CAAC,GAAG,gBAAgB,QAAQ;AACvC;AAIA,MAAM,iBAAiB,oBAAI,IAAI,CAAC,MAAM,OAAO,MAAM,OAAO,OAAO,KAAK,KAAK,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,OAAO,OAAO,OAAO,IAAI,CAAC;AAQpI,SAAS,WAAW,UAAU;AACjC,QAAM,QAAQ,OAAO,YAAY,EAAE,EAAE,KAAI,EAAG,MAAM,KAAK,EAAE,OAAO,CAAC,SAAS,QAAQ,IAAI,KAAK,CAAC,eAAe,IAAI,KAAK,YAAW,CAAE,CAAC;AAClI,SAAO,MAAM,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,CAAC,GAAG,iBAAiB,EAAE,EAAE,KAAK,EAAE,KAAK;AACrF;AAUO,SAAS,gBAAgB,UAAU,YAAY;AAClD,SAAO,SAAS,OAAO,EAAC,WAAW,KAAK,MAAM,SAAS,KAAI,IAAI,EAAC,WAAW,YAAY,IAAI,SAAS,GAAE;AAC1G;"}
@@ -8,6 +8,7 @@ const PHONE_MEDIA_QUERY = "@media (max-width: 899.95px), (max-height: 499.95px)"
8
8
  const DESKTOP_MEDIA_QUERY = "@media (min-width: 900px) and (min-height: 500px)";
9
9
  const PHONE_LANDSCAPE_MEDIA_QUERY = "@media (max-height: 499.95px)";
10
10
  const MOBILE_TAB_BAR_HEIGHT_PIXELS = Object.freeze({ portrait: 60, landscape: 46 });
11
+ const FLOATING_BOTTOM_SPACE_CSS_VARIABLE = "--calcita-floating-bottom-space";
11
12
  function aboveMobileTabBarOf(gapPixels) {
12
13
  return {
13
14
  [PHONE_MEDIA_QUERY]: {
@@ -27,6 +28,7 @@ export {
27
28
  DESKTOP_MEDIA_QUERY,
28
29
  DRAWER_WIDTH_COLLAPSED,
29
30
  DRAWER_WIDTH_EXPANDED,
31
+ FLOATING_BOTTOM_SPACE_CSS_VARIABLE,
30
32
  GLASS_PANEL_GAP_PIXELS,
31
33
  MAIN_CONTENT_ID,
32
34
  MOBILE_TAB_BAR_HEIGHT_PIXELS,
@@ -1 +1 @@
1
- {"version":3,"file":"shellSizes.js","sources":["../../src/appShell/shellSizes.js"],"sourcesContent":["// Las medidas y tiempos del marco de la app (AppLayout, del marco de walk-ot), en un solo lugar para que el menú, la barra y\n// el botón flotante se muevan juntos.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {GLASS_INSET_PIXELS} from '@/theme/glassSurface.js';\n\n/** El id del contenido: a donde lleva «Saltar al contenido». */\nexport const MAIN_CONTENT_ID = 'main-content';\n\n/** El menú lateral abierto (ícono y nombre; 240 como las plantillas de MUI) y encogido (solo íconos, el rail). */\nexport const DRAWER_WIDTH_EXPANDED = 240;\nexport const DRAWER_WIDTH_COLLAPSED = 64;\n\n/** El alto de la barra de arriba (sin avisos). */\nexport const APP_BAR_HEIGHT_PIXELS = 56;\n\n/**\n * Con vidrio (createToolkitTheme({glass: true})), el menú lateral y la barra flotan separados de la orilla de la ventana\n * y entre ellos, como los paneles de macOS 26: así el contenido pasa por detrás y el vidrio tiene qué desenfocar. Es la\n * misma separación que el tema les da a los cajones y a la hoja de abajo (GLASS_INSET_PIXELS), para que todo flote igual.\n */\nexport const GLASS_PANEL_GAP_PIXELS = GLASS_INSET_PIXELS;\n\n/**\n * Teléfono o escritorio. Teléfono: angosto (menos de 900 px, el «md» de MUI) O bajito (menos de 500 px de alto: un teléfono\n * acostado). Solo con el ancho, un iPhone Pro Max acostado (932 × 430) salía con el marco de escritorio y su menú lateral\n * no cabía (lo reportaron). Van como media queries para usarse en sx: [PHONE_MEDIA_QUERY]: {...}.\n */\nexport const PHONE_MEDIA_QUERY = '@media (max-width: 899.95px), (max-height: 499.95px)';\nexport const DESKTOP_MEDIA_QUERY = '@media (min-width: 900px) and (min-height: 500px)';\n/** El teléfono acostado: la barra de pestañas va compacta (ícono y nombre lado a lado) para dejarle el alto al contenido. */\nexport const PHONE_LANDSCAPE_MEDIA_QUERY = '@media (max-height: 499.95px)';\n\n/** La barra de pestañas del teléfono con vidrio (MobileTabBar): su alto parado y acostado. */\nexport const MOBILE_TAB_BAR_HEIGHT_PIXELS = Object.freeze({portrait: 60, landscape: 46});\n\n/**\n * Lo que flota abajo al centro en el teléfono con vidrio (el aviso de versión nueva, la invitación a instalar): encima de\n * la barra de pestañas, con la separación de la barra a la orilla, su alto, la separación pedida y la zona del gesto de\n * inicio del iPhone. Para el sx: ...aboveMobileTabBarOf(8).\n *\n * @param {number} gapPixels lo que se separa de la barra de pestañas (px)\n * @returns {object}\n */\nexport function aboveMobileTabBarOf(gapPixels) {\n return {\n [PHONE_MEDIA_QUERY]: {\n top: 'auto',\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n };\n}\n\n/**\n * La transición que comparten el menú, la barra y el botón flotante: si cada uno usara la suya, terminarían en frames\n * distintos y el ojo lo lee como un salto.\n *\n * @param {object} theme el tema de MUI\n * @param {string[]} [properties] lo que se anima\n * @returns {string}\n */\nexport function drawerTransitionOf(theme, properties = ['width']) {\n return theme.transitions.create(properties, {easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen});\n}\n"],"names":[],"mappings":";AAOY,MAAC,kBAAkB;AAGnB,MAAC,wBAAwB;AACzB,MAAC,yBAAyB;AAG1B,MAAC,wBAAwB;AAOzB,MAAC,yBAAyB;AAO1B,MAAC,oBAAoB;AACrB,MAAC,sBAAsB;AAEvB,MAAC,8BAA8B;AAG/B,MAAC,+BAA+B,OAAO,OAAO,EAAC,UAAU,IAAI,WAAW,GAAE,CAAC;AAUhF,SAAS,oBAAoB,WAAW;AAC3C,SAAO;AAAA,IACH,CAAC,iBAAiB,GAAG;AAAA,MACjB,KAAK;AAAA,MACL,QAAQ,QAAQ,yBAAyB,6BAA6B,WAAW,SAAS;AAAA,IACtG;AAAA,IACQ,CAAC,2BAA2B,GAAG;AAAA,MAC3B,QAAQ,QAAQ,yBAAyB,6BAA6B,YAAY,SAAS;AAAA,IACvG;AAAA,EACA;AACA;AAUO,SAAS,mBAAmB,OAAO,aAAa,CAAC,OAAO,GAAG;AAC9D,SAAO,MAAM,YAAY,OAAO,YAAY,EAAC,QAAQ,MAAM,YAAY,OAAO,OAAO,UAAU,MAAM,YAAY,SAAS,eAAc,CAAC;AAC7I;"}
1
+ {"version":3,"file":"shellSizes.js","sources":["../../src/appShell/shellSizes.js"],"sourcesContent":["// Las medidas y tiempos del marco de la app (AppLayout, del marco de walk-ot), en un solo lugar para que el menú, la barra y\n// el botón flotante se muevan juntos.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {GLASS_INSET_PIXELS} from '@/theme/glassSurface.js';\n\n/** El id del contenido: a donde lleva «Saltar al contenido». */\nexport const MAIN_CONTENT_ID = 'main-content';\n\n/** El menú lateral abierto (ícono y nombre; 240 como las plantillas de MUI) y encogido (solo íconos, el rail). */\nexport const DRAWER_WIDTH_EXPANDED = 240;\nexport const DRAWER_WIDTH_COLLAPSED = 64;\n\n/** El alto de la barra de arriba (sin avisos). */\nexport const APP_BAR_HEIGHT_PIXELS = 56;\n\n/**\n * Con vidrio (createToolkitTheme({glass: true})), el menú lateral y la barra flotan separados de la orilla de la ventana\n * y entre ellos, como los paneles de macOS 26: así el contenido pasa por detrás y el vidrio tiene qué desenfocar. Es la\n * misma separación que el tema les da a los cajones y a la hoja de abajo (GLASS_INSET_PIXELS), para que todo flote igual.\n */\nexport const GLASS_PANEL_GAP_PIXELS = GLASS_INSET_PIXELS;\n\n/**\n * Teléfono o escritorio. Teléfono: angosto (menos de 900 px, el «md» de MUI) O bajito (menos de 500 px de alto: un teléfono\n * acostado). Solo con el ancho, un iPhone Pro Max acostado (932 × 430) salía con el marco de escritorio y su menú lateral\n * no cabía (lo reportaron). Van como media queries para usarse en sx: [PHONE_MEDIA_QUERY]: {...}.\n */\nexport const PHONE_MEDIA_QUERY = '@media (max-width: 899.95px), (max-height: 499.95px)';\nexport const DESKTOP_MEDIA_QUERY = '@media (min-width: 900px) and (min-height: 500px)';\n/** El teléfono acostado: la barra de pestañas va compacta (ícono y nombre lado a lado) para dejarle el alto al contenido. */\nexport const PHONE_LANDSCAPE_MEDIA_QUERY = '@media (max-height: 499.95px)';\n\n/** La barra de pestañas del teléfono con vidrio (MobileTabBar): su alto parado y acostado. */\nexport const MOBILE_TAB_BAR_HEIGHT_PIXELS = Object.freeze({portrait: 60, landscape: 46});\n\n// Lo que flota abajo encima de la barra de pestañas y dura un rato (las acciones de los elegidos de una tabla) pide su alto\n// aquí mientras se ve: el marco lo suma al espacio de abajo del contenido y el final de la página (el paginador) no queda\n// tapado. Lo pone quien flota con GlobalStyles; sin nada, vale 0.\nexport const FLOATING_BOTTOM_SPACE_CSS_VARIABLE = '--calcita-floating-bottom-space';\n\n/**\n * Lo que flota abajo al centro en el teléfono con vidrio (el aviso de versión nueva, la invitación a instalar): encima de\n * la barra de pestañas, con la separación de la barra a la orilla, su alto, la separación pedida y la zona del gesto de\n * inicio del iPhone. Para el sx: ...aboveMobileTabBarOf(8).\n *\n * @param {number} gapPixels lo que se separa de la barra de pestañas (px)\n * @returns {object}\n */\nexport function aboveMobileTabBarOf(gapPixels) {\n return {\n [PHONE_MEDIA_QUERY]: {\n top: 'auto',\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n };\n}\n\n/**\n * La transición que comparten el menú, la barra y el botón flotante: si cada uno usara la suya, terminarían en frames\n * distintos y el ojo lo lee como un salto.\n *\n * @param {object} theme el tema de MUI\n * @param {string[]} [properties] lo que se anima\n * @returns {string}\n */\nexport function drawerTransitionOf(theme, properties = ['width']) {\n return theme.transitions.create(properties, {easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen});\n}\n"],"names":[],"mappings":";AAOY,MAAC,kBAAkB;AAGnB,MAAC,wBAAwB;AACzB,MAAC,yBAAyB;AAG1B,MAAC,wBAAwB;AAOzB,MAAC,yBAAyB;AAO1B,MAAC,oBAAoB;AACrB,MAAC,sBAAsB;AAEvB,MAAC,8BAA8B;AAG/B,MAAC,+BAA+B,OAAO,OAAO,EAAC,UAAU,IAAI,WAAW,GAAE,CAAC;AAK3E,MAAC,qCAAqC;AAU3C,SAAS,oBAAoB,WAAW;AAC3C,SAAO;AAAA,IACH,CAAC,iBAAiB,GAAG;AAAA,MACjB,KAAK;AAAA,MACL,QAAQ,QAAQ,yBAAyB,6BAA6B,WAAW,SAAS;AAAA,IACtG;AAAA,IACQ,CAAC,2BAA2B,GAAG;AAAA,MAC3B,QAAQ,QAAQ,yBAAyB,6BAA6B,YAAY,SAAS;AAAA,IACvG;AAAA,EACA;AACA;AAUO,SAAS,mBAAmB,OAAO,aAAa,CAAC,OAAO,GAAG;AAC9D,SAAO,MAAM,YAAY,OAAO,YAAY,EAAC,QAAQ,MAAM,YAAY,OAAO,OAAO,UAAU,MAAM,YAAY,SAAS,eAAc,CAAC;AAC7I;"}
@@ -17,8 +17,8 @@ const SCREEN_READER_ONLY_SX = {
17
17
  whiteSpace: "nowrap",
18
18
  border: 0
19
19
  };
20
- function PasswordChecklist({ password, policy = DEFAULT_PASSWORD_POLICY, id }) {
21
- const { t } = useToolkitTranslation("auth");
20
+ function PasswordChecklist({ password, policy = DEFAULT_PASSWORD_POLICY, id, texts }) {
21
+ const { t } = useToolkitTranslation("auth", texts);
22
22
  return /* @__PURE__ */ jsxs(Box, { id, children: [
23
23
  /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", component: "p", sx: { mb: 0.5 }, children: t("passwordRules.title") }),
24
24
  /* @__PURE__ */ jsx(Box, { component: "ul", sx: { listStyle: "none", m: 0, p: 0, display: "grid", gap: 0.5 }, children: passwordChecksOf(password, policy).map((check) => /* @__PURE__ */ jsxs(
@@ -1 +1 @@
1
- {"version":3,"file":"PasswordChecklist.js","sources":["../../src/auth/PasswordChecklist.jsx"],"sourcesContent":["// La lista de lo que debe tener una contraseña nueva, que se va palomeando mientras la persona escribe: dice qué falta\n// ANTES de enviar, en lugar de un «contraseña inválida» después.\n//\n// Para el lector de pantalla cada renglón dice si ya cumple o falta (con el ícono solo, se oía la regla sin saber cómo\n// iba). Va justo debajo del campo de la contraseña nueva.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';\n\nimport {DEFAULT_PASSWORD_POLICY, passwordChecksOf} from '@/auth/passwordRules.js';\n\n// texto que solo oye el lector de pantalla (el mismo truco que usa MUI)\nconst SCREEN_READER_ONLY_SX = {\n position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap', border: 0,\n};\n\n/**\n * @param {object} props\n * @param {string} props.password lo que lleva escrito la persona\n * @param {object} [props.policy] las reglas; por omisión DEFAULT_PASSWORD_POLICY\n * @param {string} [props.id]\n */\nexport default function PasswordChecklist({password, policy = DEFAULT_PASSWORD_POLICY, id}) {\n const {t} = useToolkitTranslation('auth');\n return (\n <Box id={id}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{mb: 0.5}}>{t('passwordRules.title')}</Typography>\n <Box component=\"ul\" sx={{listStyle: 'none', m: 0, p: 0, display: 'grid', gap: 0.5}}>\n {passwordChecksOf(password, policy).map((check) => (\n <Box component=\"li\" key={check.rule} data-rule={check.rule} data-met={check.isMet}\n sx={{display: 'flex', alignItems: 'center', gap: 1, color: check.isMet ? 'text.primary' : 'text.secondary'}}>\n {check.isMet\n ? <CheckCircleIcon fontSize=\"small\" color=\"success\" aria-hidden />\n : <RadioButtonUncheckedIcon fontSize=\"small\" color=\"disabled\" aria-hidden />}\n <Typography variant=\"body2\" component=\"span\">{t(`passwordRules.${check.rule}`, check.values)}</Typography>\n <Box component=\"span\" sx={SCREEN_READER_ONLY_SX}>{check.isMet ? t('passwordRules.met') : t('passwordRules.missing')}</Box>\n </Box>\n ))}\n </Box>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAgBA,MAAM,wBAAwB;AAAA,EAC1B,UAAU;AAAA,EAAY,OAAO;AAAA,EAAO,QAAQ;AAAA,EAAO,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAQ,UAAU;AAAA,EAAU,MAAM;AAAA,EAAiB,YAAY;AAAA,EAAU,QAAQ;AAC5J;AAQA,SAAwB,kBAAkB,EAAC,UAAU,SAAS,yBAAyB,MAAK;AACxF,QAAM,EAAC,EAAA,IAAK,sBAAsB,MAAM;AACxC,SACI,qBAAC,OAAI,IACD,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,WAAU,KAAI,IAAI,EAAC,IAAI,IAAA,GAAO,UAAA,EAAE,qBAAqB,GAAE;AAAA,IAC5G,oBAAC,OAAI,WAAU,MAAK,IAAI,EAAC,WAAW,QAAQ,GAAG,GAAG,GAAG,GAAG,SAAS,QAAQ,KAAK,IAAA,GACzE,UAAA,iBAAiB,UAAU,MAAM,EAAE,IAAI,CAAC,UACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAI,WAAU;AAAA,QAAsB,aAAW,MAAM;AAAA,QAAM,YAAU,MAAM;AAAA,QACvE,IAAI,EAAC,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,OAAO,MAAM,QAAQ,iBAAiB,iBAAA;AAAA,QAC1F,UAAA;AAAA,UAAA,MAAM,QACD,oBAAC,iBAAA,EAAgB,UAAS,SAAQ,OAAM,WAAU,eAAW,KAAA,CAAC,wBAC7D,0BAAA,EAAyB,UAAS,SAAQ,OAAM,YAAW,eAAW,MAAC;AAAA,UAC9E,oBAAC,YAAA,EAAW,SAAQ,SAAQ,WAAU,QAAQ,UAAA,EAAE,iBAAiB,MAAM,IAAI,IAAI,MAAM,MAAM,GAAE;AAAA,UAC7F,oBAAC,KAAA,EAAI,WAAU,QAAO,IAAI,uBAAwB,UAAA,MAAM,QAAQ,EAAE,mBAAmB,IAAI,EAAE,uBAAuB,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,MAN/F,MAAM;AAAA,IAAA,CAQlC,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"PasswordChecklist.js","sources":["../../src/auth/PasswordChecklist.jsx"],"sourcesContent":["// La lista de lo que debe tener una contraseña nueva, que se va palomeando mientras la persona escribe: dice qué falta\n// ANTES de enviar, en lugar de un «contraseña inválida» después.\n//\n// Para el lector de pantalla cada renglón dice si ya cumple o falta (con el ícono solo, se oía la regla sin saber cómo\n// iba). Va justo debajo del campo de la contraseña nueva.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';\n\nimport {DEFAULT_PASSWORD_POLICY, passwordChecksOf} from '@/auth/passwordRules.js';\n\n// texto que solo oye el lector de pantalla (el mismo truco que usa MUI)\nconst SCREEN_READER_ONLY_SX = {\n position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap', border: 0,\n};\n\n/**\n * @param {object} props\n * @param {string} props.password lo que lleva escrito la persona\n * @param {object} [props.policy] las reglas; por omisión DEFAULT_PASSWORD_POLICY\n * @param {string} [props.id]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function PasswordChecklist({password, policy = DEFAULT_PASSWORD_POLICY, id, texts}) {\n const {t} = useToolkitTranslation('auth', texts);\n return (\n <Box id={id}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{mb: 0.5}}>{t('passwordRules.title')}</Typography>\n <Box component=\"ul\" sx={{listStyle: 'none', m: 0, p: 0, display: 'grid', gap: 0.5}}>\n {passwordChecksOf(password, policy).map((check) => (\n <Box component=\"li\" key={check.rule} data-rule={check.rule} data-met={check.isMet}\n sx={{display: 'flex', alignItems: 'center', gap: 1, color: check.isMet ? 'text.primary' : 'text.secondary'}}>\n {check.isMet\n ? <CheckCircleIcon fontSize=\"small\" color=\"success\" aria-hidden />\n : <RadioButtonUncheckedIcon fontSize=\"small\" color=\"disabled\" aria-hidden />}\n <Typography variant=\"body2\" component=\"span\">{t(`passwordRules.${check.rule}`, check.values)}</Typography>\n <Box component=\"span\" sx={SCREEN_READER_ONLY_SX}>{check.isMet ? t('passwordRules.met') : t('passwordRules.missing')}</Box>\n </Box>\n ))}\n </Box>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAgBA,MAAM,wBAAwB;AAAA,EAC1B,UAAU;AAAA,EAAY,OAAO;AAAA,EAAO,QAAQ;AAAA,EAAO,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAQ,UAAU;AAAA,EAAU,MAAM;AAAA,EAAiB,YAAY;AAAA,EAAU,QAAQ;AAC5J;AASA,SAAwB,kBAAkB,EAAC,UAAU,SAAS,yBAAyB,IAAI,SAAQ;AAC/F,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,SACI,qBAAC,OAAI,IACD,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,WAAU,KAAI,IAAI,EAAC,IAAI,IAAA,GAAO,UAAA,EAAE,qBAAqB,GAAE;AAAA,IAC5G,oBAAC,OAAI,WAAU,MAAK,IAAI,EAAC,WAAW,QAAQ,GAAG,GAAG,GAAG,GAAG,SAAS,QAAQ,KAAK,IAAA,GACzE,UAAA,iBAAiB,UAAU,MAAM,EAAE,IAAI,CAAC,UACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAI,WAAU;AAAA,QAAsB,aAAW,MAAM;AAAA,QAAM,YAAU,MAAM;AAAA,QACvE,IAAI,EAAC,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,OAAO,MAAM,QAAQ,iBAAiB,iBAAA;AAAA,QAC1F,UAAA;AAAA,UAAA,MAAM,QACD,oBAAC,iBAAA,EAAgB,UAAS,SAAQ,OAAM,WAAU,eAAW,KAAA,CAAC,wBAC7D,0BAAA,EAAyB,UAAS,SAAQ,OAAM,YAAW,eAAW,MAAC;AAAA,UAC9E,oBAAC,YAAA,EAAW,SAAQ,SAAQ,WAAU,QAAQ,UAAA,EAAE,iBAAiB,MAAM,IAAI,IAAI,MAAM,MAAM,GAAE;AAAA,UAC7F,oBAAC,KAAA,EAAI,WAAU,QAAO,IAAI,uBAAwB,UAAA,MAAM,QAAQ,EAAE,mBAAmB,IAAI,EAAE,uBAAuB,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,MAN/F,MAAM;AAAA,IAAA,CAQlC,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
@@ -61,11 +61,16 @@ function EventCalendar({
61
61
  onDateSelect,
62
62
  onEventChange,
63
63
  height = "auto",
64
- ariaLabel
64
+ ariaLabel,
65
+ onDatesChange,
66
+ onViewChange,
67
+ renderEvent,
68
+ fullCalendarProps,
69
+ texts
65
70
  }) {
66
71
  const theme = useTheme();
67
72
  const palette = paletteOf(theme);
68
- const { t } = useToolkitTranslation("calendar");
73
+ const { t } = useToolkitTranslation("calendar", texts);
69
74
  const { language } = useToolkitSettings();
70
75
  const calendarRef = useRef(null);
71
76
  const [title, setTitle] = useState("");
@@ -103,20 +108,26 @@ function EventCalendar({
103
108
  /* @__PURE__ */ jsx(Box, { sx: calendarStylesOf, children: /* @__PURE__ */ jsx(
104
109
  FullCalendar,
105
110
  {
111
+ locale: language === "es" ? spanishLocale : "en",
112
+ firstDay: MONDAY,
113
+ ...fullCalendarProps,
106
114
  ref: calendarRef,
107
- plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
115
+ plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, ...fullCalendarProps?.plugins ?? []],
108
116
  initialView,
109
117
  headerToolbar: false,
110
- locale: language === "es" ? spanishLocale : "en",
111
- firstDay: MONDAY,
112
118
  height,
113
119
  nowIndicator: true,
114
120
  dayMaxEvents: DAY_MAX_EVENTS,
115
121
  events: coloredEvents,
116
122
  eventDisplay: "block",
123
+ eventContent: renderEvent ? (contentInfo) => renderEvent(events.find((event) => event.id === contentInfo.event.id) ?? contentInfo.event) : void 0,
117
124
  datesSet: (dateInfo) => {
118
125
  setTitle(dateInfo.view.title);
126
+ if (dateInfo.view.type !== view) {
127
+ onViewChange?.(dateInfo.view.type);
128
+ }
119
129
  setView(dateInfo.view.type);
130
+ onDatesChange?.({ start: dateInfo.startStr, end: dateInfo.endStr, view: dateInfo.view.type });
120
131
  },
121
132
  eventClick: onEventClick ? (clickInfo) => onEventClick(clickInfo.event.id) : void 0,
122
133
  selectable: Boolean(onDateSelect),