@stackcraftmx/calcita 0.27.0 → 0.30.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 (298) hide show
  1. package/README.md +149 -0
  2. package/dist/DataTable/AuditTrailDialog.js +18 -14
  3. package/dist/DataTable/AuditTrailDialog.js.map +1 -1
  4. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +12 -6
  5. package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
  6. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +8 -3
  7. package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
  8. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +26 -18
  9. package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
  10. package/dist/DataTable/EmptyState.js +8 -3
  11. package/dist/DataTable/EmptyState.js.map +1 -1
  12. package/dist/DataTable/FilterChips.js +11 -5
  13. package/dist/DataTable/FilterChips.js.map +1 -1
  14. package/dist/DataTable/GoToPage.js +9 -4
  15. package/dist/DataTable/GoToPage.js.map +1 -1
  16. package/dist/DataTable/ServerTable.js +74 -61
  17. package/dist/DataTable/ServerTable.js.map +1 -1
  18. package/dist/DataTable/columns.js +1 -8
  19. package/dist/DataTable/columns.js.map +1 -1
  20. package/dist/access/PermissionMatrix.js +18 -9
  21. package/dist/access/PermissionMatrix.js.map +1 -1
  22. package/dist/account/AccountSecuritySection.js +8 -3
  23. package/dist/account/AccountSecuritySection.js.map +1 -1
  24. package/dist/account/DemoTotpCodeButton.js +8 -3
  25. package/dist/account/DemoTotpCodeButton.js.map +1 -1
  26. package/dist/account/ProfileScreen.js +19 -13
  27. package/dist/account/ProfileScreen.js.map +1 -1
  28. package/dist/account/SignInScreen.js +6 -2
  29. package/dist/account/SignInScreen.js.map +1 -1
  30. package/dist/appShell/AccountMenu.js +13 -5
  31. package/dist/appShell/AccountMenu.js.map +1 -1
  32. package/dist/appShell/AppLayout.js +100 -71
  33. package/dist/appShell/AppLayout.js.map +1 -1
  34. package/dist/appShell/AppearanceSettings.js +21 -13
  35. package/dist/appShell/AppearanceSettings.js.map +1 -1
  36. package/dist/appShell/AuthLayout.js +26 -13
  37. package/dist/appShell/AuthLayout.js.map +1 -1
  38. package/dist/appShell/BarActions.js +10 -5
  39. package/dist/appShell/BarActions.js.map +1 -1
  40. package/dist/appShell/BarCapsule.js +9 -4
  41. package/dist/appShell/BarCapsule.js.map +1 -1
  42. package/dist/appShell/BarSearch.js +7 -3
  43. package/dist/appShell/BarSearch.js.map +1 -1
  44. package/dist/appShell/HeaderTools.js +20 -15
  45. package/dist/appShell/HeaderTools.js.map +1 -1
  46. package/dist/appShell/MobileTabBar.js +25 -14
  47. package/dist/appShell/MobileTabBar.js.map +1 -1
  48. package/dist/appShell/SettingsMenu.js +7 -3
  49. package/dist/appShell/SettingsMenu.js.map +1 -1
  50. package/dist/appShell/SideMenu.js +53 -35
  51. package/dist/appShell/SideMenu.js.map +1 -1
  52. package/dist/appShell/SideMenuEntry.js +18 -9
  53. package/dist/appShell/SideMenuEntry.js.map +1 -1
  54. package/dist/appShell/SubmenuActionsMenu.js +6 -2
  55. package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
  56. package/dist/appShell/TabBarEditor.js +16 -7
  57. package/dist/appShell/TabBarEditor.js.map +1 -1
  58. package/dist/appShell/TopMenu.js +12 -7
  59. package/dist/appShell/TopMenu.js.map +1 -1
  60. package/dist/appShell/Wordmark.js +6 -2
  61. package/dist/appShell/Wordmark.js.map +1 -1
  62. package/dist/auth/PasswordChecklist.js +14 -7
  63. package/dist/auth/PasswordChecklist.js.map +1 -1
  64. package/dist/calendar/EventCalendar.js +26 -19
  65. package/dist/calendar/EventCalendar.js.map +1 -1
  66. package/dist/calendar/EventDetails.js +28 -13
  67. package/dist/calendar/EventDetails.js.map +1 -1
  68. package/dist/downloads/DownloadManager.js +34 -18
  69. package/dist/downloads/DownloadManager.js.map +1 -1
  70. package/dist/feedback/ConfirmDialog.js +22 -17
  71. package/dist/feedback/ConfirmDialog.js.map +1 -1
  72. package/dist/feedback/ModalWaiting.js +12 -5
  73. package/dist/feedback/ModalWaiting.js.map +1 -1
  74. package/dist/feedback/NewBuildNotice.js +16 -8
  75. package/dist/feedback/NewBuildNotice.js.map +1 -1
  76. package/dist/filepond/FileDropzone.js +39 -31
  77. package/dist/filepond/FileDropzone.js.map +1 -1
  78. package/dist/forms/FormProblemAlert.js +4 -1
  79. package/dist/forms/FormProblemAlert.js.map +1 -1
  80. package/dist/forms/auth/SignInForm.js +6 -2
  81. package/dist/forms/auth/SignInForm.js.map +1 -1
  82. package/dist/forms/security/SecondStepForm.js +6 -2
  83. package/dist/forms/security/SecondStepForm.js.map +1 -1
  84. package/dist/forms/security/TwoStepVerificationCard.js +19 -9
  85. package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
  86. package/dist/hookForm/FormProblemAlert.js +4 -1
  87. package/dist/hookForm/FormProblemAlert.js.map +1 -1
  88. package/dist/inbox/InboxLayout.js +47 -29
  89. package/dist/inbox/InboxLayout.js.map +1 -1
  90. package/dist/inbox/MessageList.js +53 -32
  91. package/dist/inbox/MessageList.js.map +1 -1
  92. package/dist/inbox/MessageView.js +31 -15
  93. package/dist/inbox/MessageView.js.map +1 -1
  94. package/dist/index.js +7 -1
  95. package/dist/index.js.map +1 -1
  96. package/dist/inputs/ColorInput.js +22 -17
  97. package/dist/inputs/ColorInput.js.map +1 -1
  98. package/dist/inputs/FormSection.js +66 -63
  99. package/dist/inputs/FormSection.js.map +1 -1
  100. package/dist/inputs/SelectInput.js +21 -16
  101. package/dist/inputs/SelectInput.js.map +1 -1
  102. package/dist/inputs/TextInput.js +25 -20
  103. package/dist/inputs/TextInput.js.map +1 -1
  104. package/dist/inputs/WizardLayout.js +21 -15
  105. package/dist/inputs/WizardLayout.js.map +1 -1
  106. package/dist/kanban/KanbanBoard.js +82 -50
  107. package/dist/kanban/KanbanBoard.js.map +1 -1
  108. package/dist/maps/MapView.js +19 -14
  109. package/dist/maps/MapView.js.map +1 -1
  110. package/dist/pwa/InstallAppSetting.js +4 -1
  111. package/dist/pwa/InstallAppSetting.js.map +1 -1
  112. package/dist/pwa/InstallInstructionsDialog.js +6 -2
  113. package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
  114. package/dist/pwa/InstallPromptBanner.js +10 -5
  115. package/dist/pwa/InstallPromptBanner.js.map +1 -1
  116. package/dist/security/ActivityLog.js +6 -2
  117. package/dist/security/ActivityLog.js.map +1 -1
  118. package/dist/security/RecoveryCodesDialog.js +8 -3
  119. package/dist/security/RecoveryCodesDialog.js.map +1 -1
  120. package/dist/security/SessionsCard.js +12 -5
  121. package/dist/security/SessionsCard.js.map +1 -1
  122. package/dist/theme/components/glassComponents.js +19 -16
  123. package/dist/theme/components/glassComponents.js.map +1 -1
  124. package/dist/theme/components/minimalComponents.js +10 -6
  125. package/dist/theme/components/minimalComponents.js.map +1 -1
  126. package/dist/theme/createToolkitTheme.js +23 -6
  127. package/dist/theme/createToolkitTheme.js.map +1 -1
  128. package/dist/theme/cssTokens.js +23 -0
  129. package/dist/theme/cssTokens.js.map +1 -0
  130. package/dist/theme/glassSurface.js +64 -10
  131. package/dist/theme/glassSurface.js.map +1 -1
  132. package/dist/theme/glassToasterStyles.js +2 -3
  133. package/dist/theme/glassToasterStyles.js.map +1 -1
  134. package/dist/theme/themePieces.js +16 -0
  135. package/dist/theme/themePieces.js.map +1 -0
  136. package/dist/types/DataTable/AuditTrailDialog.d.ts +1 -1
  137. package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +1 -1
  138. package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +1 -1
  139. package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +1 -1
  140. package/dist/types/DataTable/EmptyState.d.ts +1 -1
  141. package/dist/types/DataTable/FilterChips.d.ts +1 -1
  142. package/dist/types/DataTable/GoToPage.d.ts +1 -1
  143. package/dist/types/DataTable/ServerTable.d.ts +1 -1
  144. package/dist/types/access/PermissionMatrix.d.ts +1 -1
  145. package/dist/types/account/AccountSecuritySection.d.ts +1 -1
  146. package/dist/types/account/DemoTotpCodeButton.d.ts +1 -1
  147. package/dist/types/account/ProfileScreen.d.ts +1 -1
  148. package/dist/types/account/SignInScreen.d.ts +1 -1
  149. package/dist/types/appShell/AccountMenu.d.ts +1 -1
  150. package/dist/types/appShell/AppLayout.d.ts +1 -1
  151. package/dist/types/appShell/AppearanceSettings.d.ts +1 -1
  152. package/dist/types/appShell/AuthLayout.d.ts +1 -1
  153. package/dist/types/appShell/BarActions.d.ts +1 -1
  154. package/dist/types/appShell/BarCapsule.d.ts +1 -1
  155. package/dist/types/appShell/BarSearch.d.ts +1 -1
  156. package/dist/types/appShell/HeaderTools.d.ts +1 -1
  157. package/dist/types/appShell/MobileTabBar.d.ts +1 -1
  158. package/dist/types/appShell/SettingsMenu.d.ts +1 -1
  159. package/dist/types/appShell/SideMenu.d.ts +1 -1
  160. package/dist/types/appShell/SideMenuEntry.d.ts +1 -1
  161. package/dist/types/appShell/SubmenuActionsMenu.d.ts +1 -1
  162. package/dist/types/appShell/TabBarEditor.d.ts +1 -1
  163. package/dist/types/appShell/TopMenu.d.ts +1 -1
  164. package/dist/types/appShell/Wordmark.d.ts +1 -1
  165. package/dist/types/auth/PasswordChecklist.d.ts +1 -1
  166. package/dist/types/calendar/EventCalendar.d.ts +1 -1
  167. package/dist/types/calendar/EventDetails.d.ts +1 -1
  168. package/dist/types/feedback/ConfirmDialog.d.ts +1 -1
  169. package/dist/types/feedback/ModalWaiting.d.ts +1 -1
  170. package/dist/types/feedback/NewBuildNotice.d.ts +1 -1
  171. package/dist/types/filepond/FileDropzone.d.ts +1 -1
  172. package/dist/types/forms/auth/SignInForm.d.ts +1 -1
  173. package/dist/types/forms/security/SecondStepForm.d.ts +1 -1
  174. package/dist/types/forms/security/TwoStepVerificationCard.d.ts +1 -1
  175. package/dist/types/inbox/InboxLayout.d.ts +1 -1
  176. package/dist/types/inbox/MessageList.d.ts +1 -1
  177. package/dist/types/inbox/MessageView.d.ts +1 -1
  178. package/dist/types/index.d.ts +2 -1
  179. package/dist/types/inputs/ColorInput.d.ts +1 -1
  180. package/dist/types/inputs/FormSection.d.ts +2 -2
  181. package/dist/types/inputs/SelectInput.d.ts +1 -1
  182. package/dist/types/inputs/TextInput.d.ts +1 -1
  183. package/dist/types/inputs/WizardLayout.d.ts +1 -1
  184. package/dist/types/kanban/KanbanBoard.d.ts +1 -1
  185. package/dist/types/maps/MapView.d.ts +1 -1
  186. package/dist/types/pwa/InstallInstructionsDialog.d.ts +1 -1
  187. package/dist/types/pwa/InstallPromptBanner.d.ts +1 -1
  188. package/dist/types/security/ActivityLog.d.ts +1 -1
  189. package/dist/types/security/RecoveryCodesDialog.d.ts +1 -1
  190. package/dist/types/security/SessionsCard.d.ts +1 -1
  191. package/dist/types/theme/createToolkitTheme.d.ts +33 -1
  192. package/dist/types/theme/cssTokens.d.ts +46 -0
  193. package/dist/types/theme/glassSurface.d.ts +57 -1
  194. package/dist/types/theme/themePieces.d.ts +20 -0
  195. package/dist/types/ui/actions/ActionsMenu.d.ts +1 -1
  196. package/dist/types/ui/actions/DownloadMenu.d.ts +1 -1
  197. package/dist/types/ui/actions/SelectionActions.d.ts +1 -1
  198. package/dist/types/ui/filters/DateRangeFilter.d.ts +1 -1
  199. package/dist/types/ui/filters/PeriodRangeSelector.d.ts +1 -1
  200. package/dist/types/ui/filters/QuickFilters.d.ts +1 -1
  201. package/dist/types/ui/filters/SegmentedControl.d.ts +7 -2
  202. package/dist/types/ui/labels/StatusChip.d.ts +1 -1
  203. package/dist/types/ui/labels/Tag.d.ts +1 -1
  204. package/dist/types/ui/layout/CollapsibleSection.d.ts +1 -1
  205. package/dist/types/ui/layout/ContentDialog.d.ts +1 -1
  206. package/dist/types/ui/layout/DefinitionList.d.ts +1 -1
  207. package/dist/types/ui/layout/DetailHeader.d.ts +1 -1
  208. package/dist/types/ui/layout/ErrorPage.d.ts +1 -1
  209. package/dist/types/ui/layout/GlassPanel.d.ts +1 -1
  210. package/dist/types/ui/layout/PageHeader.d.ts +1 -1
  211. package/dist/types/ui/layout/PageHeading.d.ts +1 -1
  212. package/dist/types/ui/layout/ProfileHeader.d.ts +1 -1
  213. package/dist/types/ui/layout/ScrollableRow.d.ts +1 -1
  214. package/dist/types/ui/layout/SectionCard.d.ts +1 -1
  215. package/dist/types/ui/metrics/BarList.d.ts +1 -1
  216. package/dist/types/ui/metrics/CalendarHeatmap.d.ts +1 -1
  217. package/dist/types/ui/metrics/ChartTooltip.d.ts +1 -1
  218. package/dist/types/ui/metrics/ColumnChart.d.ts +1 -1
  219. package/dist/types/ui/metrics/LineChart.d.ts +1 -1
  220. package/dist/types/ui/metrics/MetricCard.d.ts +1 -1
  221. package/dist/types/ui/metrics/TableToggle.d.ts +1 -1
  222. package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +1 -1
  223. package/dist/types/ui/navigation/FontSizeToggle.d.ts +1 -1
  224. package/dist/types/ui/navigation/LanguageToggle.d.ts +1 -1
  225. package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +1 -1
  226. package/dist/types/ui/timeline/GroupedTimeline.d.ts +1 -1
  227. package/dist/types/upload/FileDropzone.d.ts +1 -1
  228. package/dist/ui/actions/ActionsMenu.js +9 -4
  229. package/dist/ui/actions/ActionsMenu.js.map +1 -1
  230. package/dist/ui/actions/DownloadMenu.js +21 -17
  231. package/dist/ui/actions/DownloadMenu.js.map +1 -1
  232. package/dist/ui/actions/SelectionActions.js +15 -7
  233. package/dist/ui/actions/SelectionActions.js.map +1 -1
  234. package/dist/ui/filters/DateRangeFilter.js +11 -5
  235. package/dist/ui/filters/DateRangeFilter.js.map +1 -1
  236. package/dist/ui/filters/PeriodRangeSelector.js +11 -5
  237. package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
  238. package/dist/ui/filters/QuickFilters.js +16 -8
  239. package/dist/ui/filters/QuickFilters.js.map +1 -1
  240. package/dist/ui/filters/SegmentedControl.js +12 -5
  241. package/dist/ui/filters/SegmentedControl.js.map +1 -1
  242. package/dist/ui/labels/StatusChip.js +35 -25
  243. package/dist/ui/labels/StatusChip.js.map +1 -1
  244. package/dist/ui/labels/Tag.js +8 -3
  245. package/dist/ui/labels/Tag.js.map +1 -1
  246. package/dist/ui/layout/CollapsibleSection.js +18 -8
  247. package/dist/ui/layout/CollapsibleSection.js.map +1 -1
  248. package/dist/ui/layout/ContentDialog.js +13 -6
  249. package/dist/ui/layout/ContentDialog.js.map +1 -1
  250. package/dist/ui/layout/DefinitionList.js +13 -6
  251. package/dist/ui/layout/DefinitionList.js.map +1 -1
  252. package/dist/ui/layout/DetailHeader.js +14 -6
  253. package/dist/ui/layout/DetailHeader.js.map +1 -1
  254. package/dist/ui/layout/ErrorPage.js +20 -15
  255. package/dist/ui/layout/ErrorPage.js.map +1 -1
  256. package/dist/ui/layout/FullPageLoader.js +4 -1
  257. package/dist/ui/layout/FullPageLoader.js.map +1 -1
  258. package/dist/ui/layout/GlassPanel.js +8 -4
  259. package/dist/ui/layout/GlassPanel.js.map +1 -1
  260. package/dist/ui/layout/PageHeader.js +10 -4
  261. package/dist/ui/layout/PageHeader.js.map +1 -1
  262. package/dist/ui/layout/PageHeading.js +17 -8
  263. package/dist/ui/layout/PageHeading.js.map +1 -1
  264. package/dist/ui/layout/ProfileHeader.js +21 -10
  265. package/dist/ui/layout/ProfileHeader.js.map +1 -1
  266. package/dist/ui/layout/ScrollableRow.js +19 -15
  267. package/dist/ui/layout/ScrollableRow.js.map +1 -1
  268. package/dist/ui/layout/SectionCard.js +10 -4
  269. package/dist/ui/layout/SectionCard.js.map +1 -1
  270. package/dist/ui/metrics/BarList.js +9 -4
  271. package/dist/ui/metrics/BarList.js.map +1 -1
  272. package/dist/ui/metrics/CalendarHeatmap.js +40 -21
  273. package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
  274. package/dist/ui/metrics/ChartTooltip.js +6 -2
  275. package/dist/ui/metrics/ChartTooltip.js.map +1 -1
  276. package/dist/ui/metrics/ColumnChart.js +22 -18
  277. package/dist/ui/metrics/ColumnChart.js.map +1 -1
  278. package/dist/ui/metrics/LineChart.js +20 -16
  279. package/dist/ui/metrics/LineChart.js.map +1 -1
  280. package/dist/ui/metrics/MetricCard.js +42 -24
  281. package/dist/ui/metrics/MetricCard.js.map +1 -1
  282. package/dist/ui/metrics/TableToggle.js +7 -2
  283. package/dist/ui/metrics/TableToggle.js.map +1 -1
  284. package/dist/ui/metrics/chartParts.js +14 -5
  285. package/dist/ui/metrics/chartParts.js.map +1 -1
  286. package/dist/ui/navigation/ColorSchemeToggle.js +8 -4
  287. package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
  288. package/dist/ui/navigation/FontSizeToggle.js +6 -2
  289. package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
  290. package/dist/ui/navigation/LanguageToggle.js +8 -4
  291. package/dist/ui/navigation/LanguageToggle.js.map +1 -1
  292. package/dist/ui/navigation/PageBreadcrumbs.js +12 -6
  293. package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
  294. package/dist/ui/timeline/GroupedTimeline.js +26 -13
  295. package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
  296. package/dist/upload/FileDropzone.js +35 -25
  297. package/dist/upload/FileDropzone.js.map +1 -1
  298. package/package.json +1 -1
@@ -21,6 +21,14 @@ import { menuBadgeOf } from "../navigation/appMenu.js";
21
21
  import { isGlassTheme, accentTextStylesOf, glassDropOf } from "../theme/glassSurface.js";
22
22
  import { mergeStyles } from "../theme/mergeComponents.js";
23
23
  import { textSizeOf } from "../theme/appleTracking.js";
24
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
25
+ const THEME_NAME = "CalcitaSideMenuEntry";
26
+ const EntryButton = piecePart(THEME_NAME, "Button", ListItemButton);
27
+ const EntryIcon = piecePart(THEME_NAME, "Icon", ListItemIcon);
28
+ const EntryIconPlaceholder = piecePart(THEME_NAME, "IconPlaceholder", Box);
29
+ const EntryText = piecePart(THEME_NAME, "Text", ListItemText);
30
+ const EntryBadge = piecePart(THEME_NAME, "Badge", Chip);
31
+ const EntryExpand = piecePart(THEME_NAME, "Expand", Box);
24
32
  const ROW_LEFT_PADDING = 1.5;
25
33
  const ROW_RIGHT_PADDING = 1.5;
26
34
  const CHILD_RIGHT_PADDING = 1;
@@ -36,7 +44,8 @@ const MENU_TEXT_PIXELS = 13;
36
44
  const MENU_BADGE_PIXELS = 11;
37
45
  const BADGE_MAX = 99;
38
46
  const RAIL_BUTTON_RADIUS_PIXELS = 10;
39
- function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0 }) {
47
+ function SideMenuEntry(props) {
48
+ const { menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0 } = usePieceProps(props, THEME_NAME);
40
49
  const { t } = useToolkitTranslation("layout");
41
50
  const { Link: RouterLink } = useToolkitNavigation();
42
51
  const theme = useTheme();
@@ -66,12 +75,12 @@ function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges
66
75
  const activeText = accentTextStylesOf(theme);
67
76
  const selectedStyle = mergeStyles(isGlass ? glassDropOf(theme) : { bgcolor: "action.selected" }, activeText);
68
77
  const entryButton = /* @__PURE__ */ jsxs(
69
- ListItemButton,
78
+ EntryButton,
70
79
  {
71
80
  ...linkProps,
72
81
  selected: !hasChildren && isActive,
73
82
  "aria-expanded": hasChildren ? collapsed ? Boolean(flyoutAnchor) : isOpen : void 0,
74
- sx: {
83
+ ownSx: {
75
84
  borderRadius: isRailButton ? `${RAIL_BUTTON_RADIUS_PIXELS}px` : 1.5,
76
85
  pl: leftPadding,
77
86
  pr: isChild ? CHILD_RIGHT_PADDING : ROW_RIGHT_PADDING,
@@ -81,7 +90,7 @@ function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges
81
90
  "&.Mui-selected, &.Mui-selected:hover": selectedStyle
82
91
  },
83
92
  children: [
84
- (!isChild || Icon) && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: "inherit" }, children: /* @__PURE__ */ jsx(
93
+ (!isChild || Icon) && /* @__PURE__ */ jsx(EntryIcon, { ownSx: { minWidth: 0, mr: isChild ? CHILD_ICON_GAP : ICON_GAP, color: "inherit" }, children: /* @__PURE__ */ jsx(
85
94
  Badge,
86
95
  {
87
96
  invisible: !collapsed || !badge,
@@ -90,19 +99,19 @@ function SideMenuEntry({ menuItem, activeItem, collapsed, onNavigate, menuBadges
90
99
  badgeContent: badge?.isCount ? badge.content : void 0,
91
100
  variant: badge?.isCount ? "standard" : "dot",
92
101
  overlap: "circular",
93
- children: Icon ? /* @__PURE__ */ jsx(Icon, { sx: { fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE } }) : /* @__PURE__ */ jsx(Box, { sx: { width: ICON_SIZE, height: ICON_SIZE } })
102
+ children: Icon ? /* @__PURE__ */ jsx(Icon, { sx: { fontSize: isChild ? CHILD_ICON_SIZE : ICON_SIZE } }) : /* @__PURE__ */ jsx(EntryIconPlaceholder, { ownSx: { width: ICON_SIZE, height: ICON_SIZE } })
94
103
  }
95
104
  ) }),
96
105
  /* @__PURE__ */ jsx(
97
- ListItemText,
106
+ EntryText,
98
107
  {
99
108
  primary: label,
100
- sx: fadeWhenCollapsed,
109
+ ownSx: fadeWhenCollapsed,
101
110
  slotProps: { primary: { noWrap: true, sx: { ...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400 } } }
102
111
  }
103
112
  ),
104
- badge && /* @__PURE__ */ jsx(Chip, { label: badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content, size: "small", color: "primary", sx: (theme2) => ({ height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme2) }) }),
105
- hasChildren && /* @__PURE__ */ jsx(Box, { sx: (theme2) => ({ display: "flex", alignItems: "center", ...fadeWhenCollapsed(theme2) }), children: isOpen ? /* @__PURE__ */ jsx(ExpandMoreIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { fontSize: "small" }) })
113
+ badge && /* @__PURE__ */ jsx(EntryBadge, { label: badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content, size: "small", color: "primary", ownSx: (theme2) => ({ height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme2) }) }),
114
+ hasChildren && /* @__PURE__ */ jsx(EntryExpand, { ownSx: (theme2) => ({ display: "flex", alignItems: "center", ...fadeWhenCollapsed(theme2) }), children: isOpen ? /* @__PURE__ */ jsx(ExpandMoreIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(ChevronRightIcon, { fontSize: "small" }) })
106
115
  ]
107
116
  }
108
117
  );
@@ -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, 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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"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSideMenuEntry con defaultProps y\n// styleOverrides de sus partes: button, icon, iconPlaceholder, text, badge, expand.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSideMenuEntry)\nconst THEME_NAME = 'CalcitaSideMenuEntry';\nconst EntryButton = piecePart(THEME_NAME, 'Button', ListItemButton);\nconst EntryIcon = piecePart(THEME_NAME, 'Icon', ListItemIcon);\nconst EntryIconPlaceholder = piecePart(THEME_NAME, 'IconPlaceholder', Box);\nconst EntryText = piecePart(THEME_NAME, 'Text', ListItemText);\nconst EntryBadge = piecePart(THEME_NAME, 'Badge', Chip);\nconst EntryExpand = piecePart(THEME_NAME, 'Expand', Box);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSideMenuEntry.defaultProps) y encima lo que llega aquí\n const {menuItem, activeItem, collapsed, onNavigate, menuBadges, depth = 0} = usePieceProps(props, THEME_NAME);\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 <EntryButton {...linkProps} selected={!hasChildren && isActive} aria-expanded={hasChildren ? (collapsed ? Boolean(flyoutAnchor) : isOpen) : undefined}\n ownSx={{\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 <EntryIcon ownSx={{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}} /> : <EntryIconPlaceholder ownSx={{width: ICON_SIZE, height: ICON_SIZE}} />}\n </Badge>\n </EntryIcon>\n )}\n <EntryText primary={label} ownSx={fadeWhenCollapsed}\n slotProps={{primary: {noWrap: true, sx: {...textSizeOf(MENU_TEXT_PIXELS), fontWeight: isActive ? 600 : 400}}}} />\n {badge && (\n <EntryBadge label={badge.isCount && badge.content > BADGE_MAX ? `${BADGE_MAX}+` : badge.content} size=\"small\" color=\"primary\" ownSx={(theme) => ({height: 20, ...textSizeOf(MENU_BADGE_PIXELS), ml: 1, ...fadeWhenCollapsed(theme)})} />\n )}\n {hasChildren && (\n <EntryExpand ownSx={(theme) => ({display: 'flex', alignItems: 'center', ...fadeWhenCollapsed(theme)})}>\n {isOpen ? <ExpandMoreIcon fontSize=\"small\" /> : <ChevronRightIcon fontSize=\"small\" />}\n </EntryExpand>\n )}\n </EntryButton>\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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"}
@@ -10,6 +10,9 @@ import { menuBadgeOf } from "../navigation/appMenu.js";
10
10
  import { textSizeOf } from "../theme/appleTracking.js";
11
11
  import { useToolkitNavigation } from "../toolkit/toolkitContext.js";
12
12
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
13
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
14
+ const THEME_NAME = "CalcitaSubmenuActionsMenu";
15
+ const SubmenuBadge = piecePart(THEME_NAME, "Badge", Chip);
13
16
  const SUBMENU_PLACEMENT = Object.freeze({ side: "side", above: "above" });
14
17
  const GAP_PIXELS = 12;
15
18
  const MIN_WIDTH_PIXELS = 220;
@@ -26,7 +29,8 @@ const PLACEMENTS = Object.freeze({
26
29
  gapSx: { mt: `-${GAP_PIXELS}px` }
27
30
  }
28
31
  });
29
- function SubmenuActionsMenu({ anchorElement, onClose, menuItem, activeItem, menuBadges, onNavigate, placement = SUBMENU_PLACEMENT.side }) {
32
+ function SubmenuActionsMenu(props) {
33
+ const { anchorElement, onClose, menuItem, activeItem, menuBadges, onNavigate, placement = SUBMENU_PLACEMENT.side } = usePieceProps(props, THEME_NAME);
30
34
  const { t } = useToolkitTranslation("layout");
31
35
  const { Link: RouterLink } = useToolkitNavigation();
32
36
  const position = PLACEMENTS[placement] ?? PLACEMENTS[SUBMENU_PLACEMENT.side];
@@ -54,7 +58,7 @@ function SubmenuActionsMenu({ anchorElement, onClose, menuItem, activeItem, menu
54
58
  children: [
55
59
  ChildIcon && /* @__PURE__ */ jsx(ListItemIcon, { children: /* @__PURE__ */ jsx(ChildIcon, { fontSize: "small" }) }),
56
60
  /* @__PURE__ */ jsx(ListItemText, { children: menuItemLabelOf(child, t) }),
57
- badge && /* @__PURE__ */ jsx(Chip, { label: badge.content, size: "small", color: "primary", sx: { height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS) } })
61
+ badge && /* @__PURE__ */ jsx(SubmenuBadge, { label: badge.content, size: "small", color: "primary", ownSx: { height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS) } })
58
62
  ]
59
63
  },
60
64
  child.to ?? child.href ?? menuItemLabelOf(child, t)
@@ -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, 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;"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSubmenuActionsMenu con defaultProps y\n// styleOverrides de sus partes: badge.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSubmenuActionsMenu)\nconst THEME_NAME = 'CalcitaSubmenuActionsMenu';\nconst SubmenuBadge = piecePart(THEME_NAME, 'Badge', Chip);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSubmenuActionsMenu.defaultProps) y encima lo que llega aquí\n const {anchorElement, onClose, menuItem, activeItem, menuBadges, onNavigate, placement = SUBMENU_PLACEMENT.side} = usePieceProps(props, THEME_NAME);\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 && <SubmenuBadge label={badge.content} size=\"small\" color=\"primary\" ownSx={{height: 20, ml: 1.5, ...textSizeOf(BADGE_PIXELS)}} />}\n </MenuItem>\n );\n })}\n </Menu>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;AAsBA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,SAAS,IAAI;AAGjD,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,OAAO;AAE9C,QAAM,EAAC,eAAe,SAAS,UAAU,YAAY,YAAY,YAAY,YAAY,kBAAkB,KAAA,IAAQ,cAAc,OAAO,UAAU;AAClJ,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,cAAA,EAAa,OAAO,MAAM,SAAS,MAAK,SAAQ,OAAM,WAAU,OAAO,EAAC,QAAQ,IAAI,IAAI,KAAK,GAAG,WAAW,YAAY,IAAC,CAAG;AAAA,YAAA;AAAA,UAAA;AAAA,UAP3H,MAAM,MAAM,MAAM,QAAQ,gBAAgB,OAAO,CAAC;AAAA,QAAA;AAAA,MAUzE,CAAC;AAAA,IAAA;AAAA,EAAA;AAGb;"}
@@ -12,18 +12,27 @@ import Typography from "@mui/material/Typography";
12
12
  import { menuItemLabelOf } from "./menuItemText.js";
13
13
  import { PINNABLE_TAB_LIMIT, menuItemKeyOf } from "../navigation/appMenu.js";
14
14
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
15
- function TabBarEditor({ menuItems, pinnedKeys, onDone, onReset }) {
15
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
16
+ const THEME_NAME = "CalcitaTabBarEditor";
17
+ const EditorRoot = piecePart(THEME_NAME, "Root", Box);
18
+ const EditorHeader = piecePart(THEME_NAME, "Header", Box);
19
+ const EditorTitle = piecePart(THEME_NAME, "Title", Typography);
20
+ const EditorList = piecePart(THEME_NAME, "List", List);
21
+ const EditorItemIcon = piecePart(THEME_NAME, "ItemIcon", ListItemIcon);
22
+ const EditorFooter = piecePart(THEME_NAME, "Footer", Box);
23
+ function TabBarEditor(props) {
24
+ const { menuItems, pinnedKeys, onDone, onReset } = usePieceProps(props, THEME_NAME);
16
25
  const { t } = useToolkitTranslation("layout");
17
26
  const [chosenKeys, setChosenKeys] = useState(pinnedKeys);
18
27
  const isFull = chosenKeys.length >= PINNABLE_TAB_LIMIT;
19
28
  const toggle = (key, isOn) => setChosenKeys((current) => isOn ? [...current, key] : current.filter((chosenKey) => chosenKey !== key));
20
29
  const chosenInMenuOrder = () => menuItems.map(menuItemKeyOf).filter((key) => chosenKeys.includes(key));
21
- return /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", flexDirection: "column", flex: "1 1 auto", minHeight: 0 }, children: [
22
- /* @__PURE__ */ jsxs(Box, { sx: { px: 2, pt: 2, pb: 1, flexShrink: 0 }, children: [
23
- /* @__PURE__ */ jsx(Typography, { variant: "subtitle1", component: "h2", sx: { fontWeight: 600 }, children: t("drawer.editTabs") }),
30
+ return /* @__PURE__ */ jsxs(EditorRoot, { ownSx: { display: "flex", flexDirection: "column", flex: "1 1 auto", minHeight: 0 }, children: [
31
+ /* @__PURE__ */ jsxs(EditorHeader, { ownSx: { px: 2, pt: 2, pb: 1, flexShrink: 0 }, children: [
32
+ /* @__PURE__ */ jsx(EditorTitle, { variant: "subtitle1", component: "h2", ownSx: { fontWeight: 600 }, children: t("drawer.editTabs") }),
24
33
  /* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", children: t("drawer.editTabsHint", { count: PINNABLE_TAB_LIMIT }) })
25
34
  ] }),
26
- /* @__PURE__ */ jsx(List, { dense: true, sx: { overflowY: "auto", flex: "1 1 auto", minHeight: 0, px: 1 }, children: menuItems.map((menuItem) => {
35
+ /* @__PURE__ */ jsx(EditorList, { dense: true, ownSx: { overflowY: "auto", flex: "1 1 auto", minHeight: 0, px: 1 }, children: menuItems.map((menuItem) => {
27
36
  const key = menuItemKeyOf(menuItem);
28
37
  const label = menuItemLabelOf(menuItem, t);
29
38
  const isOn = chosenKeys.includes(key);
@@ -39,11 +48,11 @@ function TabBarEditor({ menuItems, pinnedKeys, onDone, onReset }) {
39
48
  slotProps: { input: { id: switchId, "aria-label": t("drawer.tabInBar", { label }) } }
40
49
  }
41
50
  ), children: [
42
- menuItem.Icon && /* @__PURE__ */ jsx(ListItemIcon, { sx: { minWidth: 40 }, children: /* @__PURE__ */ jsx(menuItem.Icon, {}) }),
51
+ menuItem.Icon && /* @__PURE__ */ jsx(EditorItemIcon, { ownSx: { minWidth: 40 }, children: /* @__PURE__ */ jsx(menuItem.Icon, {}) }),
43
52
  /* @__PURE__ */ jsx(ListItemText, { primary: /* @__PURE__ */ jsx("label", { htmlFor: switchId, children: label }) })
44
53
  ] }, key);
45
54
  }) }),
46
- /* @__PURE__ */ jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", gap: 1, px: 2, py: 1.5, flexShrink: 0 }, children: [
55
+ /* @__PURE__ */ jsxs(EditorFooter, { ownSx: { display: "flex", justifyContent: "space-between", gap: 1, px: 2, py: 1.5, flexShrink: 0 }, children: [
47
56
  /* @__PURE__ */ jsx(Button, { onClick: onReset, children: t("drawer.resetTabs") }),
48
57
  /* @__PURE__ */ jsx(Button, { variant: "contained", onClick: () => onDone(chosenInMenuOrder()), children: t("drawer.doneEditing") })
49
58
  ] })
@@ -1 +1 @@
1
- {"version":3,"file":"TabBarEditor.js","sources":["../../src/appShell/TabBarEditor.jsx"],"sourcesContent":["// Elegir qué pantallas van en la barra de pestañas del teléfono: una lista de todo el menú con un interruptor por renglón.\n// Sale dentro de la hoja de «Más» (MobileTabBar) al picar «Editar la barra».\n//\n// - Hasta PINNABLE_TAB_LIMIT (las que caben junto a «Más»); llenas, las demás se apagan hasta quitar una.\n// - Al menos una: el interruptor de la última no se puede apagar (sin ninguna, la barra volvería sola a las del proyecto\n// y parecería que no hizo caso).\n// - Lo elegido se guarda al picar «Listo»; «Como venía» regresa a las que eligió el proyecto.\n// - Van en el orden del menú: acomodarlas arrastrando sería otra decisión para la persona y otra cosa que probar.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Switch from '@mui/material/Switch';\nimport Typography from '@mui/material/Typography';\n\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport {PINNABLE_TAB_LIMIT, menuItemKeyOf} from '@/navigation/appMenu.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/**\n * @param {object} props\n * @param {object[]} props.menuItems todo el menú (lo que regresa composeMenu)\n * @param {string[]} props.pinnedKeys las que van hoy en la barra (menuItemKeyOf)\n * @param {(keys: string[]) => void} props.onDone con las elegidas, en el orden del menú\n * @param {() => void} props.onReset regresar a las del proyecto\n */\nexport default function TabBarEditor({menuItems, pinnedKeys, onDone, onReset}) {\n const {t} = useToolkitTranslation('layout');\n const [chosenKeys, setChosenKeys] = useState(pinnedKeys);\n const isFull = chosenKeys.length >= PINNABLE_TAB_LIMIT;\n\n const toggle = (key, isOn) => setChosenKeys((current) => (isOn ? [...current, key] : current.filter((chosenKey) => chosenKey !== key)));\n // en el orden del menú, no en el que se fueron picando\n const chosenInMenuOrder = () => menuItems.map(menuItemKeyOf).filter((key) => chosenKeys.includes(key));\n\n return (\n <Box sx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n <Box sx={{px: 2, pt: 2, pb: 1, flexShrink: 0}}>\n <Typography variant=\"subtitle1\" component=\"h2\" sx={{fontWeight: 600}}>{t('drawer.editTabs')}</Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('drawer.editTabsHint', {count: PINNABLE_TAB_LIMIT})}</Typography>\n </Box>\n <List dense sx={{overflowY: 'auto', flex: '1 1 auto', minHeight: 0, px: 1}}>\n {menuItems.map((menuItem) => {\n const key = menuItemKeyOf(menuItem);\n const label = menuItemLabelOf(menuItem, t);\n const isOn = chosenKeys.includes(key);\n const isLastOne = isOn && chosenKeys.length === 1;\n const switchId = `tab-bar-editor-${key}`;\n return (\n <ListItem key={key} secondaryAction={(\n <Switch edge=\"end\" checked={isOn} disabled={(!isOn && isFull) || isLastOne}\n onChange={(event) => toggle(key, event.target.checked)}\n slotProps={{input: {id: switchId, 'aria-label': t('drawer.tabInBar', {label})}}} />\n )}>\n {menuItem.Icon && <ListItemIcon sx={{minWidth: 40}}><menuItem.Icon /></ListItemIcon>}\n <ListItemText primary={<label htmlFor={switchId}>{label}</label>} />\n </ListItem>\n );\n })}\n </List>\n <Box sx={{display: 'flex', justifyContent: 'space-between', gap: 1, px: 2, py: 1.5, flexShrink: 0}}>\n <Button onClick={onReset}>{t('drawer.resetTabs')}</Button>\n <Button variant=\"contained\" onClick={() => onDone(chosenInMenuOrder())}>{t('drawer.doneEditing')}</Button>\n </Box>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AA+BA,SAAwB,aAAa,EAAC,WAAW,YAAY,QAAQ,WAAU;AAC3E,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ;AAC1C,QAAM,CAAC,YAAY,aAAa,IAAI,SAAS,UAAU;AACvD,QAAM,SAAS,WAAW,UAAU;AAEpC,QAAM,SAAS,CAAC,KAAK,SAAS,cAAc,CAAC,YAAa,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,QAAQ,OAAO,CAAC,cAAc,cAAc,GAAG,CAAE;AAEtI,QAAM,oBAAoB,MAAM,UAAU,IAAI,aAAa,EAAE,OAAO,CAAC,QAAQ,WAAW,SAAS,GAAG,CAAC;AAErG,SACI,qBAAC,KAAA,EAAI,IAAI,EAAC,SAAS,QAAQ,eAAe,UAAU,MAAM,YAAY,WAAW,EAAA,GAC7E,UAAA;AAAA,IAAA,qBAAC,KAAA,EAAI,IAAI,EAAC,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,YAAY,EAAA,GACvC,UAAA;AAAA,MAAA,oBAAC,YAAA,EAAW,SAAQ,aAAY,WAAU,MAAK,IAAI,EAAC,YAAY,IAAA,GAAO,UAAA,EAAE,iBAAiB,EAAA,CAAE;AAAA,MAC5F,oBAAC,YAAA,EAAW,SAAQ,SAAQ,OAAM,kBAAkB,UAAA,EAAE,uBAAuB,EAAC,OAAO,mBAAA,CAAmB,EAAA,CAAE;AAAA,IAAA,GAC9G;AAAA,wBACC,MAAA,EAAK,OAAK,MAAC,IAAI,EAAC,WAAW,QAAQ,MAAM,YAAY,WAAW,GAAG,IAAI,EAAA,GACnE,UAAA,UAAU,IAAI,CAAC,aAAa;AACzB,YAAM,MAAM,cAAc,QAAQ;AAClC,YAAM,QAAQ,gBAAgB,UAAU,CAAC;AACzC,YAAM,OAAO,WAAW,SAAS,GAAG;AACpC,YAAM,YAAY,QAAQ,WAAW,WAAW;AAChD,YAAM,WAAW,kBAAkB,GAAG;AACtC,aACI,qBAAC,YAAmB,iBAChB;AAAA,QAAC;AAAA,QAAA;AAAA,UAAO,MAAK;AAAA,UAAM,SAAS;AAAA,UAAM,UAAW,CAAC,QAAQ,UAAW;AAAA,UACzD,UAAU,CAAC,UAAU,OAAO,KAAK,MAAM,OAAO,OAAO;AAAA,UACrD,WAAW,EAAC,OAAO,EAAC,IAAI,UAAU,cAAc,EAAE,mBAAmB,EAAC,MAAA,CAAM,IAAC;AAAA,QAAC;AAAA,MAAA,GAErF,UAAA;AAAA,QAAA,SAAS,QAAQ,oBAAC,cAAA,EAAa,IAAI,EAAC,UAAU,GAAA,GAAK,UAAA,oBAAC,SAAS,MAAT,CAAA,CAAc,EAAA,CAAE;AAAA,QACrE,oBAAC,gBAAa,SAAS,oBAAC,WAAM,SAAS,UAAW,iBAAM,EAAA,CAAU;AAAA,MAAA,EAAA,GANvD,GAOf;AAAA,IAER,CAAC,EAAA,CACL;AAAA,yBACC,KAAA,EAAI,IAAI,EAAC,SAAS,QAAQ,gBAAgB,iBAAiB,KAAK,GAAG,IAAI,GAAG,IAAI,KAAK,YAAY,KAC5F,UAAA;AAAA,MAAA,oBAAC,QAAA,EAAO,SAAS,SAAU,UAAA,EAAE,kBAAkB,GAAE;AAAA,MACjD,oBAAC,QAAA,EAAO,SAAQ,aAAY,SAAS,MAAM,OAAO,kBAAA,CAAmB,GAAI,UAAA,EAAE,oBAAoB,EAAA,CAAE;AAAA,IAAA,EAAA,CACrG;AAAA,EAAA,GACJ;AAER;"}
1
+ {"version":3,"file":"TabBarEditor.js","sources":["../../src/appShell/TabBarEditor.jsx"],"sourcesContent":["// Elegir qué pantallas van en la barra de pestañas del teléfono: una lista de todo el menú con un interruptor por renglón.\n// Sale dentro de la hoja de «Más» (MobileTabBar) al picar «Editar la barra».\n//\n// - Hasta PINNABLE_TAB_LIMIT (las que caben junto a «Más»); llenas, las demás se apagan hasta quitar una.\n// - Al menos una: el interruptor de la última no se puede apagar (sin ninguna, la barra volvería sola a las del proyecto\n// y parecería que no hizo caso).\n// - Lo elegido se guarda al picar «Listo»; «Como venía» regresa a las que eligió el proyecto.\n// - Van en el orden del menú: acomodarlas arrastrando sería otra decisión para la persona y otra cosa que probar.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTabBarEditor con defaultProps y\n// styleOverrides de sus partes: root, header, title, list, itemIcon, footer.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Switch from '@mui/material/Switch';\nimport Typography from '@mui/material/Typography';\n\nimport {menuItemLabelOf} from '@/appShell/menuItemText.js';\nimport {PINNABLE_TAB_LIMIT, menuItemKeyOf} from '@/navigation/appMenu.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaTabBarEditor)\nconst THEME_NAME = 'CalcitaTabBarEditor';\nconst EditorRoot = piecePart(THEME_NAME, 'Root', Box);\nconst EditorHeader = piecePart(THEME_NAME, 'Header', Box);\nconst EditorTitle = piecePart(THEME_NAME, 'Title', Typography);\nconst EditorList = piecePart(THEME_NAME, 'List', List);\nconst EditorItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\nconst EditorFooter = piecePart(THEME_NAME, 'Footer', Box);\n\n/**\n * @param {object} props\n * @param {object[]} props.menuItems todo el menú (lo que regresa composeMenu)\n * @param {string[]} props.pinnedKeys las que van hoy en la barra (menuItemKeyOf)\n * @param {(keys: string[]) => void} props.onDone con las elegidas, en el orden del menú\n * @param {() => void} props.onReset regresar a las del proyecto\n */\nexport default function TabBarEditor(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTabBarEditor.defaultProps) y encima lo que llega aquí\n const {menuItems, pinnedKeys, onDone, onReset} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('layout');\n const [chosenKeys, setChosenKeys] = useState(pinnedKeys);\n const isFull = chosenKeys.length >= PINNABLE_TAB_LIMIT;\n\n const toggle = (key, isOn) => setChosenKeys((current) => (isOn ? [...current, key] : current.filter((chosenKey) => chosenKey !== key)));\n // en el orden del menú, no en el que se fueron picando\n const chosenInMenuOrder = () => menuItems.map(menuItemKeyOf).filter((key) => chosenKeys.includes(key));\n\n return (\n <EditorRoot ownSx={{display: 'flex', flexDirection: 'column', flex: '1 1 auto', minHeight: 0}}>\n <EditorHeader ownSx={{px: 2, pt: 2, pb: 1, flexShrink: 0}}>\n <EditorTitle variant=\"subtitle1\" component=\"h2\" ownSx={{fontWeight: 600}}>{t('drawer.editTabs')}</EditorTitle>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('drawer.editTabsHint', {count: PINNABLE_TAB_LIMIT})}</Typography>\n </EditorHeader>\n <EditorList dense ownSx={{overflowY: 'auto', flex: '1 1 auto', minHeight: 0, px: 1}}>\n {menuItems.map((menuItem) => {\n const key = menuItemKeyOf(menuItem);\n const label = menuItemLabelOf(menuItem, t);\n const isOn = chosenKeys.includes(key);\n const isLastOne = isOn && chosenKeys.length === 1;\n const switchId = `tab-bar-editor-${key}`;\n return (\n <ListItem key={key} secondaryAction={(\n <Switch edge=\"end\" checked={isOn} disabled={(!isOn && isFull) || isLastOne}\n onChange={(event) => toggle(key, event.target.checked)}\n slotProps={{input: {id: switchId, 'aria-label': t('drawer.tabInBar', {label})}}} />\n )}>\n {menuItem.Icon && <EditorItemIcon ownSx={{minWidth: 40}}><menuItem.Icon /></EditorItemIcon>}\n <ListItemText primary={<label htmlFor={switchId}>{label}</label>} />\n </ListItem>\n );\n })}\n </EditorList>\n <EditorFooter ownSx={{display: 'flex', justifyContent: 'space-between', gap: 1, px: 2, py: 1.5, flexShrink: 0}}>\n <Button onClick={onReset}>{t('drawer.resetTabs')}</Button>\n <Button variant=\"contained\" onClick={() => onDone(chosenInMenuOrder())}>{t('drawer.doneEditing')}</Button>\n </EditorFooter>\n </EditorRoot>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -11,6 +11,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
13
  import { menuItemLabelOf, menuLinkPropsOf } from "./menuItemText.js";
14
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
15
+ const THEME_NAME = "CalcitaTopMenu";
16
+ const TopMenuLink = piecePart(THEME_NAME, "Link", Button);
17
+ const TopMenuRoot = piecePart(THEME_NAME, "Root", Box);
14
18
  const ACTIVE_UNDERLINE_PIXELS = 2;
15
19
  function TopMenuEntry({ menuItem, activeItem }) {
16
20
  const { t } = useToolkitTranslation("layout");
@@ -29,26 +33,26 @@ function TopMenuEntry({ menuItem, activeItem }) {
29
33
  };
30
34
  if (!hasChildren) {
31
35
  return /* @__PURE__ */ jsx(
32
- Button,
36
+ TopMenuLink,
33
37
  {
34
38
  ...menuLinkPropsOf(menuItem, RouterLink),
35
39
  startIcon: Icon ? /* @__PURE__ */ jsx(Icon, { fontSize: "small" }) : void 0,
36
40
  "aria-current": isActive ? "page" : void 0,
37
- sx: buttonSx,
41
+ ownSx: buttonSx,
38
42
  children: menuItemLabelOf(menuItem, t)
39
43
  }
40
44
  );
41
45
  }
42
46
  return /* @__PURE__ */ jsxs(Fragment, { children: [
43
47
  /* @__PURE__ */ jsx(
44
- Button,
48
+ TopMenuLink,
45
49
  {
46
50
  onClick: (event) => setAnchorElement(event.currentTarget),
47
51
  "aria-haspopup": "menu",
48
52
  "aria-expanded": Boolean(anchorElement),
49
53
  startIcon: Icon ? /* @__PURE__ */ jsx(Icon, { fontSize: "small" }) : void 0,
50
54
  endIcon: /* @__PURE__ */ jsx(ExpandMoreIcon, { fontSize: "small" }),
51
- sx: buttonSx,
55
+ ownSx: buttonSx,
52
56
  children: menuItemLabelOf(menuItem, t)
53
57
  }
54
58
  ),
@@ -61,14 +65,15 @@ function TopMenuEntry({ menuItem, activeItem }) {
61
65
  }) })
62
66
  ] });
63
67
  }
64
- function TopMenu({ menuItems, activeItem }) {
68
+ function TopMenu(props) {
69
+ const { menuItems, activeItem } = usePieceProps(props, THEME_NAME);
65
70
  const { t } = useToolkitTranslation("layout");
66
71
  return /* @__PURE__ */ jsx(
67
- Box,
72
+ TopMenuRoot,
68
73
  {
69
74
  component: "nav",
70
75
  "aria-label": t("drawer.ariaLabel"),
71
- sx: { display: { xs: "none", md: "flex" }, alignItems: "stretch", alignSelf: "stretch", gap: 0.5, minWidth: 0, overflowX: "auto" },
76
+ ownSx: { display: { xs: "none", md: "flex" }, alignItems: "stretch", alignSelf: "stretch", gap: 0.5, minWidth: 0, overflowX: "auto" },
72
77
  children: menuItems.map((menuItem) => /* @__PURE__ */ jsx(TopMenuEntry, { menuItem, activeItem }, menuItem.to ?? menuItem.href ?? menuItemLabelOf(menuItem, t)))
73
78
  }
74
79
  );
@@ -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, 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;"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTopMenu con defaultProps y\n// styleOverrides de sus partes: link, root.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaTopMenu)\nconst THEME_NAME = 'CalcitaTopMenu';\nconst TopMenuLink = piecePart(THEME_NAME, 'Link', Button);\nconst TopMenuRoot = piecePart(THEME_NAME, 'Root', Box);\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 <TopMenuLink {...menuLinkPropsOf(menuItem, RouterLink)} startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined}\n aria-current={isActive ? 'page' : undefined} ownSx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </TopMenuLink>\n );\n }\n return (\n <>\n <TopMenuLink onClick={(event) => setAnchorElement(event.currentTarget)} aria-haspopup=\"menu\" aria-expanded={Boolean(anchorElement)}\n startIcon={Icon ? <Icon fontSize=\"small\" /> : undefined} endIcon={<ExpandMoreIcon fontSize=\"small\" />} ownSx={buttonSx}>\n {menuItemLabelOf(menuItem, t)}\n </TopMenuLink>\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTopMenu.defaultProps) y encima lo que llega aquí\n const {menuItems, activeItem} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('layout');\n return (\n <TopMenuRoot component=\"nav\" aria-label={t('drawer.ariaLabel')}\n ownSx={{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 </TopMenuRoot>\n );\n}\n"],"names":[],"mappings":"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"}
@@ -1,9 +1,13 @@
1
1
  "use client";
2
2
  import { jsx } from "react/jsx-runtime";
3
3
  import Box from "@mui/material/Box";
4
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
5
+ const THEME_NAME = "CalcitaWordmark";
6
+ const WordmarkRoot = piecePart(THEME_NAME, "Root", Box);
4
7
  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 });
8
+ function Wordmark(props) {
9
+ const { children } = usePieceProps(props, THEME_NAME);
10
+ return /* @__PURE__ */ jsx(WordmarkRoot, { ownSx: { height: WORDMARK_HEIGHT_PIXELS, display: "flex", alignItems: "center", flex: "0 0 auto", "& > img, & > svg": { height: "100%", width: "auto", display: "block" } }, children });
7
11
  }
8
12
  export {
9
13
  Wordmark as default
@@ -1 +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;"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaWordmark con defaultProps y\n// styleOverrides de sus partes: root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Box from '@mui/material/Box';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaWordmark)\nconst THEME_NAME = 'CalcitaWordmark';\nconst WordmarkRoot = piecePart(THEME_NAME, 'Root', 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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaWordmark.defaultProps) y encima lo que llega aquí\n const {children} = usePieceProps(props, THEME_NAME);\n return (\n <WordmarkRoot ownSx={{height: WORDMARK_HEIGHT_PIXELS, display: 'flex', alignItems: 'center', flex: '0 0 auto', '& > img, & > svg': {height: '100%', width: 'auto', display: 'block'}}}>\n {children}\n </WordmarkRoot>\n );\n}\n"],"names":[],"mappings":";;;;AAWA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,GAAG;AAGtD,MAAM,yBAAyB;AAK/B,SAAwB,SAAS,OAAO;AAEpC,QAAM,EAAC,SAAA,IAAY,cAAc,OAAO,UAAU;AAClD,SACI,oBAAC,gBAAa,OAAO,EAAC,QAAQ,wBAAwB,SAAS,QAAQ,YAAY,UAAU,MAAM,YAAY,oBAAoB,EAAC,QAAQ,QAAQ,OAAO,QAAQ,SAAS,QAAA,KACvK,UACL;AAER;"}
@@ -5,7 +5,13 @@ import Box from "@mui/material/Box";
5
5
  import Typography from "@mui/material/Typography";
6
6
  import CheckCircleIcon from "@mui/icons-material/CheckCircle";
7
7
  import RadioButtonUncheckedIcon from "@mui/icons-material/RadioButtonUnchecked";
8
- import { passwordChecksOf, DEFAULT_PASSWORD_POLICY } from "./passwordRules.js";
8
+ import { DEFAULT_PASSWORD_POLICY, passwordChecksOf } from "./passwordRules.js";
9
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
10
+ const THEME_NAME = "CalcitaPasswordChecklist";
11
+ const ChecklistTitle = piecePart(THEME_NAME, "Title", Typography);
12
+ const ChecklistList = piecePart(THEME_NAME, "List", Box);
13
+ const ChecklistItem = piecePart(THEME_NAME, "Item", Box);
14
+ const ChecklistHidden = piecePart(THEME_NAME, "Hidden", Box);
9
15
  const SCREEN_READER_ONLY_SX = {
10
16
  position: "absolute",
11
17
  width: "1px",
@@ -17,21 +23,22 @@ const SCREEN_READER_ONLY_SX = {
17
23
  whiteSpace: "nowrap",
18
24
  border: 0
19
25
  };
20
- function PasswordChecklist({ password, policy = DEFAULT_PASSWORD_POLICY, id, texts }) {
26
+ function PasswordChecklist(props) {
27
+ const { password, policy = DEFAULT_PASSWORD_POLICY, id, texts } = usePieceProps(props, THEME_NAME);
21
28
  const { t } = useToolkitTranslation("auth", texts);
22
29
  return /* @__PURE__ */ jsxs(Box, { id, children: [
23
- /* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", component: "p", sx: { mb: 0.5 }, children: t("passwordRules.title") }),
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(
25
- Box,
30
+ /* @__PURE__ */ jsx(ChecklistTitle, { variant: "caption", color: "text.secondary", component: "p", ownSx: { mb: 0.5 }, children: t("passwordRules.title") }),
31
+ /* @__PURE__ */ jsx(ChecklistList, { component: "ul", ownSx: { listStyle: "none", m: 0, p: 0, display: "grid", gap: 0.5 }, children: passwordChecksOf(password, policy).map((check) => /* @__PURE__ */ jsxs(
32
+ ChecklistItem,
26
33
  {
27
34
  component: "li",
28
35
  "data-rule": check.rule,
29
36
  "data-met": check.isMet,
30
- sx: { display: "flex", alignItems: "center", gap: 1, color: check.isMet ? "text.primary" : "text.secondary" },
37
+ ownSx: { display: "flex", alignItems: "center", gap: 1, color: check.isMet ? "text.primary" : "text.secondary" },
31
38
  children: [
32
39
  check.isMet ? /* @__PURE__ */ jsx(CheckCircleIcon, { fontSize: "small", color: "success", "aria-hidden": true }) : /* @__PURE__ */ jsx(RadioButtonUncheckedIcon, { fontSize: "small", color: "disabled", "aria-hidden": true }),
33
40
  /* @__PURE__ */ jsx(Typography, { variant: "body2", component: "span", children: t(`passwordRules.${check.rule}`, check.values) }),
34
- /* @__PURE__ */ jsx(Box, { component: "span", sx: SCREEN_READER_ONLY_SX, children: check.isMet ? t("passwordRules.met") : t("passwordRules.missing") })
41
+ /* @__PURE__ */ jsx(ChecklistHidden, { component: "span", ownSx: SCREEN_READER_ONLY_SX, children: check.isMet ? t("passwordRules.met") : t("passwordRules.missing") })
35
42
  ]
36
43
  },
37
44
  check.rule
@@ -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 * @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;"}
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// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaPasswordChecklist con defaultProps y\n// styleOverrides de sus partes: title, list, item, hidden.\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';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaPasswordChecklist)\nconst THEME_NAME = 'CalcitaPasswordChecklist';\nconst ChecklistTitle = piecePart(THEME_NAME, 'Title', Typography);\nconst ChecklistList = piecePart(THEME_NAME, 'List', Box);\nconst ChecklistItem = piecePart(THEME_NAME, 'Item', Box);\nconst ChecklistHidden = piecePart(THEME_NAME, 'Hidden', Box);\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(props) {\n // lo que el proyecto puso en su tema (components.CalcitaPasswordChecklist.defaultProps) y encima lo que llega aquí\n const {password, policy = DEFAULT_PASSWORD_POLICY, id, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('auth', texts);\n return (\n <Box id={id}>\n <ChecklistTitle variant=\"caption\" color=\"text.secondary\" component=\"p\" ownSx={{mb: 0.5}}>{t('passwordRules.title')}</ChecklistTitle>\n <ChecklistList component=\"ul\" ownSx={{listStyle: 'none', m: 0, p: 0, display: 'grid', gap: 0.5}}>\n {passwordChecksOf(password, policy).map((check) => (\n <ChecklistItem component=\"li\" key={check.rule} data-rule={check.rule} data-met={check.isMet}\n ownSx={{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 <ChecklistHidden component=\"span\" ownSx={SCREEN_READER_ONLY_SX}>{check.isMet ? t('passwordRules.met') : t('passwordRules.missing')}</ChecklistHidden>\n </ChecklistItem>\n ))}\n </ChecklistList>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;AAoBA,MAAM,aAAa;AACnB,MAAM,iBAAiB,UAAU,YAAY,SAAS,UAAU;AAChE,MAAM,gBAAgB,UAAU,YAAY,QAAQ,GAAG;AACvD,MAAM,gBAAgB,UAAU,YAAY,QAAQ,GAAG;AACvD,MAAM,kBAAkB,UAAU,YAAY,UAAU,GAAG;AAG3D,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,OAAO;AAE7C,QAAM,EAAC,UAAU,SAAS,yBAAyB,IAAI,UAAS,cAAc,OAAO,UAAU;AAC/F,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,SACI,qBAAC,OAAI,IACD,UAAA;AAAA,IAAA,oBAAC,gBAAA,EAAe,SAAQ,WAAU,OAAM,kBAAiB,WAAU,KAAI,OAAO,EAAC,IAAI,IAAA,GAAO,UAAA,EAAE,qBAAqB,GAAE;AAAA,IACnH,oBAAC,iBAAc,WAAU,MAAK,OAAO,EAAC,WAAW,QAAQ,GAAG,GAAG,GAAG,GAAG,SAAS,QAAQ,KAAK,IAAA,GACtF,UAAA,iBAAiB,UAAU,MAAM,EAAE,IAAI,CAAC,UACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAc,WAAU;AAAA,QAAsB,aAAW,MAAM;AAAA,QAAM,YAAU,MAAM;AAAA,QACjF,OAAO,EAAC,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,OAAO,MAAM,QAAQ,iBAAiB,iBAAA;AAAA,QAC7F,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,iBAAA,EAAgB,WAAU,QAAO,OAAO,uBAAwB,UAAA,MAAM,QAAQ,EAAE,mBAAmB,IAAI,EAAE,uBAAuB,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,MANpG,MAAM;AAAA,IAAA,CAQ5C,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
@@ -20,6 +20,12 @@ import { paletteOf } from "../theme/components/paletteOf.js";
20
20
  import { useToolkitSettings } from "../toolkit/toolkitContext.js";
21
21
  import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
22
22
  import SegmentedControl from "../ui/filters/SegmentedControl.js";
23
+ import { piecePart, usePieceProps } from "../theme/themePieces.js";
24
+ const THEME_NAME = "CalcitaEventCalendar";
25
+ const CalendarToolbar = piecePart(THEME_NAME, "Toolbar", Stack);
26
+ const CalendarNavigation = piecePart(THEME_NAME, "Navigation", Stack);
27
+ const CalendarTitle = piecePart(THEME_NAME, "Title", Typography);
28
+ const CalendarCalendar = piecePart(THEME_NAME, "Calendar", Box);
23
29
  const CALENDAR_VIEW = Object.freeze({ month: "dayGridMonth", week: "timeGridWeek", day: "timeGridDay", list: "listWeek" });
24
30
  const VIEW_TEXT_KEY = Object.freeze({
25
31
  [CALENDAR_VIEW.month]: "month",
@@ -53,21 +59,22 @@ function calendarStylesOf(theme) {
53
59
  "& .fc-list": { borderRadius: "12px" }
54
60
  };
55
61
  }
56
- function EventCalendar({
57
- events,
58
- initialView = CALENDAR_VIEW.month,
59
- views = Object.values(CALENDAR_VIEW),
60
- onEventClick,
61
- onDateSelect,
62
- onEventChange,
63
- height = "auto",
64
- ariaLabel,
65
- onDatesChange,
66
- onViewChange,
67
- renderEvent,
68
- fullCalendarProps,
69
- texts
70
- }) {
62
+ function EventCalendar(props) {
63
+ const {
64
+ events,
65
+ initialView = CALENDAR_VIEW.month,
66
+ views = Object.values(CALENDAR_VIEW),
67
+ onEventClick,
68
+ onDateSelect,
69
+ onEventChange,
70
+ height = "auto",
71
+ ariaLabel,
72
+ onDatesChange,
73
+ onViewChange,
74
+ renderEvent,
75
+ fullCalendarProps,
76
+ texts
77
+ } = usePieceProps(props, THEME_NAME);
71
78
  const theme = useTheme();
72
79
  const palette = paletteOf(theme);
73
80
  const { t } = useToolkitTranslation("calendar", texts);
@@ -88,12 +95,12 @@ function EventCalendar({
88
95
  }
89
96
  };
90
97
  return /* @__PURE__ */ jsxs(Stack, { spacing: 2, role: "region", "aria-label": ariaLabel, children: [
91
- /* @__PURE__ */ jsxs(Stack, { direction: { xs: "column", md: "row" }, spacing: 1.5, sx: { alignItems: { md: "center" }, justifyContent: "space-between" }, children: [
92
- /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
98
+ /* @__PURE__ */ jsxs(CalendarToolbar, { direction: { xs: "column", md: "row" }, spacing: 1.5, ownSx: { alignItems: { md: "center" }, justifyContent: "space-between" }, children: [
99
+ /* @__PURE__ */ jsxs(CalendarNavigation, { direction: "row", spacing: 1, ownSx: { alignItems: "center" }, children: [
93
100
  /* @__PURE__ */ jsx(Tooltip, { title: t("previous"), children: /* @__PURE__ */ jsx(IconButton, { "aria-label": t("previous"), onClick: () => calendarApi()?.prev(), children: /* @__PURE__ */ jsx(ChevronLeftRoundedIcon, {}) }) }),
94
101
  /* @__PURE__ */ jsx(Tooltip, { title: t("next"), children: /* @__PURE__ */ jsx(IconButton, { "aria-label": t("next"), onClick: () => calendarApi()?.next(), children: /* @__PURE__ */ jsx(ChevronRightRoundedIcon, {}) }) }),
95
102
  /* @__PURE__ */ jsx(Button, { variant: "outlined", size: "small", onClick: () => calendarApi()?.today(), children: t("today") }),
96
- /* @__PURE__ */ jsx(Typography, { variant: "h6", component: "h2", sx: { pl: 1 }, "aria-live": "polite", children: capitalizeFirst(title) })
103
+ /* @__PURE__ */ jsx(CalendarTitle, { variant: "h6", component: "h2", ownSx: { pl: 1 }, "aria-live": "polite", children: capitalizeFirst(title) })
97
104
  ] }),
98
105
  views.length > 1 && /* @__PURE__ */ jsx(
99
106
  SegmentedControl,
@@ -105,7 +112,7 @@ function EventCalendar({
105
112
  }
106
113
  )
107
114
  ] }),
108
- /* @__PURE__ */ jsx(Box, { sx: calendarStylesOf, children: /* @__PURE__ */ jsx(
115
+ /* @__PURE__ */ jsx(CalendarCalendar, { ownSx: calendarStylesOf, children: /* @__PURE__ */ jsx(
109
116
  FullCalendar,
110
117
  {
111
118
  locale: language === "es" ? spanishLocale : "en",