@stackcraftmx/calcita 0.47.0 → 0.52.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.
- package/README.md +192 -13
- package/cli/calcita.js +17 -0
- package/cli/checkTexts.js +266 -0
- package/cli/packageVersions.js +6 -1
- package/cli/projectFiles.js +4 -2
- package/cli/templates/project/README.md.tpl +21 -1
- package/cli/templates/project/eslint.config.js.tpl +27 -0
- package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
- package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
- package/cli/templates/project/vite.config.js.tpl +8 -1
- package/dist/DataTable/AuditTrailDialog.js +3 -2
- package/dist/DataTable/AuditTrailDialog.js.map +1 -1
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
- package/dist/DataTable/EmptyState.js +3 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +3 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +3 -2
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +95 -28
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +8 -3
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
- package/dist/DataTable/StackedCell.js +60 -0
- package/dist/DataTable/StackedCell.js.map +1 -0
- package/dist/DataTable/TableCardsView.js +88 -0
- package/dist/DataTable/TableCardsView.js.map +1 -0
- package/dist/DataTable/columnWidths.js +30 -0
- package/dist/DataTable/columnWidths.js.map +1 -0
- package/dist/DataTable/columns.js +47 -1
- package/dist/DataTable/columns.js.map +1 -1
- package/dist/DataTable/fixedColumns.js +24 -0
- package/dist/DataTable/fixedColumns.js.map +1 -0
- package/dist/DataTable/useTableView.js +29 -0
- package/dist/DataTable/useTableView.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountProvider.js +1 -1
- package/dist/account/AccountSecuritySection.js +3 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/DemoTotpCodeButton.js +3 -2
- package/dist/account/DemoTotpCodeButton.js.map +1 -1
- package/dist/account/ProfileScreen.js +3 -2
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +3 -2
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/appShell/AccountMenu.js +3 -2
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +3 -2
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +3 -2
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +3 -2
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +11 -4
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +3 -2
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +3 -2
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +3 -2
- package/dist/appShell/BarSearch.js.map +1 -1
- package/dist/appShell/HeaderTools.js +3 -2
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +3 -2
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +3 -2
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +3 -2
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +3 -2
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +3 -2
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TabBarEditor.js +3 -2
- package/dist/appShell/TabBarEditor.js.map +1 -1
- package/dist/appShell/TopMenu.js +3 -2
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +3 -2
- package/dist/appShell/Wordmark.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +3 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +3 -2
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +3 -2
- package/dist/calendar/EventDetails.js.map +1 -1
- package/dist/datePickers/DateInput.js +28 -22
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +33 -27
- package/dist/datePickers/DateTimeInput.js.map +1 -1
- package/dist/datePickers/MonthInput.js +27 -21
- package/dist/datePickers/MonthInput.js.map +1 -1
- package/dist/datePickers/TimeInput.js +26 -20
- package/dist/datePickers/TimeInput.js.map +1 -1
- package/dist/datePickers/usePickerField.js +5 -4
- package/dist/datePickers/usePickerField.js.map +1 -1
- package/dist/downloads/DownloadManager.js +3 -2
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +3 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +3 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +3 -2
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/forms/FormProblemAlert.js +3 -2
- package/dist/forms/FormProblemAlert.js.map +1 -1
- package/dist/forms/FormWizard.js +21 -6
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/SubmitButton.js +7 -2
- package/dist/forms/SubmitButton.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +3 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/fieldRules.js +30 -0
- package/dist/forms/fieldRules.js.map +1 -0
- package/dist/forms/security/SecondStepForm.js +3 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +3 -2
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/forms/useValidationText.js +18 -0
- package/dist/forms/useValidationText.js.map +1 -0
- package/dist/forms/validation.js +35 -31
- package/dist/forms/validation.js.map +1 -1
- package/dist/forms/validationMessages.js +64 -7
- package/dist/forms/validationMessages.js.map +1 -1
- package/dist/forms.js +3 -0
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormProblemAlert.js +3 -2
- package/dist/hookForm/FormProblemAlert.js.map +1 -1
- package/dist/hookForm/FormWizard.js +19 -5
- package/dist/hookForm/FormWizard.js.map +1 -1
- package/dist/hookForm/SubmitButton.js +7 -2
- package/dist/hookForm/SubmitButton.js.map +1 -1
- package/dist/inbox/InboxLayout.js +3 -2
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +3 -2
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +3 -2
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/index.js +193 -163
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +32 -25
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +8 -5
- package/dist/inputs/CheckboxGroupInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +7 -4
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FieldFrame.js +10 -4
- package/dist/inputs/FieldFrame.js.map +1 -1
- package/dist/inputs/FormSection.js +1 -1
- package/dist/inputs/MaskedInput.js +1 -0
- package/dist/inputs/MaskedInput.js.map +1 -1
- package/dist/inputs/NumberInput.js +29 -24
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +8 -5
- package/dist/inputs/RepeatingFields.js.map +1 -1
- package/dist/inputs/SelectInput.js +8 -5
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +5 -3
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +8 -5
- package/dist/inputs/TransferListInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +48 -19
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/inputs/useWizardSteps.js +36 -0
- package/dist/inputs/useWizardSteps.js.map +1 -0
- package/dist/inputs/wizardSteps.js +32 -0
- package/dist/inputs/wizardSteps.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +3 -2
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/legal/CookieNotice.js +3 -2
- package/dist/legal/CookieNotice.js.map +1 -1
- package/dist/legal/TermsAcceptanceDialog.js +3 -2
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -1
- package/dist/locales/en.js +24 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +24 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +3 -2
- package/dist/maps/MapView.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +3 -2
- package/dist/mexico/CfdiSummary.js.map +1 -1
- package/dist/mexico/EfirmaFields.js +3 -2
- package/dist/mexico/EfirmaFields.js.map +1 -1
- package/dist/mexico/mexicoIdentifiers.js +5 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -1
- package/dist/mexico/mexicoRules.js +9 -4
- package/dist/mexico/mexicoRules.js.map +1 -1
- package/dist/mexico.js +4 -2
- package/dist/pwa/InstallAppSetting.js +3 -2
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +3 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +3 -2
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/react-hook-form.js +3 -0
- package/dist/react-hook-form.js.map +1 -1
- package/dist/richText/RichTextEditor.js +7 -4
- package/dist/richText/RichTextEditor.js.map +1 -1
- package/dist/richText/RichTextView.js +3 -2
- package/dist/richText/RichTextView.js.map +1 -1
- package/dist/security/ActivityLog.js +3 -2
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +3 -2
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +3 -2
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +21 -2
- package/dist/table.js.map +1 -1
- package/dist/theme/themePieces.js +70 -4
- package/dist/theme/themePieces.js.map +1 -1
- package/dist/toolkit/ToolkitProvider.js +8 -1
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -4
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/combineTexts.js +32 -0
- package/dist/toolkit/combineTexts.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +17 -3
- package/dist/toolkit/hashNavigation.js.map +1 -1
- package/dist/toolkit/navigationPaths.js +15 -0
- package/dist/toolkit/navigationPaths.js.map +1 -1
- package/dist/toolkit/pathIndex.js +66 -0
- package/dist/toolkit/pathIndex.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +19 -1
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
- package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
- package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
- package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
- package/dist/types/DataTable/EmptyState.d.ts +6 -9
- package/dist/types/DataTable/FilterChips.d.ts +6 -11
- package/dist/types/DataTable/GoToPage.d.ts +6 -9
- package/dist/types/DataTable/ServerTable.d.ts +16 -80
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +6 -13
- package/dist/types/DataTable/StackedCell.d.ts +12 -0
- package/dist/types/DataTable/TableCardsView.d.ts +27 -0
- package/dist/types/DataTable/columnWidths.d.ts +21 -0
- package/dist/types/DataTable/columns.d.ts +111 -0
- package/dist/types/DataTable/fixedColumns.d.ts +29 -0
- package/dist/types/DataTable/index.d.ts +6 -1
- package/dist/types/DataTable/useTableView.d.ts +22 -0
- package/dist/types/access/PermissionMatrix.d.ts +6 -10
- package/dist/types/account/AccountSecuritySection.d.ts +6 -7
- package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
- package/dist/types/account/ProfileScreen.d.ts +6 -18
- package/dist/types/account/SignInScreen.d.ts +6 -13
- package/dist/types/appShell/AccountMenu.d.ts +6 -11
- package/dist/types/appShell/AppLayout.d.ts +3 -40
- package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
- package/dist/types/appShell/AuthLayout.d.ts +3 -11
- package/dist/types/appShell/BarActionButton.d.ts +7 -10
- package/dist/types/appShell/BarActions.d.ts +6 -15
- package/dist/types/appShell/BarCapsule.d.ts +6 -9
- package/dist/types/appShell/BarSearch.d.ts +6 -15
- package/dist/types/appShell/HeaderTools.d.ts +6 -16
- package/dist/types/appShell/MobileTabBar.d.ts +6 -13
- package/dist/types/appShell/SettingsMenu.d.ts +6 -10
- package/dist/types/appShell/SideMenu.d.ts +6 -20
- package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
- package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
- package/dist/types/appShell/TabBarEditor.d.ts +6 -9
- package/dist/types/appShell/TopMenu.d.ts +6 -5
- package/dist/types/appShell/Wordmark.d.ts +6 -7
- package/dist/types/auth/PasswordChecklist.d.ts +6 -9
- package/dist/types/calendar/EventCalendar.d.ts +6 -23
- package/dist/types/calendar/EventDetails.d.ts +6 -10
- package/dist/types/datePickers/DateInput.d.ts +7 -22
- package/dist/types/datePickers/DateTimeInput.d.ts +7 -27
- package/dist/types/datePickers/MonthInput.d.ts +7 -21
- package/dist/types/datePickers/TimeInput.d.ts +7 -21
- package/dist/types/datePickers/usePickerField.d.ts +5 -1
- package/dist/types/downloads/DownloadManager.d.ts +6 -6
- package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
- package/dist/types/feedback/ModalWaiting.d.ts +6 -9
- package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
- package/dist/types/filepond/FileDropzone.d.ts +6 -29
- package/dist/types/forms/FormProblemAlert.d.ts +6 -5
- package/dist/types/forms/FormWizard.d.ts +12 -1
- package/dist/types/forms/SubmitButton.d.ts +6 -6
- package/dist/types/forms/auth/SignInForm.d.ts +6 -11
- package/dist/types/forms/fieldRules.d.ts +44 -0
- package/dist/types/forms/index.d.ts +1 -0
- package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
- package/dist/types/forms/useValidationText.d.ts +12 -0
- package/dist/types/forms/validation.d.ts +117 -174
- package/dist/types/forms/validationMessages.d.ts +54 -6
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
- package/dist/types/hookForm/FormWizard.d.ts +12 -1
- package/dist/types/hookForm/SubmitButton.d.ts +6 -13
- package/dist/types/inbox/InboxLayout.d.ts +6 -17
- package/dist/types/inbox/MessageList.d.ts +6 -18
- package/dist/types/inbox/MessageView.d.ts +6 -14
- package/dist/types/index.d.ts +10 -2
- package/dist/types/inputs/CatalogInput.d.ts +7 -24
- package/dist/types/inputs/CheckboxGroupInput.d.ts +7 -18
- package/dist/types/inputs/ColorInput.d.ts +7 -16
- package/dist/types/inputs/FieldFrame.d.ts +6 -10
- package/dist/types/inputs/MaskedInput.d.ts +3 -0
- package/dist/types/inputs/NumberInput.d.ts +7 -20
- package/dist/types/inputs/RepeatingFields.d.ts +7 -28
- package/dist/types/inputs/SelectInput.d.ts +7 -20
- package/dist/types/inputs/TextInput.d.ts +3 -0
- package/dist/types/inputs/TransferListInput.d.ts +7 -20
- package/dist/types/inputs/WizardLayout.d.ts +11 -21
- package/dist/types/inputs/useWizardSteps.d.ts +23 -0
- package/dist/types/inputs/wizardSteps.d.ts +65 -0
- package/dist/types/kanban/KanbanBoard.d.ts +6 -25
- package/dist/types/legal/CookieNotice.d.ts +6 -16
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +6 -18
- package/dist/types/locales/en.d.ts +24 -0
- package/dist/types/locales/es.d.ts +24 -0
- package/dist/types/maps/MapView.d.ts +6 -17
- package/dist/types/mexico/CfdiSummary.d.ts +6 -11
- package/dist/types/mexico/EfirmaFields.d.ts +6 -16
- package/dist/types/mexico/mexicoIdentifiers.d.ts +7 -0
- package/dist/types/mexico/mexicoRules.d.ts +19 -3
- package/dist/types/mexico.d.ts +2 -2
- package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
- package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/richText/RichTextEditor.d.ts +7 -22
- package/dist/types/richText/RichTextView.d.ts +6 -9
- package/dist/types/security/ActivityLog.d.ts +6 -9
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
- package/dist/types/security/SessionsCard.d.ts +6 -11
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/themePieces.d.ts +55 -3
- package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
- package/dist/types/toolkit/browserNavigation.d.ts +7 -3
- package/dist/types/toolkit/combineTexts.d.ts +6 -0
- package/dist/types/toolkit/hashNavigation.d.ts +4 -2
- package/dist/types/toolkit/navigationPaths.d.ts +35 -0
- package/dist/types/toolkit/pathIndex.d.ts +27 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
- package/dist/types/ui/actions/ActionsMenu.d.ts +9 -11
- package/dist/types/ui/actions/CopyButton.d.ts +6 -12
- package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
- package/dist/types/ui/actions/SecretField.d.ts +6 -11
- package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
- package/dist/types/ui/cards/RecordCard.d.ts +16 -29
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +6 -11
- package/dist/types/ui/countdown/Countdown.d.ts +6 -15
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +6 -13
- package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
- package/dist/types/ui/filters/FilterDrawer.d.ts +6 -17
- package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
- package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
- package/dist/types/ui/help/HelpButton.d.ts +6 -10
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +6 -14
- package/dist/types/ui/index.d.ts +1 -1
- package/dist/types/ui/labels/MarkedText.d.ts +10 -0
- package/dist/types/ui/labels/Tag.d.ts +6 -12
- package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
- package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
- package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
- package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
- package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
- package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
- package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
- package/dist/types/ui/layout/PageHeader.d.ts +6 -10
- package/dist/types/ui/layout/PageHeading.d.ts +6 -11
- package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
- package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
- package/dist/types/ui/layout/SectionCard.d.ts +6 -8
- package/dist/types/ui/metrics/BarList.d.ts +6 -12
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
- package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
- package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
- package/dist/types/ui/metrics/LineChart.d.ts +6 -20
- package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
- package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
- package/dist/types/ui/metrics/chartParts.d.ts +15 -30
- package/dist/types/ui/navigation/BackButton.d.ts +7 -9
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
- package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
- package/dist/types/ui/navigation/RouteTabs.d.ts +7 -12
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
- package/dist/types/upload/AttachmentsInput.d.ts +11 -1
- package/dist/types/upload/FileDropzone.d.ts +6 -21
- package/dist/types/upload/FileInput.d.ts +7 -24
- package/dist/types/upload/FileUploadInput.d.ts +7 -31
- package/dist/types/upload/ImageInput.d.ts +7 -25
- package/dist/types/upload/TemplateDownloadButton.d.ts +6 -18
- package/dist/ui/actions/ActionsMenu.js +14 -5
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +8 -3
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +3 -2
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -3
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +3 -2
- package/dist/ui/actions/SelectionActions.js.map +1 -1
- package/dist/ui/cards/RecordCard.js +10 -5
- package/dist/ui/cards/RecordCard.js.map +1 -1
- package/dist/ui/cards/RecordCardSkeleton.js +8 -3
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -1
- package/dist/ui/countdown/Countdown.js +3 -2
- package/dist/ui/countdown/Countdown.js.map +1 -1
- package/dist/ui/documents/PdfViewerDialog.js +3 -2
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -1
- package/dist/ui/filters/DateRangeFilter.js +3 -2
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/FilterDrawer.js +3 -2
- package/dist/ui/filters/FilterDrawer.js.map +1 -1
- package/dist/ui/filters/PeriodRangeSelector.js +3 -2
- package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +3 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/help/HelpButton.js +8 -3
- package/dist/ui/help/HelpButton.js.map +1 -1
- package/dist/ui/help/HelpCarouselDialog.js +3 -2
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -1
- package/dist/ui/labels/MarkedText.js +31 -0
- package/dist/ui/labels/MarkedText.js.map +1 -0
- package/dist/ui/labels/Tag.js +3 -2
- package/dist/ui/labels/Tag.js.map +1 -1
- package/dist/ui/labels/markedTextPieces.js +64 -0
- package/dist/ui/labels/markedTextPieces.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +3 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +3 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/DefinitionList.js +3 -2
- package/dist/ui/layout/DefinitionList.js.map +1 -1
- package/dist/ui/layout/DetailHeader.js +3 -2
- package/dist/ui/layout/DetailHeader.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +3 -2
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +3 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/GlassPanel.js +3 -2
- package/dist/ui/layout/GlassPanel.js.map +1 -1
- package/dist/ui/layout/PageHeader.js +3 -2
- package/dist/ui/layout/PageHeader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +3 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/layout/ProfileHeader.js +3 -2
- package/dist/ui/layout/ProfileHeader.js.map +1 -1
- package/dist/ui/layout/ScrollableRow.js +3 -2
- package/dist/ui/layout/ScrollableRow.js.map +1 -1
- package/dist/ui/layout/SectionCard.js +3 -2
- package/dist/ui/layout/SectionCard.js.map +1 -1
- package/dist/ui/metrics/BarList.js +3 -2
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +3 -2
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +3 -2
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +3 -2
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +3 -2
- package/dist/ui/metrics/LineChart.js.map +1 -1
- package/dist/ui/metrics/MetricCard.js +3 -2
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +3 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +7 -4
- package/dist/ui/metrics/chartParts.js.map +1 -1
- package/dist/ui/navigation/BackButton.js +7 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +3 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +3 -2
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/navigation/RouteTabs.js +8 -4
- package/dist/ui/navigation/RouteTabs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +3 -2
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/AttachmentsInput.js +9 -6
- package/dist/upload/AttachmentsInput.js.map +1 -1
- package/dist/upload/FileChip.js +2 -1
- package/dist/upload/FileChip.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -4
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +9 -6
- package/dist/upload/FileInput.js.map +1 -1
- package/dist/upload/FileUploadInput.js +9 -6
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/dist/upload/ImageInput.js +8 -5
- package/dist/upload/ImageInput.js.map +1 -1
- package/dist/upload/TemplateDownloadButton.js +3 -2
- package/dist/upload/TemplateDownloadButton.js.map +1 -1
- package/package.json +1 -1
- package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ActionsMenu.js","sources":["../../../src/ui/actions/ActionsMenu.jsx"],"sourcesContent":["// Un botón «⋯» con un menú de acciones: el de cada tarjeta del tablero, el de cada columna, el de cada mensaje de la bandeja,\n// el de cada tarjeta de registro. Las acciones las da la pantalla (label ya traducido, ícono, color, apagada); sin acciones\n// no se pinta nada.\n//\n// <ActionsMenu label=\"Acciones de «Junta de cierre»\"\n// actions={[{key: 'delete', label: 'Borrar', icon: <DeleteOutlineIcon fontSize=\"small\" />, color: 'error', onClick: remove}]} />\n//\n// Tan flexible como un menú de sistema (cada renglón es uno de estos):\n// {key, label, icon?, description?, color?, disabled?, onClick} una acción\n// {key, label, href, download?} una liga (bajar un documento, abrir otra página)\n// {key, label, children: [...]} un submenú: se abre DENTRO del mismo menú, con «‹ Regresar»\n// arriba (igual en el teléfono); también con → y ←\n// {type: 'section', key, label} un título de sección\n// {type: 'divider', key} una línea\n// {key, render: ({close}) => …} un renglón propio (abre su ventana y cierra el menú)\n//\n// El clic no llega a lo de abajo: no levanta una tarjeta que se arrastra ni abre el renglón donde vive.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActionsMenu con defaultProps y\n// styleOverrides de sus partes: item, itemIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';\nimport ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\n\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n/** Los renglones que no son acciones. */\nexport const ACTIONS_MENU_ROW = Object.freeze({section: 'section', divider: 'divider'});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaActionsMenu)\nconst THEME_NAME = 'CalcitaActionsMenu';\nconst ActionsItem = piecePart(THEME_NAME, 'Item', MenuItem);\nconst ActionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\n\n/**\n * Los renglones del nivel abierto y sus títulos, siguiendo las llaves de los submenús abiertos.\n *\n * @param {object[]} actions\n * @param {string[]} path las llaves de los submenús abiertos, del primero al último\n * @returns {{items: object[], parents: object[]}}\n */\nfunction levelOf(actions, path) {\n const parents = [];\n let items = actions;\n for (const key of path) {\n const parent = items.find((item) => item.key === key);\n if (!parent?.children) {\n break;\n }\n parents.push(parent);\n items = parent.children;\n }\n return {items, parents};\n}\n\n/**\n * @param {object} props\n * @param {string} props.label el nombre del botón (lector de pantalla y su tooltip), ya traducido\n * @param {Array<{key: string, type?: string, label?: string, icon?: import('react').ReactNode, description?: string, color?: string, disabled?: boolean, onClick?: () => void, href?: string, download?: string|boolean, children?: object[], render?: (helpers: {close: () => void}) => import('react').ReactNode}>} [props.actions]\n * @param {import('react').ReactNode} [props.icon] otro ícono para el botón (por omisión «⋯»)\n * @param {'small'|'medium'} [props.size]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function ActionsMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActionsMenu.defaultProps) y encima lo que llega aquí\n const {label, actions, icon, size = 'small', texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const [anchor, setAnchor] = useState(null);\n // los submenús abiertos (llaves), del primero al último\n const [path, setPath] = useState([]);\n const listRef = useRef(null);\n const {items, parents} = levelOf(actions ?? [], path);\n\n // al entrar o salir de un submenú, el foco va a su primer renglón (con el teclado se sigue sin el ratón)\n useEffect(() => {\n if (anchor) {\n listRef.current?.querySelector('[role=\"menuitem\"]:not([aria-disabled=\"true\"])')?.focus();\n }\n }, [path, anchor]);\n\n if (!actions?.length) {\n return null;\n }\n const close = () => {\n setAnchor(null);\n setPath([]);\n };\n const goBack = () => setPath((current) => current.slice(0, -1));\n\n return (\n <>\n <Tooltip title={label}>\n <IconButton size={size} aria-label={label} aria-haspopup=\"menu\"\n onClick={(event) => {\n event.stopPropagation();\n setAnchor(event.currentTarget);\n }}>\n {icon ?? <MoreHorizRoundedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n <Menu anchorEl={anchor} open={Boolean(anchor)} onClose={close} onClick={(event) => event.stopPropagation()}\n slotProps={{list: {ref: listRef, onKeyDown: (event) => {\n if (event.key === 'ArrowLeft' && path.length > 0) {\n event.preventDefault();\n goBack();\n }\n }}}}>\n {parents.length > 0 && [\n <ActionsItem key=\"__back\" onClick={goBack} ownSx={{fontWeight: 600}}>\n <ActionsItemIcon><ChevronLeftRoundedIcon fontSize=\"small\" /></ActionsItemIcon>\n <ListItemText primary={parents.at(-1).label} secondary={t('actionsMenu.back')} />\n </ActionsItem>,\n <Divider key=\"__back-divider\" />,\n ]}\n {items.map((item) => {\n if (item.type === ACTIONS_MENU_ROW.divider) {\n return <Divider key={item.key} />;\n }\n if (item.type === ACTIONS_MENU_ROW.section) {\n return <ListSubheader key={item.key} sx={{lineHeight: 2.5, bgcolor: 'transparent'}}>{item.label}</ListSubheader>;\n }\n if (item.render) {\n return <li key={item.key}>{item.render({close})}</li>;\n }\n const hasChildren = Boolean(item.children?.length);\n return (\n <ActionsItem\n key={item.key}\n disabled={item.disabled}\n {...(item.href && {component: 'a', href: item.href, download: item.download})}\n aria-haspopup={hasChildren ? 'menu' : undefined}\n onKeyDown={(event) => {\n if (hasChildren && event.key === 'ArrowRight') {\n event.preventDefault();\n setPath((current) => [...current, item.key]);\n }\n }}\n onClick={() => {\n if (hasChildren) {\n setPath((current) => [...current, item.key]);\n return;\n }\n close();\n item.onClick?.();\n }}\n ownSx={item.color ? {color: `${item.color}.main`} : undefined}\n >\n {item.icon && <ActionsItemIcon ownSx={item.color ? {color: 'inherit'} : undefined}>{item.icon}</ActionsItemIcon>}\n <ListItemText primary={item.label} secondary={item.description} />\n {hasChildren && <ChevronRightRoundedIcon fontSize=\"small\" sx={{ml: 2, color: 'text.secondary'}} />}\n </ActionsItem>\n );\n })}\n </Menu>\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"ActionsMenu.js","sources":["../../../src/ui/actions/ActionsMenu.jsx"],"sourcesContent":["// Un botón «⋯» con un menú de acciones: el de cada tarjeta del tablero, el de cada columna, el de cada mensaje de la bandeja,\n// el de cada tarjeta de registro. Las acciones las da la pantalla (label ya traducido, ícono, color, apagada); sin acciones\n// no se pinta nada.\n//\n// <ActionsMenu label=\"Acciones de «Junta de cierre»\"\n// actions={[{key: 'delete', label: 'Borrar', icon: <DeleteOutlineIcon fontSize=\"small\" />, color: 'error', onClick: remove}]} />\n//\n// Tan flexible como un menú de sistema (cada renglón es uno de estos):\n// {key, label, icon?, description?, color?, disabled?, onClick} una acción; onClick recibe el evento del clic\n// {key, label, to, replace?} una pantalla de la app, con la navegación del proyecto\n// (react-router, Next o la del navegador): el clic normal\n// navega sin recargar y Cmd/Ctrl+clic o el botón de en\n// medio la abren en otra pestaña, como cualquier liga\n// {key, label, href, download?} una liga de afuera (bajar un documento, otro sistema)\n// {key, label, children: [...]} un submenú: se abre DENTRO del mismo menú, con «‹ Regresar»\n// arriba (igual en el teléfono); también con → y ←\n// {type: 'section', key, label} un título de sección\n// {type: 'divider', key} una línea\n// {key, render: ({close}) => …} un renglón propio (abre su ventana y cierra el menú)\n//\n// El clic no llega a lo de abajo: no levanta una tarjeta que se arrastra ni abre el renglón donde vive.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaActionsMenu con defaultProps y\n// styleOverrides de sus partes: item, itemIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Menu from '@mui/material/Menu';\nimport MenuItem from '@mui/material/MenuItem';\nimport Tooltip from '@mui/material/Tooltip';\nimport ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';\nimport ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';\nimport MoreHorizRoundedIcon from '@mui/icons-material/MoreHorizRounded';\n\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitNavigation} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n/** Los renglones que no son acciones. */\nexport const ACTIONS_MENU_ROW = Object.freeze({section: 'section', divider: 'divider'});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaActionsMenu)\nconst THEME_NAME = 'CalcitaActionsMenu';\nconst ActionsItem = piecePart(THEME_NAME, 'Item', MenuItem);\nconst ActionsItemIcon = piecePart(THEME_NAME, 'ItemIcon', ListItemIcon);\n\n/**\n * Los renglones del nivel abierto y sus títulos, siguiendo las llaves de los submenús abiertos.\n *\n * @param {object[]} actions\n * @param {string[]} path las llaves de los submenús abiertos, del primero al último\n * @returns {{items: object[], parents: object[]}}\n */\nfunction levelOf(actions, path) {\n const parents = [];\n let items = actions;\n for (const key of path) {\n const parent = items.find((item) => item.key === key);\n if (!parent?.children) {\n break;\n }\n parents.push(parent);\n items = parent.children;\n }\n return {items, parents};\n}\n\n/**\n * @param {object} props\n * @param {string} props.label el nombre del botón (lector de pantalla y su tooltip), ya traducido\n * @param {Array<{key: string, type?: string, label?: string, icon?: import('react').ReactNode, description?: string, color?: string, disabled?: boolean, onClick?: (event: import('react').MouseEvent<HTMLElement>) => void, to?: string, replace?: boolean, href?: string, download?: string|boolean, children?: object[], render?: (helpers: {close: () => void}) => import('react').ReactNode}>} [props.actions]\n * @param {import('react').ReactNode} [props.icon] otro ícono para el botón (por omisión «⋯»)\n * @param {'small'|'medium'} [props.size]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction ActionsMenuContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaActionsMenu.defaultProps) y encima lo que llega aquí\n const {label, actions, icon, size = 'small', texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n // las pantallas de la app (to) van con el enlace de la navegación del proyecto: así el clic normal no recarga la app\n const {Link} = useToolkitNavigation();\n const [anchor, setAnchor] = useState(null);\n // los submenús abiertos (llaves), del primero al último\n const [path, setPath] = useState([]);\n const listRef = useRef(null);\n const {items, parents} = levelOf(actions ?? [], path);\n\n // al entrar o salir de un submenú, el foco va a su primer renglón (con el teclado se sigue sin el ratón)\n useEffect(() => {\n if (anchor) {\n listRef.current?.querySelector('[role=\"menuitem\"]:not([aria-disabled=\"true\"])')?.focus();\n }\n }, [path, anchor]);\n\n if (!actions?.length) {\n return null;\n }\n const close = () => {\n setAnchor(null);\n setPath([]);\n };\n const goBack = () => setPath((current) => current.slice(0, -1));\n\n return (\n <>\n <Tooltip title={label}>\n <IconButton size={size} aria-label={label} aria-haspopup=\"menu\"\n onClick={(event) => {\n event.stopPropagation();\n setAnchor(event.currentTarget);\n }}>\n {icon ?? <MoreHorizRoundedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n <Menu anchorEl={anchor} open={Boolean(anchor)} onClose={close} onClick={(event) => event.stopPropagation()}\n slotProps={{list: {ref: listRef, onKeyDown: (event) => {\n if (event.key === 'ArrowLeft' && path.length > 0) {\n event.preventDefault();\n goBack();\n }\n }}}}>\n {parents.length > 0 && [\n <ActionsItem key=\"__back\" onClick={goBack} ownSx={{fontWeight: 600}}>\n <ActionsItemIcon><ChevronLeftRoundedIcon fontSize=\"small\" /></ActionsItemIcon>\n <ListItemText primary={parents.at(-1).label} secondary={t('actionsMenu.back')} />\n </ActionsItem>,\n <Divider key=\"__back-divider\" />,\n ]}\n {items.map((item) => {\n if (item.type === ACTIONS_MENU_ROW.divider) {\n return <Divider key={item.key} />;\n }\n if (item.type === ACTIONS_MENU_ROW.section) {\n return <ListSubheader key={item.key} sx={{lineHeight: 2.5, bgcolor: 'transparent'}}>{item.label}</ListSubheader>;\n }\n if (item.render) {\n return <li key={item.key}>{item.render({close})}</li>;\n }\n const hasChildren = Boolean(item.children?.length);\n // a dónde lleva el renglón: una pantalla de la app (to), una liga de afuera (href) o nada (una acción)\n let destinationProps = {};\n if (item.to) {\n destinationProps = {component: Link, to: item.to, ...(item.replace && {replace: true})};\n } else if (item.href) {\n destinationProps = {component: 'a', href: item.href, download: item.download};\n }\n return (\n <ActionsItem\n key={item.key}\n disabled={item.disabled}\n {...destinationProps}\n aria-haspopup={hasChildren ? 'menu' : undefined}\n onKeyDown={(event) => {\n if (hasChildren && event.key === 'ArrowRight') {\n event.preventDefault();\n setPath((current) => [...current, item.key]);\n }\n }}\n onClick={(event) => {\n if (hasChildren) {\n setPath((current) => [...current, item.key]);\n return;\n }\n close();\n // con el evento la pantalla decide (saber si fue con Cmd/Ctrl, o detener una liga propia)\n item.onClick?.(event);\n }}\n ownSx={item.color ? {color: `${item.color}.main`} : undefined}\n >\n {item.icon && <ActionsItemIcon ownSx={item.color ? {color: 'inherit'} : undefined}>{item.icon}</ActionsItemIcon>}\n <ListItemText primary={item.label} secondary={item.description} />\n {hasChildren && <ChevronRightRoundedIcon fontSize=\"small\" sx={{ml: 2, color: 'text.secondary'}} />}\n </ActionsItem>\n );\n })}\n </Menu>\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst ActionsMenu = withPieceSlots(ActionsMenuContent, THEME_NAME);\nexport default ActionsMenu;\n"],"names":[],"mappings":"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"}
|
|
@@ -9,13 +9,17 @@ import CopyIcon from "@mui/icons-material/ContentCopyOutlined";
|
|
|
9
9
|
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
|
10
10
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
11
11
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
12
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
13
|
+
const THEME_NAME = "CalcitaCopyButton";
|
|
14
|
+
const CopyButtonButton = piecePart(THEME_NAME, "Button", IconButton);
|
|
12
15
|
const FEEDBACK_MILLISECONDS = 2e3;
|
|
13
16
|
const COPY_STATE = Object.freeze({ idle: "idle", copied: "copied", failed: "failed" });
|
|
14
17
|
const ICON_BY_STATE = Object.freeze({ [COPY_STATE.idle]: CopyIcon, [COPY_STATE.copied]: CheckIcon, [COPY_STATE.failed]: ErrorOutlineIcon });
|
|
15
18
|
const COLOR_BY_STATE = Object.freeze({ [COPY_STATE.idle]: void 0, [COPY_STATE.copied]: "success.dark", [COPY_STATE.failed]: "error.dark" });
|
|
16
19
|
const VISUALLY_HIDDEN_SX = { position: "absolute", width: "1px", height: "1px", overflow: "hidden", clip: "rect(0 0 0 0)", whiteSpace: "nowrap" };
|
|
17
|
-
function
|
|
18
|
-
|
|
20
|
+
function CopyButtonContent(props) {
|
|
21
|
+
const { value, label, size = "small", onCopied, onCopyFailed, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
22
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
19
23
|
const { t } = useToolkitTranslation("common", texts);
|
|
20
24
|
const [copyState, setCopyState] = useState(COPY_STATE.idle);
|
|
21
25
|
const resetTimer = useRef(null);
|
|
@@ -40,10 +44,11 @@ function CopyButton({ value, label, size = "small", onCopied, onCopyFailed, text
|
|
|
40
44
|
}[copyState];
|
|
41
45
|
const StateIcon = ICON_BY_STATE[copyState];
|
|
42
46
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
43
|
-
/* @__PURE__ */ jsx(Tooltip, { title: feedbackText, open: copyState === COPY_STATE.idle ? void 0 : true, placement: "top", children: /* @__PURE__ */ jsx(
|
|
47
|
+
/* @__PURE__ */ jsx(Tooltip, { title: feedbackText, open: copyState === COPY_STATE.idle ? void 0 : true, placement: "top", children: /* @__PURE__ */ jsx(CopyButtonButton, { size, "aria-label": buttonLabel, onClick: copy, sx: { color: COLOR_BY_STATE[copyState] }, children: /* @__PURE__ */ jsx(StateIcon, { fontSize: "inherit" }) }) }),
|
|
44
48
|
/* @__PURE__ */ jsx(Box, { component: "span", role: "status", "aria-live": "polite", sx: VISUALLY_HIDDEN_SX, children: copyState === COPY_STATE.idle ? "" : feedbackText })
|
|
45
49
|
] });
|
|
46
50
|
}
|
|
51
|
+
const CopyButton = withPieceSlots(CopyButtonContent, THEME_NAME);
|
|
47
52
|
export {
|
|
48
53
|
CopyButton as default
|
|
49
54
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyButton.js","sources":["../../../src/ui/actions/CopyButton.jsx"],"sourcesContent":["// Botón para copiar al portapapeles: un ícono que pone el texto y lo confirma ahí mismo («Copiado» un par de segundos, con la\n// palomita). Va donde copiar a mano cuesta trabajo: un folio, un texto largo que en pantalla va cortado, un renglón entero\n// (con tableRowTextOf: separado por tabuladores, al pegarlo en Excel cada dato cae en su celda).\n//\n// Si el navegador no deja copiar (una página en http, dentro de un iframe, sin permiso), el botón lo dice en rojo en lugar\n// de fingir que copió, y avisa con onCopyFailed. La confirmación también se anuncia a los lectores de pantalla (el globo no\n// siempre se anuncia).\n//\n// value puede ser una función: se llama al picar (arma el renglón solo si de verdad se copia).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// cuánto se queda la confirmación (copiado o no se pudo) antes de regresar al ícono de copiar\nconst FEEDBACK_MILLISECONDS = 2000;\n// en qué va el botón: listo, copiado o no se pudo\nconst COPY_STATE = Object.freeze({idle: 'idle', copied: 'copied', failed: 'failed'});\nconst ICON_BY_STATE = Object.freeze({[COPY_STATE.idle]: CopyIcon, [COPY_STATE.copied]: CheckIcon, [COPY_STATE.failed]: ErrorOutlineIcon});\nconst COLOR_BY_STATE = Object.freeze({[COPY_STATE.idle]: undefined, [COPY_STATE.copied]: 'success.dark', [COPY_STATE.failed]: 'error.dark'});\n// el aviso para lectores de pantalla, escondido a la vista. OJO: '1px' en texto; en MUI un width: 1 es 100% y el texto\n// escondido ensanchaba la página\nconst VISUALLY_HIDDEN_SX = {position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap'};\n\n/**\n * @param {object} props\n * @param {string|(() => string)} props.value lo que se copia; una función se llama al picar\n * @param {string} [props.label] ya traducido: qué copia («Copiar el folio»); por omisión «Copiar». Es el globo y el nombre\n * del botón para un lector de pantalla\n * @param {'small'|'medium'} [props.size]\n * @param {() => void} [props.onCopied]\n * @param {(error: unknown) => void} [props.onCopyFailed] el navegador no dejó copiar (el botón ya lo dice en rojo)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\
|
|
1
|
+
{"version":3,"file":"CopyButton.js","sources":["../../../src/ui/actions/CopyButton.jsx"],"sourcesContent":["// Botón para copiar al portapapeles: un ícono que pone el texto y lo confirma ahí mismo («Copiado» un par de segundos, con la\n// palomita). Va donde copiar a mano cuesta trabajo: un folio, un texto largo que en pantalla va cortado, un renglón entero\n// (con tableRowTextOf: separado por tabuladores, al pegarlo en Excel cada dato cae en su celda).\n//\n// Si el navegador no deja copiar (una página en http, dentro de un iframe, sin permiso), el botón lo dice en rojo en lugar\n// de fingir que copió, y avisa con onCopyFailed. La confirmación también se anuncia a los lectores de pantalla (el globo no\n// siempre se anuncia).\n//\n// value puede ser una función: se llama al picar (arma el renglón solo si de verdad se copia).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaCopyButton con defaultProps, styleOverrides de\n// sus partes (button) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useEffect, useRef, useState} from 'react';\nimport Box from '@mui/material/Box';\nimport IconButton from '@mui/material/IconButton';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport ErrorOutlineIcon from '@mui/icons-material/ErrorOutline';\n\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaCopyButton)\nconst THEME_NAME = 'CalcitaCopyButton';\nconst CopyButtonButton = piecePart(THEME_NAME, 'Button', IconButton);\n\n// cuánto se queda la confirmación (copiado o no se pudo) antes de regresar al ícono de copiar\nconst FEEDBACK_MILLISECONDS = 2000;\n// en qué va el botón: listo, copiado o no se pudo\nconst COPY_STATE = Object.freeze({idle: 'idle', copied: 'copied', failed: 'failed'});\nconst ICON_BY_STATE = Object.freeze({[COPY_STATE.idle]: CopyIcon, [COPY_STATE.copied]: CheckIcon, [COPY_STATE.failed]: ErrorOutlineIcon});\nconst COLOR_BY_STATE = Object.freeze({[COPY_STATE.idle]: undefined, [COPY_STATE.copied]: 'success.dark', [COPY_STATE.failed]: 'error.dark'});\n// el aviso para lectores de pantalla, escondido a la vista. OJO: '1px' en texto; en MUI un width: 1 es 100% y el texto\n// escondido ensanchaba la página\nconst VISUALLY_HIDDEN_SX = {position: 'absolute', width: '1px', height: '1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap'};\n\n/**\n * @param {object} props\n * @param {string|(() => string)} props.value lo que se copia; una función se llama al picar\n * @param {string} [props.label] ya traducido: qué copia («Copiar el folio»); por omisión «Copiar». Es el globo y el nombre\n * del botón para un lector de pantalla\n * @param {'small'|'medium'} [props.size]\n * @param {() => void} [props.onCopied]\n * @param {(error: unknown) => void} [props.onCopyFailed] el navegador no dejó copiar (el botón ya lo dice en rojo)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction CopyButtonContent(props) {\n const {value, label, size = 'small', onCopied, onCopyFailed, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const [copyState, setCopyState] = useState(COPY_STATE.idle);\n const resetTimer = useRef(null);\n const buttonLabel = label ?? t('form.copy');\n\n // al desmontar no se queda un temporizador vivo\n useEffect(() => () => clearTimeout(resetTimer.current), []);\n\n const copy = async () => {\n clearTimeout(resetTimer.current);\n try {\n await navigator.clipboard.writeText(typeof value === 'function' ? value() : value);\n setCopyState(COPY_STATE.copied);\n onCopied?.();\n } catch (error) {\n setCopyState(COPY_STATE.failed);\n onCopyFailed?.(error);\n }\n resetTimer.current = setTimeout(() => setCopyState(COPY_STATE.idle), FEEDBACK_MILLISECONDS);\n };\n\n const feedbackText = {\n [COPY_STATE.idle]: buttonLabel,\n [COPY_STATE.copied]: t('form.copied'),\n [COPY_STATE.failed]: t('form.copyFailed'),\n }[copyState];\n const StateIcon = ICON_BY_STATE[copyState];\n\n return (\n <>\n {/* la confirmación se queda abierta mientras dura, aunque el cursor ya no esté encima; arriba y no a un lado, para no\n tapar el dato de junto */}\n <Tooltip title={feedbackText} open={copyState === COPY_STATE.idle ? undefined : true} placement=\"top\">\n <CopyButtonButton size={size} aria-label={buttonLabel} onClick={copy} sx={{color: COLOR_BY_STATE[copyState]}}>\n <StateIcon fontSize=\"inherit\" />\n </CopyButtonButton>\n </Tooltip>\n <Box component=\"span\" role=\"status\" aria-live=\"polite\" sx={VISUALLY_HIDDEN_SX}>\n {copyState === COPY_STATE.idle ? '' : feedbackText}\n </Box>\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst CopyButton = withPieceSlots(CopyButtonContent, THEME_NAME);\nexport default CopyButton;\n"],"names":[],"mappings":";;;;;;;;;;;;AA2BA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,UAAU,UAAU;AAGnE,MAAM,wBAAwB;AAE9B,MAAM,aAAa,OAAO,OAAO,EAAC,MAAM,QAAQ,QAAQ,UAAU,QAAQ,UAAS;AACnF,MAAM,gBAAgB,OAAO,OAAO,EAAC,CAAC,WAAW,IAAI,GAAG,UAAU,CAAC,WAAW,MAAM,GAAG,WAAW,CAAC,WAAW,MAAM,GAAG,kBAAiB;AACxI,MAAM,iBAAiB,OAAO,OAAO,EAAC,CAAC,WAAW,IAAI,GAAG,QAAW,CAAC,WAAW,MAAM,GAAG,gBAAgB,CAAC,WAAW,MAAM,GAAG,cAAa;AAG3I,MAAM,qBAAqB,EAAC,UAAU,YAAY,OAAO,OAAO,QAAQ,OAAO,UAAU,UAAU,MAAM,iBAAiB,YAAY,SAAA;AAYtI,SAAS,kBAAkB,OAAO;AAC9B,QAAM,EAAC,OAAO,OAAO,OAAO,SAAS,UAAU,cAAc,OAAO,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AACtH,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,WAAW,IAAI;AAC1D,QAAM,aAAa,OAAO,IAAI;AAC9B,QAAM,cAAc,SAAS,EAAE,WAAW;AAG1C,YAAU,MAAM,MAAM,aAAa,WAAW,OAAO,GAAG,CAAA,CAAE;AAE1D,QAAM,OAAO,YAAY;AACrB,iBAAa,WAAW,OAAO;AAC/B,QAAI;AACA,YAAM,UAAU,UAAU,UAAU,OAAO,UAAU,aAAa,MAAA,IAAU,KAAK;AACjF,mBAAa,WAAW,MAAM;AAC9B,iBAAA;AAAA,IACJ,SAAS,OAAO;AACZ,mBAAa,WAAW,MAAM;AAC9B,qBAAe,KAAK;AAAA,IACxB;AACA,eAAW,UAAU,WAAW,MAAM,aAAa,WAAW,IAAI,GAAG,qBAAqB;AAAA,EAC9F;AAEA,QAAM,eAAe;AAAA,IACjB,CAAC,WAAW,IAAI,GAAG;AAAA,IACnB,CAAC,WAAW,MAAM,GAAG,EAAE,aAAa;AAAA,IACpC,CAAC,WAAW,MAAM,GAAG,EAAE,iBAAiB;AAAA,EAAA,EAC1C,SAAS;AACX,QAAM,YAAY,cAAc,SAAS;AAEzC,SACI,qBAAA,UAAA,EAGI,UAAA;AAAA,IAAA,oBAAC,SAAA,EAAQ,OAAO,cAAc,MAAM,cAAc,WAAW,OAAO,SAAY,MAAM,WAAU,OAC5F,UAAA,oBAAC,oBAAiB,MAAY,cAAY,aAAa,SAAS,MAAM,IAAI,EAAC,OAAO,eAAe,SAAS,EAAA,GACtG,UAAA,oBAAC,WAAA,EAAU,UAAS,UAAA,CAAU,GAClC,GACJ;AAAA,IACA,oBAAC,KAAA,EAAI,WAAU,QAAO,MAAK,UAAS,aAAU,UAAS,IAAI,oBACtD,UAAA,cAAc,WAAW,OAAO,KAAK,aAAA,CAC1C;AAAA,EAAA,GACJ;AAER;AAGA,MAAM,aAAa,eAAe,mBAAmB,UAAU;"}
|
|
@@ -16,11 +16,11 @@ import { startDownload } from "../../downloads/downloadQueue.js";
|
|
|
16
16
|
import { useFileTypes } from "../../downloads/fileTypes.js";
|
|
17
17
|
import ConfirmDialog from "../../feedback/ConfirmDialog.js";
|
|
18
18
|
import { useConfirmAction } from "../../feedback/useConfirmAction.js";
|
|
19
|
-
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
19
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
20
20
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
21
21
|
const THEME_NAME = "CalcitaDownloadMenu";
|
|
22
22
|
const DownloadButton = piecePart(THEME_NAME, "Button", Button);
|
|
23
|
-
function
|
|
23
|
+
function DownloadMenuContent(props) {
|
|
24
24
|
const {
|
|
25
25
|
formats,
|
|
26
26
|
download,
|
|
@@ -163,6 +163,7 @@ function DownloadMenu(props) {
|
|
|
163
163
|
)
|
|
164
164
|
] });
|
|
165
165
|
}
|
|
166
|
+
const DownloadMenu = withPieceSlots(DownloadMenuContent, THEME_NAME);
|
|
166
167
|
export {
|
|
167
168
|
DownloadMenu as default
|
|
168
169
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DownloadMenu.js","sources":["../../../src/ui/actions/DownloadMenu.jsx"],"sourcesContent":["// \"Descargar ▾\" con los formatos de un reporte: PDF para leer o archivar, Excel o CSV para trabajar.\n//\n// Un solo botón y no tres: en la barra de una pantalla tres botones de descarga compiten con la acción principal.\n// Se acomoda a donde quedó, como BarActionButton: en una cápsula de la barra (las `actions` de PageHeading con cápsulas)\n// es solo el ícono con su nombre en el tooltip, como las demás acciones; antes salía la píldora «Descargar ▾» dentro de\n// la cápsula y se veía una cápsula dentro de otra. En el «⋯» del teléfono es un renglón que abre los formatos.\n// Quien llama pasa QUÉ descargar por formato (download(format, transferOptions) → {blob, fileName}, la forma de\n// un cliente HTTP que descarga). La descarga va al panel de descargas (startDownload, downloads): el diálogo se cierra al\n// confirmar y el avance, el aviso de \"descargado\" y el error se ven allá, mientras la persona sigue trabajando.\n//\n// Regla de tablas: una descarga pasa por un diálogo de confirmación que dice qué se va a bajar (p. ej. que la base\n// completa de clientes tarda unos minutos). Con `confirmation` el formato elegido NO se descarga de inmediato: abre\n// ConfirmDialog (máquina confirmActionMachine) y solo al confirmar se pide el archivo, una sola vez aunque haya doble clic.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaDownloadMenu con defaultProps y\n// styleOverrides de sus partes: button.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\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 Tooltip from '@mui/material/Tooltip';\nimport ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\n\nimport {BAR_ACTIONS_PLACEMENT, useBarActionsPlacement, useCloseBarActionsMenu} from '@/appShell/barActionsSlot.js';\nimport {startDownload} from '@/downloads/downloadQueue.js';\nimport {useFileTypes} from '@/downloads/fileTypes.js';\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaDownloadMenu)\nconst THEME_NAME = 'CalcitaDownloadMenu';\nconst DownloadButton = piecePart(THEME_NAME, 'Button', Button);\n\n\n/**\n * @param {object} props\n * @param {string[]} props.formats los tipos de archivo, en orden: los de la casa (xlsx, csv, pdf) y los que el proyecto declare\n * en ToolkitProvider fileTypes (zip, xml…)\n * @param {(format: string, transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} props.download\n * @param {string} [props.label] ya traducido; por defecto \"Descargar\"\n * @param {string} [props.downloadLabel] ya traducido; lo que se ve en el panel de descargas antes de saber el nombre\n * del archivo\n * @param {boolean} [props.disabled]\n * @param {(format: string) => {title: string, description: import('react').ReactNode, confirmLabel?: string}} [props.confirmation]\n * @param {(format: string) => void} [props.onDownloaded] p. ej. volver a pedir la tabla (si la descarga cambia algo en\n * el servidor)\n * @param {(format: string, error: unknown) => void} [props.onFailed] cuando la descarga no se pudo (el panel ya lo dice)\n * @param {(format: string) => void} [props.onCanceled] cuando la persona la canceló desde el panel\n * @param {'button'|'icon'} [props.variant] icon = solo el icono (columna de acciones de una tabla)\n * @param {string} [props.ariaLabel] ya traducido; obligatorio en la variante icon\n * @param {(format: string) => void} [props.onChooseFormat] quien llama se encarga de confirmar y descargar\n * @param {import('react').ElementType} [props.icon] ícono del botón; por defecto el de descarga. Distingue dos descargas en la\n * misma fila\n *\n * IMPORTANTE: hay que pasar `confirmation` u `onChooseFormat`. Sin ninguno de los dos el menú no descarga nada (regla de\n * tablas: toda descarga pasa por confirmación).\n *\n * OJO onChooseFormat: dentro de una celda del DataGrid el diálogo de confirmación NO puede vivir en la celda (el grid\n * virtualiza y vuelve a pintar sus celdas; el diálogo se perdía al elegir el formato). Ahí el menú solo avisa qué formato\n * se eligió y la pantalla abre SU confirmación (useConfirmAction) fuera de la tabla.\n */\nexport default function DownloadMenu(props) {\n // lo que el proyecto puso en su tema (components.CalcitaDownloadMenu.defaultProps) y encima lo que llega aquí\n const {\n formats, download, label, downloadLabel, disabled = false, confirmation, onDownloaded, onFailed, onCanceled, variant = 'button', ariaLabel,\n onChooseFormat, icon: ButtonIcon = FileDownloadOutlinedIcon,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common');\n const fileTypes = useFileTypes();\n // de dónde sale el menú de formatos: del botón, o (desde el «⋯» del teléfono, que se cierra) de donde estaba el renglón\n const [anchorElement, setAnchorElement] = useState(null);\n const [anchorPosition, setAnchorPosition] = useState(null);\n const placement = useBarActionsPlacement();\n const closeMoreMenu = useCloseBarActionsMenu();\n const closeFormats = () => {\n setAnchorElement(null);\n setAnchorPosition(null);\n };\n\n // no se espera a que termine: la descarga sigue en el panel y el diálogo de confirmación se cierra de inmediato\n const runDownload = (format) => {\n startDownload({\n label: downloadLabel ?? label ?? ariaLabel ?? t('actions.download'),\n format,\n request: (transferOptions) => download(format, transferOptions),\n onCompleted: () => onDownloaded?.(format),\n onFailed: (error) => onFailed?.(format, error),\n onCanceled: () => onCanceled?.(format),\n });\n };\n\n const confirmedDownload = useConfirmAction({run: ({subject}) => runDownload(subject.format)});\n\n const chooseFormat = (format) => {\n closeFormats();\n if (onChooseFormat) {\n onChooseFormat(format);\n return;\n }\n if (confirmation) {\n confirmedDownload.open({format});\n return;\n }\n // Regla de tablas: nada se descarga con un solo clic. Sin confirmation ni onChooseFormat es un error de quien\n // llama, no un camino válido: lo decimos en la consola y no descargamos\n console.error('DownloadMenu: falta confirmation u onChooseFormat; toda descarga pasa por un diálogo de confirmación');\n };\n\n const buttonLabel = label ?? t('actions.download');\n const openMenu = (event) => setAnchorElement(event.currentTarget);\n // desde el «⋯»: se cierra ese menú y los formatos salen donde estaba el renglón\n const openFromMoreMenu = (event) => {\n const {bottom, right} = event.currentTarget.getBoundingClientRect();\n closeMoreMenu();\n setAnchorPosition({top: bottom, left: right});\n };\n const isFormatsOpen = Boolean(anchorElement || anchorPosition);\n const confirmationTexts = confirmedDownload.subject ? confirmation?.(confirmedDownload.subject.format) : null;\n\n return (\n <>\n {placement === BAR_ACTIONS_PLACEMENT.menu && (\n <MenuItem disabled={disabled} aria-haspopup=\"menu\" onClick={openFromMoreMenu}>\n <ListItemIcon><ButtonIcon /></ListItemIcon>\n <ListItemText>{buttonLabel}</ListItemText>\n </MenuItem>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.capsule && (\n // en la cápsula, del tamaño y color de las demás acciones (BarActionButton)\n <Tooltip title={buttonLabel} describeChild>\n <span>\n <IconButton color=\"primary\" aria-label={buttonLabel} aria-haspopup=\"menu\" aria-expanded={isFormatsOpen}\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon />\n </IconButton>\n </span>\n </Tooltip>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.inline && (variant === 'icon' ? (\n // describeChild: el nombre accesible ya lo trae el botón; sin esto el Tooltip le pone el mismo aria-label al\n // span y un lector de pantalla (o una prueba) encuentra dos \"botones\" con el mismo nombre\n <Tooltip title={ariaLabel ?? buttonLabel} describeChild>\n {/* el span deja que el tooltip funcione con el botón inhabilitado */}\n <span>\n <IconButton size=\"small\" aria-label={ariaLabel ?? buttonLabel} aria-haspopup=\"menu\"\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <DownloadButton variant=\"outlined\" startIcon={<ButtonIcon />} endIcon={<ArrowDropDownIcon />}\n ownSx={{flexShrink: 0}} disabled={disabled} aria-haspopup=\"menu\" onClick={openMenu}>\n {buttonLabel}\n </DownloadButton>\n ))}\n <Menu open={isFormatsOpen} onClose={closeFormats}\n anchorReference={anchorPosition ? 'anchorPosition' : 'anchorEl'} anchorEl={anchorElement} anchorPosition={anchorPosition ?? undefined}\n anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} transformOrigin={{vertical: 'top', horizontal: 'right'}}>\n {formats.map((format) => {\n const fileType = fileTypes[format];\n const Icon = fileType?.Icon ?? FileDownloadOutlinedIcon;\n return (\n <MenuItem key={format} onClick={() => chooseFormat(format)}>\n <ListItemIcon><Icon fontSize=\"small\" /></ListItemIcon>\n {/* los de la casa con sus textos; los del proyecto con su label y hint (o esas llaves en sus texts) */}\n <ListItemText primary={t(`download.formats.${format}.label`, {defaultValue: fileType?.label ?? format.toUpperCase()})}\n secondary={t(`download.formats.${format}.hint`, {defaultValue: fileType?.hint ?? ''})}\n slotProps={{secondary: {variant: 'caption'}}} />\n </MenuItem>\n );\n })}\n </Menu>\n {confirmation && (\n <ConfirmDialog\n {...confirmedDownload.dialogProps}\n title={confirmationTexts?.title ?? ''}\n description={confirmationTexts?.description}\n confirmLabel={confirmationTexts?.confirmLabel ?? t('actions.download')}\n />\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DownloadMenu.js","sources":["../../../src/ui/actions/DownloadMenu.jsx"],"sourcesContent":["// \"Descargar ▾\" con los formatos de un reporte: PDF para leer o archivar, Excel o CSV para trabajar.\n//\n// Un solo botón y no tres: en la barra de una pantalla tres botones de descarga compiten con la acción principal.\n// Se acomoda a donde quedó, como BarActionButton: en una cápsula de la barra (las `actions` de PageHeading con cápsulas)\n// es solo el ícono con su nombre en el tooltip, como las demás acciones; antes salía la píldora «Descargar ▾» dentro de\n// la cápsula y se veía una cápsula dentro de otra. En el «⋯» del teléfono es un renglón que abre los formatos.\n// Quien llama pasa QUÉ descargar por formato (download(format, transferOptions) → {blob, fileName}, la forma de\n// un cliente HTTP que descarga). La descarga va al panel de descargas (startDownload, downloads): el diálogo se cierra al\n// confirmar y el avance, el aviso de \"descargado\" y el error se ven allá, mientras la persona sigue trabajando.\n//\n// Regla de tablas: una descarga pasa por un diálogo de confirmación que dice qué se va a bajar (p. ej. que la base\n// completa de clientes tarda unos minutos). Con `confirmation` el formato elegido NO se descarga de inmediato: abre\n// ConfirmDialog (máquina confirmActionMachine) y solo al confirmar se pide el archivo, una sola vez aunque haya doble clic.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaDownloadMenu con defaultProps y\n// styleOverrides de sus partes: button.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Button from '@mui/material/Button';\nimport IconButton from '@mui/material/IconButton';\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 Tooltip from '@mui/material/Tooltip';\nimport ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';\nimport FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';\n\nimport {BAR_ACTIONS_PLACEMENT, useBarActionsPlacement, useCloseBarActionsMenu} from '@/appShell/barActionsSlot.js';\nimport {startDownload} from '@/downloads/downloadQueue.js';\nimport {useFileTypes} from '@/downloads/fileTypes.js';\nimport ConfirmDialog from '@/feedback/ConfirmDialog.jsx';\nimport {useConfirmAction} from '@/feedback/useConfirmAction.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaDownloadMenu)\nconst THEME_NAME = 'CalcitaDownloadMenu';\nconst DownloadButton = piecePart(THEME_NAME, 'Button', Button);\n\n\n/**\n * @param {object} props\n * @param {string[]} props.formats los tipos de archivo, en orden: los de la casa (xlsx, csv, pdf) y los que el proyecto declare\n * en ToolkitProvider fileTypes (zip, xml…)\n * @param {(format: string, transferOptions: {signal: AbortSignal, onDownloadProgress: (progress: {loaded: number, total?: number|null}) => void}) => Promise<{blob: Blob, fileName: string}>} props.download\n * @param {string} [props.label] ya traducido; por defecto \"Descargar\"\n * @param {string} [props.downloadLabel] ya traducido; lo que se ve en el panel de descargas antes de saber el nombre\n * del archivo\n * @param {boolean} [props.disabled]\n * @param {(format: string) => {title: string, description: import('react').ReactNode, confirmLabel?: string}} [props.confirmation]\n * @param {(format: string) => void} [props.onDownloaded] p. ej. volver a pedir la tabla (si la descarga cambia algo en\n * el servidor)\n * @param {(format: string, error: unknown) => void} [props.onFailed] cuando la descarga no se pudo (el panel ya lo dice)\n * @param {(format: string) => void} [props.onCanceled] cuando la persona la canceló desde el panel\n * @param {'button'|'icon'} [props.variant] icon = solo el icono (columna de acciones de una tabla)\n * @param {string} [props.ariaLabel] ya traducido; obligatorio en la variante icon\n * @param {(format: string) => void} [props.onChooseFormat] quien llama se encarga de confirmar y descargar\n * @param {import('react').ElementType} [props.icon] ícono del botón; por defecto el de descarga. Distingue dos descargas en la\n * misma fila\n *\n * IMPORTANTE: hay que pasar `confirmation` u `onChooseFormat`. Sin ninguno de los dos el menú no descarga nada (regla de\n * tablas: toda descarga pasa por confirmación).\n *\n * OJO onChooseFormat: dentro de una celda del DataGrid el diálogo de confirmación NO puede vivir en la celda (el grid\n * virtualiza y vuelve a pintar sus celdas; el diálogo se perdía al elegir el formato). Ahí el menú solo avisa qué formato\n * se eligió y la pantalla abre SU confirmación (useConfirmAction) fuera de la tabla.\n */\nfunction DownloadMenuContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaDownloadMenu.defaultProps) y encima lo que llega aquí\n const {\n formats, download, label, downloadLabel, disabled = false, confirmation, onDownloaded, onFailed, onCanceled, variant = 'button', ariaLabel,\n onChooseFormat, icon: ButtonIcon = FileDownloadOutlinedIcon,\n ...unknownProps\n} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common');\n const fileTypes = useFileTypes();\n // de dónde sale el menú de formatos: del botón, o (desde el «⋯» del teléfono, que se cierra) de donde estaba el renglón\n const [anchorElement, setAnchorElement] = useState(null);\n const [anchorPosition, setAnchorPosition] = useState(null);\n const placement = useBarActionsPlacement();\n const closeMoreMenu = useCloseBarActionsMenu();\n const closeFormats = () => {\n setAnchorElement(null);\n setAnchorPosition(null);\n };\n\n // no se espera a que termine: la descarga sigue en el panel y el diálogo de confirmación se cierra de inmediato\n const runDownload = (format) => {\n startDownload({\n label: downloadLabel ?? label ?? ariaLabel ?? t('actions.download'),\n format,\n request: (transferOptions) => download(format, transferOptions),\n onCompleted: () => onDownloaded?.(format),\n onFailed: (error) => onFailed?.(format, error),\n onCanceled: () => onCanceled?.(format),\n });\n };\n\n const confirmedDownload = useConfirmAction({run: ({subject}) => runDownload(subject.format)});\n\n const chooseFormat = (format) => {\n closeFormats();\n if (onChooseFormat) {\n onChooseFormat(format);\n return;\n }\n if (confirmation) {\n confirmedDownload.open({format});\n return;\n }\n // Regla de tablas: nada se descarga con un solo clic. Sin confirmation ni onChooseFormat es un error de quien\n // llama, no un camino válido: lo decimos en la consola y no descargamos\n console.error('DownloadMenu: falta confirmation u onChooseFormat; toda descarga pasa por un diálogo de confirmación');\n };\n\n const buttonLabel = label ?? t('actions.download');\n const openMenu = (event) => setAnchorElement(event.currentTarget);\n // desde el «⋯»: se cierra ese menú y los formatos salen donde estaba el renglón\n const openFromMoreMenu = (event) => {\n const {bottom, right} = event.currentTarget.getBoundingClientRect();\n closeMoreMenu();\n setAnchorPosition({top: bottom, left: right});\n };\n const isFormatsOpen = Boolean(anchorElement || anchorPosition);\n const confirmationTexts = confirmedDownload.subject ? confirmation?.(confirmedDownload.subject.format) : null;\n\n return (\n <>\n {placement === BAR_ACTIONS_PLACEMENT.menu && (\n <MenuItem disabled={disabled} aria-haspopup=\"menu\" onClick={openFromMoreMenu}>\n <ListItemIcon><ButtonIcon /></ListItemIcon>\n <ListItemText>{buttonLabel}</ListItemText>\n </MenuItem>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.capsule && (\n // en la cápsula, del tamaño y color de las demás acciones (BarActionButton)\n <Tooltip title={buttonLabel} describeChild>\n <span>\n <IconButton color=\"primary\" aria-label={buttonLabel} aria-haspopup=\"menu\" aria-expanded={isFormatsOpen}\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon />\n </IconButton>\n </span>\n </Tooltip>\n )}\n {placement === BAR_ACTIONS_PLACEMENT.inline && (variant === 'icon' ? (\n // describeChild: el nombre accesible ya lo trae el botón; sin esto el Tooltip le pone el mismo aria-label al\n // span y un lector de pantalla (o una prueba) encuentra dos \"botones\" con el mismo nombre\n <Tooltip title={ariaLabel ?? buttonLabel} describeChild>\n {/* el span deja que el tooltip funcione con el botón inhabilitado */}\n <span>\n <IconButton size=\"small\" aria-label={ariaLabel ?? buttonLabel} aria-haspopup=\"menu\"\n disabled={disabled} onClick={openMenu}>\n <ButtonIcon fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <DownloadButton variant=\"outlined\" startIcon={<ButtonIcon />} endIcon={<ArrowDropDownIcon />}\n ownSx={{flexShrink: 0}} disabled={disabled} aria-haspopup=\"menu\" onClick={openMenu}>\n {buttonLabel}\n </DownloadButton>\n ))}\n <Menu open={isFormatsOpen} onClose={closeFormats}\n anchorReference={anchorPosition ? 'anchorPosition' : 'anchorEl'} anchorEl={anchorElement} anchorPosition={anchorPosition ?? undefined}\n anchorOrigin={{vertical: 'bottom', horizontal: 'right'}} transformOrigin={{vertical: 'top', horizontal: 'right'}}>\n {formats.map((format) => {\n const fileType = fileTypes[format];\n const Icon = fileType?.Icon ?? FileDownloadOutlinedIcon;\n return (\n <MenuItem key={format} onClick={() => chooseFormat(format)}>\n <ListItemIcon><Icon fontSize=\"small\" /></ListItemIcon>\n {/* los de la casa con sus textos; los del proyecto con su label y hint (o esas llaves en sus texts) */}\n <ListItemText primary={t(`download.formats.${format}.label`, {defaultValue: fileType?.label ?? format.toUpperCase()})}\n secondary={t(`download.formats.${format}.hint`, {defaultValue: fileType?.hint ?? ''})}\n slotProps={{secondary: {variant: 'caption'}}} />\n </MenuItem>\n );\n })}\n </Menu>\n {confirmation && (\n <ConfirmDialog\n {...confirmedDownload.dialogProps}\n title={confirmationTexts?.title ?? ''}\n description={confirmationTexts?.description}\n confirmLabel={confirmationTexts?.confirmLabel ?? t('actions.download')}\n />\n )}\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst DownloadMenu = withPieceSlots(DownloadMenuContent, THEME_NAME);\nexport default DownloadMenu;\n"],"names":[],"mappings":"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"}
|
|
@@ -12,10 +12,14 @@ import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined
|
|
|
12
12
|
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
|
13
13
|
import FieldFrame from "../../inputs/FieldFrame.js";
|
|
14
14
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
15
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
16
|
+
const THEME_NAME = "CalcitaSecretField";
|
|
17
|
+
const SecretFieldField = piecePart(THEME_NAME, "Field", TextField);
|
|
15
18
|
const COPIED_FEEDBACK_MILLISECONDS = 2e3;
|
|
16
19
|
const MONOSPACE_FONT = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
17
|
-
function
|
|
18
|
-
|
|
20
|
+
function SecretFieldContent(props) {
|
|
21
|
+
const { value, label, helperText = " ", onCopied, onRevealChange, texts, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
22
|
+
warnUnknownProps(THEME_NAME, unknownProps);
|
|
19
23
|
const { t } = useToolkitTranslation("common", texts);
|
|
20
24
|
const generatedId = useId();
|
|
21
25
|
const inputId = `secret-${generatedId}`;
|
|
@@ -37,7 +41,7 @@ function SecretField({ value, label, helperText = " ", onCopied, onRevealChange,
|
|
|
37
41
|
}
|
|
38
42
|
};
|
|
39
43
|
return /* @__PURE__ */ jsx(FieldFrame, { inputId, label, children: /* @__PURE__ */ jsx(
|
|
40
|
-
|
|
44
|
+
SecretFieldField,
|
|
41
45
|
{
|
|
42
46
|
id: inputId,
|
|
43
47
|
fullWidth: true,
|
|
@@ -57,6 +61,7 @@ function SecretField({ value, label, helperText = " ", onCopied, onRevealChange,
|
|
|
57
61
|
}
|
|
58
62
|
) });
|
|
59
63
|
}
|
|
64
|
+
const SecretField = withPieceSlots(SecretFieldContent, THEME_NAME);
|
|
60
65
|
export {
|
|
61
66
|
SecretField as default
|
|
62
67
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecretField.js","sources":["../../../src/ui/actions/SecretField.jsx"],"sourcesContent":["// Un secreto que se enseña UNA vez (una clave de acceso, un token recién creado): tapado por omisión, con el ojo para verlo\n// y el botón para copiarlo.\n//\n// Tapado porque la pantalla se comparte, se graba o la ve quien pasa por atrás; el botón de copiar evita seleccionarlo a\n// mano y dejar un carácter afuera. Si el navegador no deja copiar, se destapa para copiarlo a mano.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport IconButton from '@mui/material/IconButton';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';\nimport VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';\n\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\nconst COPIED_FEEDBACK_MILLISECONDS = 2000;\nconst MONOSPACE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * @param {object} props\n * @param {string} props.value el secreto\n * @param {string} props.label ya traducido\n * @param {string} [props.helperText] p. ej. «Guárdela: no se vuelve a enseñar»\n * @param {() => void} [props.onCopied]\n * @param {(isRevealed: boolean) => void} [props.onRevealChange] se destapó o se tapó (para registrar quién vio el secreto)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\
|
|
1
|
+
{"version":3,"file":"SecretField.js","sources":["../../../src/ui/actions/SecretField.jsx"],"sourcesContent":["// Un secreto que se enseña UNA vez (una clave de acceso, un token recién creado): tapado por omisión, con el ojo para verlo\n// y el botón para copiarlo.\n//\n// Tapado porque la pantalla se comparte, se graba o la ve quien pasa por atrás; el botón de copiar evita seleccionarlo a\n// mano y dejar un carácter afuera. Si el navegador no deja copiar, se destapa para copiarlo a mano.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSecretField con defaultProps, styleOverrides de\n// sus partes (field) y slots/slotProps para cambiarlas.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport IconButton from '@mui/material/IconButton';\nimport InputAdornment from '@mui/material/InputAdornment';\nimport TextField from '@mui/material/TextField';\nimport Tooltip from '@mui/material/Tooltip';\nimport CheckIcon from '@mui/icons-material/CheckRounded';\nimport CopyIcon from '@mui/icons-material/ContentCopyOutlined';\nimport VisibilityOffOutlinedIcon from '@mui/icons-material/VisibilityOffOutlined';\nimport VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';\n\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSecretField)\nconst THEME_NAME = 'CalcitaSecretField';\nconst SecretFieldField = piecePart(THEME_NAME, 'Field', TextField);\n\nconst COPIED_FEEDBACK_MILLISECONDS = 2000;\nconst MONOSPACE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * @param {object} props\n * @param {string} props.value el secreto\n * @param {string} props.label ya traducido\n * @param {string} [props.helperText] p. ej. «Guárdela: no se vuelve a enseñar»\n * @param {() => void} [props.onCopied]\n * @param {(isRevealed: boolean) => void} [props.onRevealChange] se destapó o se tapó (para registrar quién vio el secreto)\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction SecretFieldContent(props) {\n const {value, label, helperText = ' ', onCopied, onRevealChange, texts, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `secret-${generatedId}`;\n const [isRevealed, setIsRevealedState] = useState(false);\n const setIsRevealed = (nextIsRevealed) => {\n setIsRevealedState(nextIsRevealed);\n onRevealChange?.(nextIsRevealed);\n };\n const [isCopied, setIsCopied] = useState(false);\n const revealLabel = isRevealed ? t('form.hidePassword') : t('form.showPassword');\n\n const copy = async () => {\n try {\n await navigator.clipboard.writeText(value);\n setIsCopied(true);\n onCopied?.();\n setTimeout(() => setIsCopied(false), COPIED_FEEDBACK_MILLISECONDS);\n } catch {\n // sin permiso de portapapeles: se destapa para copiarlo a mano\n setIsRevealed(true);\n }\n };\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <SecretFieldField\n id={inputId}\n fullWidth\n size=\"small\"\n value={value}\n helperText={helperText}\n type={isRevealed ? 'text' : 'password'}\n slotProps={{\n htmlInput: {readOnly: true, spellCheck: false, autoComplete: 'off', style: {fontFamily: MONOSPACE_FONT}},\n input: {\n endAdornment: (\n <InputAdornment position=\"end\">\n <Tooltip title={revealLabel}>\n <IconButton size=\"small\" aria-label={revealLabel} onClick={() => setIsRevealed(!isRevealed)}>\n {isRevealed ? <VisibilityOffOutlinedIcon fontSize=\"small\" /> : <VisibilityOutlinedIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n <Tooltip title={isCopied ? t('form.copied') : t('form.copy')}>\n <IconButton size=\"small\" aria-label={t('form.copy')} onClick={copy} color={isCopied ? 'success' : 'default'}>\n {isCopied ? <CheckIcon fontSize=\"small\" /> : <CopyIcon fontSize=\"small\" />}\n </IconButton>\n </Tooltip>\n </InputAdornment>\n ),\n },\n }}\n />\n </FieldFrame>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SecretField = withPieceSlots(SecretFieldContent, THEME_NAME);\nexport default SecretField;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;AA0BA,MAAM,aAAa;AACnB,MAAM,mBAAmB,UAAU,YAAY,SAAS,SAAS;AAEjE,MAAM,+BAA+B;AACrC,MAAM,iBAAiB;AAWvB,SAAS,mBAAmB,OAAO;AAC/B,QAAM,EAAC,OAAO,OAAO,aAAa,KAAK,UAAU,gBAAgB,OAAO,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAC1H,mBAAiB,YAAY,YAAY;AACzC,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,UAAU,WAAW;AACrC,QAAM,CAAC,YAAY,kBAAkB,IAAI,SAAS,KAAK;AACvD,QAAM,gBAAgB,CAAC,mBAAmB;AACtC,uBAAmB,cAAc;AACjC,qBAAiB,cAAc;AAAA,EACnC;AACA,QAAM,CAAC,UAAU,WAAW,IAAI,SAAS,KAAK;AAC9C,QAAM,cAAc,aAAa,EAAE,mBAAmB,IAAI,EAAE,mBAAmB;AAE/E,QAAM,OAAO,YAAY;AACrB,QAAI;AACA,YAAM,UAAU,UAAU,UAAU,KAAK;AACzC,kBAAY,IAAI;AAChB,iBAAA;AACA,iBAAW,MAAM,YAAY,KAAK,GAAG,4BAA4B;AAAA,IACrE,QAAQ;AAEJ,oBAAc,IAAI;AAAA,IACtB;AAAA,EACJ;AAEA,SACI,oBAAC,YAAA,EAAW,SAAkB,OAC1B,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,IAAI;AAAA,MACJ,WAAS;AAAA,MACT,MAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA,MAAM,aAAa,SAAS;AAAA,MAC5B,WAAW;AAAA,QACP,WAAW,EAAC,UAAU,MAAM,YAAY,OAAO,cAAc,OAAO,OAAO,EAAC,YAAY,eAAA,EAAc;AAAA,QACtG,OAAO;AAAA,UACH,cACI,qBAAC,gBAAA,EAAe,UAAS,OACrB,UAAA;AAAA,YAAA,oBAAC,SAAA,EAAQ,OAAO,aACZ,UAAA,oBAAC,YAAA,EAAW,MAAK,SAAQ,cAAY,aAAa,SAAS,MAAM,cAAc,CAAC,UAAU,GACrF,UAAA,aAAa,oBAAC,2BAAA,EAA0B,UAAS,QAAA,CAAQ,IAAK,oBAAC,wBAAA,EAAuB,UAAS,QAAA,CAAQ,EAAA,CAC5G,GACJ;AAAA,YACA,oBAAC,SAAA,EAAQ,OAAO,WAAW,EAAE,aAAa,IAAI,EAAE,WAAW,GACvD,UAAA,oBAAC,YAAA,EAAW,MAAK,SAAQ,cAAY,EAAE,WAAW,GAAG,SAAS,MAAM,OAAO,WAAW,YAAY,WAC7F,qBAAW,oBAAC,WAAA,EAAU,UAAS,QAAA,CAAQ,IAAK,oBAAC,UAAA,EAAS,UAAS,QAAA,CAAQ,GAC5E,EAAA,CACJ;AAAA,UAAA,EAAA,CACJ;AAAA,QAAA;AAAA,MAER;AAAA,IACJ;AAAA,EAAA,GAER;AAER;AAGA,MAAM,cAAc,eAAe,oBAAoB,UAAU;"}
|
|
@@ -18,7 +18,7 @@ import { usesMobileTabBar } from "../../appShell/tabBarRule.js";
|
|
|
18
18
|
import { minimalFloatingStylesOf } from "../../theme/components/minimalComponents.js";
|
|
19
19
|
import { isGlassTheme, glassStylesOf } from "../../theme/glassSurface.js";
|
|
20
20
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
21
|
-
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
21
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
22
22
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
23
23
|
const THEME_NAME = "CalcitaSelectionActions";
|
|
24
24
|
const SelectionBar = piecePart(THEME_NAME, "Bar", Stack);
|
|
@@ -49,7 +49,7 @@ function bottomCapsuleSxOf(theme) {
|
|
|
49
49
|
...aboveMobileTabBarOf(FLOATING_GAP_PIXELS)
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
function
|
|
52
|
+
function SelectionActionsContent(props) {
|
|
53
53
|
const { countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx, ...unknownProps } = usePieceProps(props, THEME_NAME);
|
|
54
54
|
warnUnknownProps(THEME_NAME, unknownProps);
|
|
55
55
|
const theme = useTheme();
|
|
@@ -144,6 +144,7 @@ function SelectionActions(props) {
|
|
|
144
144
|
] })
|
|
145
145
|
] });
|
|
146
146
|
}
|
|
147
|
+
const SelectionActions = withPieceSlots(SelectionActionsContent, THEME_NAME);
|
|
147
148
|
export {
|
|
148
149
|
SELECTION_PHONE_LAYOUT,
|
|
149
150
|
SelectionActions as default,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionActions.js","sources":["../../../src/ui/actions/SelectionActions.jsx"],"sourcesContent":["// Las acciones de lo elegido en una lista («5 pedidos elegidos · Cancelar · Borrar · Quitar selección»), en el lugar que le\n// toca según el equipo. Una sola pieza para que cada lista con casillas (la tabla de servidor, una propia) no repita dónde\n// va cada cosa:\n// - escritorio, y el teléfono sin barra de pestañas abajo (el tema de MUI sin vidrio): un renglón donde se pone la pieza,\n// cada acción como botón con su texto;\n// - teléfono con vidrio, o minimalista con cápsulas (usesMobileTabBar), según phoneLayout (SELECTION_PHONE_LAYOUT):\n// top (por omisión): arriba, en la barra flotante, «5 elegidos ✕» y las acciones como íconos en su propia cápsula,\n// como Apple separa los grupos; las que no caben, al «⋯» (BarActions);\n// en los dos, mientras hay algo elegido, el renglón de arriba queda solo para lo elegido (BarActions exclusive): las\n// demás cápsulas de la pantalla y la lupa se esconden, como el modo de edición del iPhone;\n// split: como iOS en modo de edición (Fotos, Mail, Archivos): arriba «5 elegidos ✕» y abajo, encima de las pestañas,\n// una cápsula fija con las acciones, donde llega el pulgar. Mientras se ve, pide su alto al marco\n// (FLOATING_BOTTOM_SPACE_CSS_VARIABLE) para no tapar el final de la página.\n// Lo pidió el autor: en el teléfono, configurable, y por omisión todo arriba.\n//\n// Con vidrio, la cápsula de arriba deja sus botones del color del texto (BarCapsule: la regla de la barra); abajo, una acción\n// destructiva (color: 'error') conserva su rojo, como pide la guía de Apple para lo que borra.\n//\n// Cada acción puede regresar una promesa: mientras corre, las demás se apagan (nadie cancela dos veces lo mismo).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSelectionActions con defaultProps y\n// styleOverrides de sus partes: bar, count, clear, phoneCount, phoneActions.\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 GlobalStyles from '@mui/material/GlobalStyles';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport BarActionButton from '@/appShell/BarActionButton.jsx';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, GLASS_PANEL_GAP_PIXELS, PHONE_MEDIA_QUERY, aboveMobileTabBarOf,\n} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSelectionActions)\nconst THEME_NAME = 'CalcitaSelectionActions';\nconst SelectionBar = piecePart(THEME_NAME, 'Bar', Stack);\nconst SelectionCount = piecePart(THEME_NAME, 'Count', Typography);\nconst SelectionClear = piecePart(THEME_NAME, 'Clear', Button);\nconst SelectionPhoneCount = piecePart(THEME_NAME, 'PhoneCount', Typography);\nconst SelectionPhoneActions = piecePart(THEME_NAME, 'PhoneActions', Box);\n\n/** Dónde van las acciones de lo elegido en el teléfono con barra de pestañas abajo: todo arriba o divididas (iOS). */\nexport const SELECTION_PHONE_LAYOUT = Object.freeze({top: 'top', split: 'split'});\n\n// lo que se separa la cápsula de abajo de la barra de pestañas (px), lo mismo que el aviso de versión nueva\nconst FLOATING_GAP_PIXELS = 8;\n// lo que pide al marco mientras flota abajo: su alto (48) y su separación, con aire\nconst FLOATING_BOTTOM_SPACE_PIXELS = 64;\n// lo que solo se ve en el teléfono\nconst PHONE_ONLY_SX = {display: 'none', [PHONE_MEDIA_QUERY]: {display: 'flex'}};\n\n/**\n * ¿En el teléfono lo elegido sale de su renglón (arriba o abajo)? La misma regla que la barra de pestañas de abajo: con ella,\n * el renglón de la pantalla se queda con lo suyo (el conteo de la tabla) y las acciones flotan.\n *\n * @param {object} theme\n * @returns {boolean}\n */\nexport function selectionFloatsOnPhone(theme) {\n return usesMobileTabBar(theme);\n}\n\n/**\n * La cápsula fija abajo, centrada en la ventana, encima de la barra de pestañas.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction bottomCapsuleSxOf(theme) {\n return {\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n alignItems: 'center',\n gap: 0.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n p: 0.5,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n };\n}\n\n/**\n * @param {object} props\n * @param {string} props.countText «5 pedidos elegidos», ya traducido. La pieza se pinta mientras la pantalla la ponga: con\n * nada elegido, no la ponga\n * @param {Array<{key: string, label: string, icon?: import('react').ReactNode, color?: 'inherit'|'primary'|'secondary'|'error'|'info'|'success'|'warning', disabled?: boolean, onClick: () => unknown}>} [props.actions]\n * label ya traducido; en el teléfono se ve el ícono (sin él, el texto). onClick puede regresar una promesa\n * @param {() => void} props.onClear quitar la selección\n * @param {'top'|'split'} [props.phoneLayout] SELECTION_PHONE_LAYOUT; por omisión todo arriba\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {selection: {clear, actionsLabel, summaryLabel}}\n * @param {object} [props.sx] estilos del renglón\n */\nexport default function SelectionActions(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSelectionActions.defaultProps) y encima lo que llega aquí\n const {countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const theme = useTheme();\n const {t} = useToolkitTranslation('common', texts);\n const [runningActionKey, setRunningActionKey] = useState(null);\n const floatsOnPhone = selectionFloatsOnPhone(theme);\n const extraSx = Array.isArray(sx) ? sx : [sx];\n\n const runAction = async (action) => {\n setRunningActionKey(action.key);\n try {\n await action.onClick();\n } finally {\n setRunningActionKey(null);\n }\n };\n const isDisabledOf = (action) => runningActionKey !== null || Boolean(action.disabled);\n\n const clearButton = (\n <Tooltip title={t('selection.clear')}>\n <IconButton aria-label={t('selection.clear')} onClick={onClear}>\n <CloseRoundedIcon />\n </IconButton>\n </Tooltip>\n );\n\n // el renglón: escritorio siempre; en el teléfono, solo si ahí no flota\n const row = (\n <SelectionBar role=\"toolbar\" aria-label={t('selection.actionsLabel')} direction=\"row\" spacing={1} useFlexGap\n ownSx={[{flexGrow: 1, alignItems: 'center', flexWrap: 'wrap', minHeight: 32},\n floatsOnPhone && {[PHONE_MEDIA_QUERY]: {display: 'none'}}, ...extraSx]}>\n <SelectionCount variant=\"body2\" ownSx={{fontWeight: 600, mr: 1}}>{countText}</SelectionCount>\n {actions.map((action) => (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} startIcon={action.icon}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)}>\n {action.label}\n </Button>\n ))}\n <SelectionClear size=\"small\" color=\"inherit\" startIcon={<CloseRoundedIcon />} onClick={onClear} ownSx={{ml: 'auto'}}>\n {t('selection.clear')}\n </SelectionClear>\n </SelectionBar>\n );\n\n if (!floatsOnPhone) {\n return row;\n }\n\n const showsBottomCapsule = phoneLayout === SELECTION_PHONE_LAYOUT.split && actions.length > 0;\n\n return (\n <>\n {row}\n {/* arriba: con las demás cápsulas flotantes de la pantalla (BarActions), antes que ellas. Sin label en BarActions:\n el nombre ya lo lleva la cápsula (dos grupos anidados con el mismo nombre se leen dos veces) */}\n <BarActions bare exclusive order={-2} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n <BarCapsule aria-label={t('selection.summaryLabel')} sx={{pl: 2}}>\n <SelectionPhoneCount variant=\"body2\" role=\"status\" aria-live=\"polite\" ownSx={{fontWeight: 600, whiteSpace: 'nowrap', mr: 0.5}}>\n {countText}\n </SelectionPhoneCount>\n {clearButton}\n </BarCapsule>\n </BarActions>\n {phoneLayout === SELECTION_PHONE_LAYOUT.top && actions.length > 0 && (\n <BarActions label={t('selection.actionsLabel')} exclusive order={-1} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n {actions.map((action) => (\n <BarActionButton key={action.key} label={action.label} icon={action.icon} color={action.color}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)} />\n ))}\n </BarActions>\n )}\n {showsBottomCapsule && (\n <>\n <GlobalStyles styles={{':root': {[FLOATING_BOTTOM_SPACE_CSS_VARIABLE]: `${FLOATING_BOTTOM_SPACE_PIXELS}px`}}} />\n <SelectionPhoneActions role=\"toolbar\" aria-label={t('selection.actionsLabel')} ownSx={[bottomCapsuleSxOf, PHONE_ONLY_SX]}>\n {actions.map((action) => (action.icon ? (\n <Tooltip key={action.key} title={action.label}>\n {/* el span deja ver el tooltip aunque el botón esté apagado */}\n <span>\n <IconButton aria-label={action.label} color={action.color ?? 'default'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.icon}\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.label}\n </Button>\n )))}\n </SelectionPhoneActions>\n </>\n )}\n </>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SelectionActions.js","sources":["../../../src/ui/actions/SelectionActions.jsx"],"sourcesContent":["// Las acciones de lo elegido en una lista («5 pedidos elegidos · Cancelar · Borrar · Quitar selección»), en el lugar que le\n// toca según el equipo. Una sola pieza para que cada lista con casillas (la tabla de servidor, una propia) no repita dónde\n// va cada cosa:\n// - escritorio, y el teléfono sin barra de pestañas abajo (el tema de MUI sin vidrio): un renglón donde se pone la pieza,\n// cada acción como botón con su texto;\n// - teléfono con vidrio, o minimalista con cápsulas (usesMobileTabBar), según phoneLayout (SELECTION_PHONE_LAYOUT):\n// top (por omisión): arriba, en la barra flotante, «5 elegidos ✕» y las acciones como íconos en su propia cápsula,\n// como Apple separa los grupos; las que no caben, al «⋯» (BarActions);\n// en los dos, mientras hay algo elegido, el renglón de arriba queda solo para lo elegido (BarActions exclusive): las\n// demás cápsulas de la pantalla y la lupa se esconden, como el modo de edición del iPhone;\n// split: como iOS en modo de edición (Fotos, Mail, Archivos): arriba «5 elegidos ✕» y abajo, encima de las pestañas,\n// una cápsula fija con las acciones, donde llega el pulgar. Mientras se ve, pide su alto al marco\n// (FLOATING_BOTTOM_SPACE_CSS_VARIABLE) para no tapar el final de la página.\n// Lo pidió el autor: en el teléfono, configurable, y por omisión todo arriba.\n//\n// Con vidrio, la cápsula de arriba deja sus botones del color del texto (BarCapsule: la regla de la barra); abajo, una acción\n// destructiva (color: 'error') conserva su rojo, como pide la guía de Apple para lo que borra.\n//\n// Cada acción puede regresar una promesa: mientras corre, las demás se apagan (nadie cancela dos veces lo mismo).\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaSelectionActions con defaultProps y\n// styleOverrides de sus partes: bar, count, clear, phoneCount, phoneActions.\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 GlobalStyles from '@mui/material/GlobalStyles';\nimport IconButton from '@mui/material/IconButton';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport {useTheme} from '@mui/material/styles';\nimport CloseRoundedIcon from '@mui/icons-material/CloseRounded';\n\nimport BarActionButton from '@/appShell/BarActionButton.jsx';\nimport BarActions from '@/appShell/BarActions.jsx';\nimport BarCapsule from '@/appShell/BarCapsule.jsx';\nimport {\n FLOATING_BOTTOM_SPACE_CSS_VARIABLE, GLASS_PANEL_GAP_PIXELS, PHONE_MEDIA_QUERY, aboveMobileTabBarOf,\n} from '@/appShell/shellSizes.js';\nimport {usesMobileTabBar} from '@/appShell/tabBarRule.js';\nimport {minimalFloatingStylesOf} from '@/theme/components/minimalComponents.js';\nimport {glassStylesOf, isGlassTheme} from '@/theme/glassSurface.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaSelectionActions)\nconst THEME_NAME = 'CalcitaSelectionActions';\nconst SelectionBar = piecePart(THEME_NAME, 'Bar', Stack);\nconst SelectionCount = piecePart(THEME_NAME, 'Count', Typography);\nconst SelectionClear = piecePart(THEME_NAME, 'Clear', Button);\nconst SelectionPhoneCount = piecePart(THEME_NAME, 'PhoneCount', Typography);\nconst SelectionPhoneActions = piecePart(THEME_NAME, 'PhoneActions', Box);\n\n/** Dónde van las acciones de lo elegido en el teléfono con barra de pestañas abajo: todo arriba o divididas (iOS). */\nexport const SELECTION_PHONE_LAYOUT = Object.freeze({top: 'top', split: 'split'});\n\n// lo que se separa la cápsula de abajo de la barra de pestañas (px), lo mismo que el aviso de versión nueva\nconst FLOATING_GAP_PIXELS = 8;\n// lo que pide al marco mientras flota abajo: su alto (48) y su separación, con aire\nconst FLOATING_BOTTOM_SPACE_PIXELS = 64;\n// lo que solo se ve en el teléfono\nconst PHONE_ONLY_SX = {display: 'none', [PHONE_MEDIA_QUERY]: {display: 'flex'}};\n\n/**\n * ¿En el teléfono lo elegido sale de su renglón (arriba o abajo)? La misma regla que la barra de pestañas de abajo: con ella,\n * el renglón de la pantalla se queda con lo suyo (el conteo de la tabla) y las acciones flotan.\n *\n * @param {object} theme\n * @returns {boolean}\n */\nexport function selectionFloatsOnPhone(theme) {\n return usesMobileTabBar(theme);\n}\n\n/**\n * La cápsula fija abajo, centrada en la ventana, encima de la barra de pestañas.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction bottomCapsuleSxOf(theme) {\n return {\n ...(isGlassTheme(theme) ? glassStylesOf(theme) : minimalFloatingStylesOf(theme)),\n position: 'fixed',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: theme.zIndex.appBar,\n alignItems: 'center',\n gap: 0.5,\n width: 'max-content',\n maxWidth: `calc(100% - ${GLASS_PANEL_GAP_PIXELS * 2}px)`,\n borderRadius: '999px',\n p: 0.5,\n ...aboveMobileTabBarOf(FLOATING_GAP_PIXELS),\n };\n}\n\n/**\n * @param {object} props\n * @param {string} props.countText «5 pedidos elegidos», ya traducido. La pieza se pinta mientras la pantalla la ponga: con\n * nada elegido, no la ponga\n * @param {Array<{key: string, label: string, icon?: import('react').ReactNode, color?: 'inherit'|'primary'|'secondary'|'error'|'info'|'success'|'warning', disabled?: boolean, onClick: () => unknown}>} [props.actions]\n * label ya traducido; en el teléfono se ve el ícono (sin él, el texto). onClick puede regresar una promesa\n * @param {() => void} props.onClear quitar la selección\n * @param {'top'|'split'} [props.phoneLayout] SELECTION_PHONE_LAYOUT; por omisión todo arriba\n * @param {object} [props.texts] los textos que cambian, con la forma de `common`: {selection: {clear, actionsLabel, summaryLabel}}\n * @param {object} [props.sx] estilos del renglón\n */\nfunction SelectionActionsContent(props) {\n // lo que el proyecto puso en su tema (components.CalcitaSelectionActions.defaultProps) y encima lo que llega aquí\n const {countText, actions = [], onClear, phoneLayout = SELECTION_PHONE_LAYOUT.top, texts, sx, ...unknownProps} = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const theme = useTheme();\n const {t} = useToolkitTranslation('common', texts);\n const [runningActionKey, setRunningActionKey] = useState(null);\n const floatsOnPhone = selectionFloatsOnPhone(theme);\n const extraSx = Array.isArray(sx) ? sx : [sx];\n\n const runAction = async (action) => {\n setRunningActionKey(action.key);\n try {\n await action.onClick();\n } finally {\n setRunningActionKey(null);\n }\n };\n const isDisabledOf = (action) => runningActionKey !== null || Boolean(action.disabled);\n\n const clearButton = (\n <Tooltip title={t('selection.clear')}>\n <IconButton aria-label={t('selection.clear')} onClick={onClear}>\n <CloseRoundedIcon />\n </IconButton>\n </Tooltip>\n );\n\n // el renglón: escritorio siempre; en el teléfono, solo si ahí no flota\n const row = (\n <SelectionBar role=\"toolbar\" aria-label={t('selection.actionsLabel')} direction=\"row\" spacing={1} useFlexGap\n ownSx={[{flexGrow: 1, alignItems: 'center', flexWrap: 'wrap', minHeight: 32},\n floatsOnPhone && {[PHONE_MEDIA_QUERY]: {display: 'none'}}, ...extraSx]}>\n <SelectionCount variant=\"body2\" ownSx={{fontWeight: 600, mr: 1}}>{countText}</SelectionCount>\n {actions.map((action) => (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} startIcon={action.icon}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)}>\n {action.label}\n </Button>\n ))}\n <SelectionClear size=\"small\" color=\"inherit\" startIcon={<CloseRoundedIcon />} onClick={onClear} ownSx={{ml: 'auto'}}>\n {t('selection.clear')}\n </SelectionClear>\n </SelectionBar>\n );\n\n if (!floatsOnPhone) {\n return row;\n }\n\n const showsBottomCapsule = phoneLayout === SELECTION_PHONE_LAYOUT.split && actions.length > 0;\n\n return (\n <>\n {row}\n {/* arriba: con las demás cápsulas flotantes de la pantalla (BarActions), antes que ellas. Sin label en BarActions:\n el nombre ya lo lleva la cápsula (dos grupos anidados con el mismo nombre se leen dos veces) */}\n <BarActions bare exclusive order={-2} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n <BarCapsule aria-label={t('selection.summaryLabel')} sx={{pl: 2}}>\n <SelectionPhoneCount variant=\"body2\" role=\"status\" aria-live=\"polite\" ownSx={{fontWeight: 600, whiteSpace: 'nowrap', mr: 0.5}}>\n {countText}\n </SelectionPhoneCount>\n {clearButton}\n </BarCapsule>\n </BarActions>\n {phoneLayout === SELECTION_PHONE_LAYOUT.top && actions.length > 0 && (\n <BarActions label={t('selection.actionsLabel')} exclusive order={-1} sx={PHONE_ONLY_SX} inlineSx={PHONE_ONLY_SX}>\n {actions.map((action) => (\n <BarActionButton key={action.key} label={action.label} icon={action.icon} color={action.color}\n disabled={isDisabledOf(action)} onClick={() => runAction(action)} />\n ))}\n </BarActions>\n )}\n {showsBottomCapsule && (\n <>\n <GlobalStyles styles={{':root': {[FLOATING_BOTTOM_SPACE_CSS_VARIABLE]: `${FLOATING_BOTTOM_SPACE_PIXELS}px`}}} />\n <SelectionPhoneActions role=\"toolbar\" aria-label={t('selection.actionsLabel')} ownSx={[bottomCapsuleSxOf, PHONE_ONLY_SX]}>\n {actions.map((action) => (action.icon ? (\n <Tooltip key={action.key} title={action.label}>\n {/* el span deja ver el tooltip aunque el botón esté apagado */}\n <span>\n <IconButton aria-label={action.label} color={action.color ?? 'default'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.icon}\n </IconButton>\n </span>\n </Tooltip>\n ) : (\n <Button key={action.key} size=\"small\" color={action.color ?? 'primary'} disabled={isDisabledOf(action)}\n onClick={() => runAction(action)}>\n {action.label}\n </Button>\n )))}\n </SelectionPhoneActions>\n </>\n )}\n </>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst SelectionActions = withPieceSlots(SelectionActionsContent, THEME_NAME);\nexport default SelectionActions;\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,OAAO,KAAK;AACvD,MAAM,iBAAiB,UAAU,YAAY,SAAS,UAAU;AAChE,MAAM,iBAAiB,UAAU,YAAY,SAAS,MAAM;AAC5D,MAAM,sBAAsB,UAAU,YAAY,cAAc,UAAU;AAC1E,MAAM,wBAAwB,UAAU,YAAY,gBAAgB,GAAG;AAGhE,MAAM,yBAAyB,OAAO,OAAO,EAAC,KAAK,OAAO,OAAO,SAAQ;AAGhF,MAAM,sBAAsB;AAE5B,MAAM,+BAA+B;AAErC,MAAM,gBAAgB,EAAC,SAAS,QAAQ,CAAC,iBAAiB,GAAG,EAAC,SAAS,SAAM;AAStE,SAAS,uBAAuB,OAAO;AAC1C,SAAO,iBAAiB,KAAK;AACjC;AAQA,SAAS,kBAAkB,OAAO;AAC9B,SAAO;AAAA,IACH,GAAI,aAAa,KAAK,IAAI,cAAc,KAAK,IAAI,wBAAwB,KAAK;AAAA,IAC9E,UAAU;AAAA,IACV,MAAM;AAAA,IACN,WAAW;AAAA,IACX,QAAQ,MAAM,OAAO;AAAA,IACrB,YAAY;AAAA,IACZ,KAAK;AAAA,IACL,OAAO;AAAA,IACP,UAAU,eAAe,yBAAyB,CAAC;AAAA,IACnD,cAAc;AAAA,IACd,GAAG;AAAA,IACH,GAAG,oBAAoB,mBAAmB;AAAA,EAAA;AAElD;AAaA,SAAS,wBAAwB,OAAO;AAEpC,QAAM,EAAC,WAAW,UAAU,CAAA,GAAI,SAAS,cAAc,uBAAuB,KAAK,OAAO,IAAI,GAAG,aAAA,IAAgB,cAAc,OAAO,UAAU;AAChJ,mBAAiB,YAAY,YAAY;AACzC,QAAM,QAAQ,SAAA;AACd,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU,KAAK;AACjD,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,SAAS,IAAI;AAC7D,QAAM,gBAAgB,uBAAuB,KAAK;AAClD,QAAM,UAAU,MAAM,QAAQ,EAAE,IAAI,KAAK,CAAC,EAAE;AAE5C,QAAM,YAAY,OAAO,WAAW;AAChC,wBAAoB,OAAO,GAAG;AAC9B,QAAI;AACA,YAAM,OAAO,QAAA;AAAA,IACjB,UAAA;AACI,0BAAoB,IAAI;AAAA,IAC5B;AAAA,EACJ;AACA,QAAM,eAAe,CAAC,WAAW,qBAAqB,QAAQ,QAAQ,OAAO,QAAQ;AAErF,QAAM,cACF,oBAAC,SAAA,EAAQ,OAAO,EAAE,iBAAiB,GAC/B,UAAA,oBAAC,YAAA,EAAW,cAAY,EAAE,iBAAiB,GAAG,SAAS,SACnD,UAAA,oBAAC,kBAAA,CAAA,CAAiB,GACtB,GACJ;AAIJ,QAAM,MACF;AAAA,IAAC;AAAA,IAAA;AAAA,MAAa,MAAK;AAAA,MAAU,cAAY,EAAE,wBAAwB;AAAA,MAAG,WAAU;AAAA,MAAM,SAAS;AAAA,MAAG,YAAU;AAAA,MACrG,OAAO;AAAA,QAAC,EAAC,UAAU,GAAG,YAAY,UAAU,UAAU,QAAQ,WAAW,GAAA;AAAA,QACrE,iBAAiB,EAAC,CAAC,iBAAiB,GAAG,EAAC,SAAS,SAAM;AAAA,QAAI,GAAG;AAAA,MAAA;AAAA,MACrE,UAAA;AAAA,QAAA,oBAAC,gBAAA,EAAe,SAAQ,SAAQ,OAAO,EAAC,YAAY,KAAK,IAAI,EAAA,GAAK,UAAA,UAAA,CAAU;AAAA,QAC3E,QAAQ,IAAI,CAAC,WACV;AAAA,UAAC;AAAA,UAAA;AAAA,YAAwB,MAAK;AAAA,YAAQ,OAAO,OAAO,SAAS;AAAA,YAAW,WAAW,OAAO;AAAA,YAClF,UAAU,aAAa,MAAM;AAAA,YAAG,SAAS,MAAM,UAAU,MAAM;AAAA,YAClE,UAAA,OAAO;AAAA,UAAA;AAAA,UAFC,OAAO;AAAA,QAAA,CAIvB;AAAA,4BACA,gBAAA,EAAe,MAAK,SAAQ,OAAM,WAAU,WAAW,oBAAC,kBAAA,CAAA,CAAiB,GAAI,SAAS,SAAS,OAAO,EAAC,IAAI,UACvG,UAAA,EAAE,iBAAiB,EAAA,CACxB;AAAA,MAAA;AAAA,IAAA;AAAA,EAAA;AAIR,MAAI,CAAC,eAAe;AAChB,WAAO;AAAA,EACX;AAEA,QAAM,qBAAqB,gBAAgB,uBAAuB,SAAS,QAAQ,SAAS;AAE5F,SACI,qBAAA,UAAA,EACK,UAAA;AAAA,IAAA;AAAA,IAGD,oBAAC,cAAW,MAAI,MAAC,WAAS,MAAC,OAAO,IAAI,IAAI,eAAe,UAAU,eAC/D,UAAA,qBAAC,YAAA,EAAW,cAAY,EAAE,wBAAwB,GAAG,IAAI,EAAC,IAAI,EAAA,GAC1D,UAAA;AAAA,MAAA,oBAAC,uBAAoB,SAAQ,SAAQ,MAAK,UAAS,aAAU,UAAS,OAAO,EAAC,YAAY,KAAK,YAAY,UAAU,IAAI,IAAA,GACpH,UAAA,WACL;AAAA,MACC;AAAA,IAAA,EAAA,CACL,EAAA,CACJ;AAAA,IACC,gBAAgB,uBAAuB,OAAO,QAAQ,SAAS,KAC5D,oBAAC,YAAA,EAAW,OAAO,EAAE,wBAAwB,GAAG,WAAS,MAAC,OAAO,IAAI,IAAI,eAAe,UAAU,eAC7F,UAAA,QAAQ,IAAI,CAAC,WACV;AAAA,MAAC;AAAA,MAAA;AAAA,QAAiC,OAAO,OAAO;AAAA,QAAO,MAAM,OAAO;AAAA,QAAM,OAAO,OAAO;AAAA,QACvE,UAAU,aAAa,MAAM;AAAA,QAAG,SAAS,MAAM,UAAU,MAAM;AAAA,MAAA;AAAA,MAD1D,OAAO;AAAA,IAAA,CAEhC,GACL;AAAA,IAEH,sBACG,qBAAA,UAAA,EACI,UAAA;AAAA,MAAA,oBAAC,cAAA,EAAa,QAAQ,EAAC,SAAS,EAAC,CAAC,kCAAkC,GAAG,GAAG,4BAA4B,KAAA,EAAI,EAAC,CAAG;AAAA,MAC9G,oBAAC,uBAAA,EAAsB,MAAK,WAAU,cAAY,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,aAAa,GAClH,UAAA,QAAQ,IAAI,CAAC,WAAY,OAAO,OAC7B,oBAAC,SAAA,EAAyB,OAAO,OAAO,OAEpC,UAAA,oBAAC,QAAA,EACG,UAAA;AAAA,QAAC;AAAA,QAAA;AAAA,UAAW,cAAY,OAAO;AAAA,UAAO,OAAO,OAAO,SAAS;AAAA,UAAW,UAAU,aAAa,MAAM;AAAA,UACzF,SAAS,MAAM,UAAU,MAAM;AAAA,UACtC,UAAA,OAAO;AAAA,QAAA;AAAA,MAAA,EACZ,CACJ,EAAA,GAPU,OAAO,GAQrB,IAEA;AAAA,QAAC;AAAA,QAAA;AAAA,UAAwB,MAAK;AAAA,UAAQ,OAAO,OAAO,SAAS;AAAA,UAAW,UAAU,aAAa,MAAM;AAAA,UAC7F,SAAS,MAAM,UAAU,MAAM;AAAA,UAClC,UAAA,OAAO;AAAA,QAAA;AAAA,QAFC,OAAO;AAAA,MAAA,CAItB,EAAA,CACN;AAAA,IAAA,EAAA,CACJ;AAAA,EAAA,GAER;AAER;AAGA,MAAM,mBAAmB,eAAe,yBAAyB,UAAU;"}
|
|
@@ -13,10 +13,12 @@ import Typography from "@mui/material/Typography";
|
|
|
13
13
|
import ExpandMoreRoundedIcon from "@mui/icons-material/ExpandMoreRounded";
|
|
14
14
|
import OpenInNewRoundedIcon from "@mui/icons-material/OpenInNewRounded";
|
|
15
15
|
import { initialsOf } from "../../appShell/menuItemText.js";
|
|
16
|
-
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
16
|
+
import { piecePart, withPieceSlots, usePieceProps } from "../../theme/themePieces.js";
|
|
17
17
|
import { useToolkitTranslation } from "../../toolkit/useToolkitTranslation.js";
|
|
18
18
|
import { warnUnknownProps } from "../../toolkit/unknownProps.js";
|
|
19
19
|
import ActionsMenu from "../actions/ActionsMenu.js";
|
|
20
|
+
const RECORD_CARD_FOOTER_ALIGN = Object.freeze({ spaceBetween: "spaceBetween", start: "start", end: "end" });
|
|
21
|
+
const FOOTER_JUSTIFY = Object.freeze({ spaceBetween: "flex-start", start: "flex-start", end: "flex-end" });
|
|
20
22
|
const THEME_NAME = "CalcitaRecordCard";
|
|
21
23
|
const RecordCardRoot = piecePart(THEME_NAME, "Root", Paper);
|
|
22
24
|
const RecordCardHeader = piecePart(THEME_NAME, "Header", Stack);
|
|
@@ -29,7 +31,7 @@ const RecordCardFact = piecePart(THEME_NAME, "Fact", Box);
|
|
|
29
31
|
const RecordCardHighlight = piecePart(THEME_NAME, "Highlight", Box);
|
|
30
32
|
const RecordCardFooter = piecePart(THEME_NAME, "Footer", Stack);
|
|
31
33
|
const RecordCardDetails = piecePart(THEME_NAME, "Details", Box);
|
|
32
|
-
function
|
|
34
|
+
function RecordCardContent(props) {
|
|
33
35
|
const {
|
|
34
36
|
title,
|
|
35
37
|
eyebrow,
|
|
@@ -42,6 +44,7 @@ function RecordCard(props) {
|
|
|
42
44
|
trailing,
|
|
43
45
|
columns,
|
|
44
46
|
footer,
|
|
47
|
+
footerAlign = RECORD_CARD_FOOTER_ALIGN.spaceBetween,
|
|
45
48
|
details,
|
|
46
49
|
detailsLabel,
|
|
47
50
|
defaultDetailsOpen = false,
|
|
@@ -65,7 +68,7 @@ function RecordCard(props) {
|
|
|
65
68
|
};
|
|
66
69
|
const hasBody = groups.length > 0 || highlight || trailing;
|
|
67
70
|
const desktopColumns = columns ?? `${groups.length > 0 ? `repeat(${groups.length}, minmax(0, 1fr))` : ""}${highlight ? " auto" : ""}${trailing ? " auto" : ""}`.trim();
|
|
68
|
-
return /* @__PURE__ */ jsxs(RecordCardRoot, { variant: "outlined", component: "article", "aria-labelledby": titleId, ownSx: { p: { xs: 2, sm: 2.5 }, minWidth: 0,
|
|
71
|
+
return /* @__PURE__ */ jsxs(RecordCardRoot, { variant: "outlined", component: "article", "aria-labelledby": titleId, ownSx: { p: { xs: 2, sm: 2.5 }, minWidth: 0 }, sx, children: [
|
|
69
72
|
header ?? /* @__PURE__ */ jsxs(RecordCardHeader, { direction: "row", ownSx: { gap: 1.5, alignItems: "flex-start", flexWrap: { xs: "wrap", sm: "nowrap" } }, children: [
|
|
70
73
|
avatar && /* @__PURE__ */ jsx(RecordCardAvatar, { ownSx: { width: 44, height: 44, bgcolor: "action.selected", color: "text.primary", fontSize: 16, fontWeight: 600 }, children: typeof avatar === "string" ? initialsOf(avatar) : avatar }),
|
|
71
74
|
/* @__PURE__ */ jsxs(RecordCardTitles, { ownSx: { flex: 1, minWidth: 0, order: 1 }, children: [
|
|
@@ -122,8 +125,8 @@ function RecordCard(props) {
|
|
|
122
125
|
trailing && /* @__PURE__ */ jsx(Box, { sx: { minWidth: 0, alignSelf: "center", borderTop: { xs: 1, md: 0 }, pt: { xs: 1.5, md: 0 }, borderColor: "divider" }, children: trailing })
|
|
123
126
|
] }),
|
|
124
127
|
children,
|
|
125
|
-
(footer || details) && /* @__PURE__ */ jsxs(RecordCardFooter, { direction: "row", ownSx: { mt: 2, gap: 1, alignItems: "center", flexWrap: "wrap" }, children: [
|
|
126
|
-
/* @__PURE__ */ jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: footer }),
|
|
128
|
+
(footer || details) && /* @__PURE__ */ jsxs(RecordCardFooter, { direction: "row", ownSx: { mt: 2, gap: 1, alignItems: "center", flexWrap: "wrap", justifyContent: FOOTER_JUSTIFY[footerAlign] ?? "flex-start" }, children: [
|
|
129
|
+
/* @__PURE__ */ jsx(Box, { sx: { flex: footerAlign === RECORD_CARD_FOOTER_ALIGN.spaceBetween ? 1 : "0 1 auto", minWidth: 0 }, children: footer }),
|
|
127
130
|
details && /* @__PURE__ */ jsx(
|
|
128
131
|
Button,
|
|
129
132
|
{
|
|
@@ -139,7 +142,9 @@ function RecordCard(props) {
|
|
|
139
142
|
details && /* @__PURE__ */ jsx(Collapse, { in: isDetailsOpen, unmountOnExit: true, children: /* @__PURE__ */ jsx(RecordCardDetails, { id: detailsId, ownSx: { mt: 2, pt: 2, borderTop: 1, borderColor: "divider" }, children: details }) })
|
|
140
143
|
] });
|
|
141
144
|
}
|
|
145
|
+
const RecordCard = withPieceSlots(RecordCardContent, THEME_NAME);
|
|
142
146
|
export {
|
|
147
|
+
RECORD_CARD_FOOTER_ALIGN,
|
|
143
148
|
RecordCard as default
|
|
144
149
|
};
|
|
145
150
|
//# sourceMappingURL=RecordCard.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecordCard.js","sources":["../../../src/ui/cards/RecordCard.jsx"],"sourcesContent":["// La tarjeta de un registro (un pedido, un cliente, una solicitud) para listas donde una tabla se queda corta: cada registro\n// con su identidad arriba, sus datos agrupados en el cuerpo y lo que se puede hacer con él. Para una lista de tarjetas,\n// una debajo de otra; con RecordCardSkeleton mientras carga.\n//\n// encabezado avatar (iniciales o ícono), eyebrow (el folio), title, subtitle; a la derecha status (estatus e\n// insignias) y el menú «⋯» (actions)\n// cuerpo groups: grupos de datos {title?, facts: [{label, value}]} lado a lado en escritorio y uno debajo de otro en\n// el teléfono; highlight: el dato principal, grande (el total)\n// abajo footer (botones, una cuenta regresiva, un formulario corto) y details: lo que se despliega con «Ver\n// detalle» (una tabla, un historial), que avisa con onDetailsToggle\n//\n// El ORDEN es el que se da y nada se reacomoda solo: los grupos de izquierda a derecha (de arriba abajo en el teléfono) y\n// los datos de cada grupo en el orden de facts. Para un orden obligatorio (el de un sistema que ya existe, donde el estatus\n// va después de las fechas), trailing es la última columna del cuerpo y columns fija el ancho de cada una.\n//\n// La tarjeta NO se pica entera (la regla de la casa: una acción, un botón): para abrir el registro, onOpen pone su botón.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaRecordCard con defaultProps y\n// styleOverrides de sus partes: root, header, avatar, titles, status, body, group, fact, highlight, footer, details.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport Avatar from '@mui/material/Avatar';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Collapse from '@mui/material/Collapse';\nimport IconButton from '@mui/material/IconButton';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';\nimport OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';\n\nimport {initialsOf} from '@/appShell/menuItemText.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport ActionsMenu from '@/ui/actions/ActionsMenu.jsx';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaRecordCard)\nconst THEME_NAME = 'CalcitaRecordCard';\nconst RecordCardRoot = piecePart(THEME_NAME, 'Root', Paper);\nconst RecordCardHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst RecordCardAvatar = piecePart(THEME_NAME, 'Avatar', Avatar);\nconst RecordCardTitles = piecePart(THEME_NAME, 'Titles', Box);\nconst RecordCardStatus = piecePart(THEME_NAME, 'Status', Stack);\nconst RecordCardBody = piecePart(THEME_NAME, 'Body', Box);\nconst RecordCardGroup = piecePart(THEME_NAME, 'Group', Box);\nconst RecordCardFact = piecePart(THEME_NAME, 'Fact', Box);\nconst RecordCardHighlight = piecePart(THEME_NAME, 'Highlight', Box);\nconst RecordCardFooter = piecePart(THEME_NAME, 'Footer', Stack);\nconst RecordCardDetails = piecePart(THEME_NAME, 'Details', Box);\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} props.title el nombre del registro, ya traducido\n * @param {import('react').ReactNode} [props.eyebrow] arriba del título: el folio o el tipo\n * @param {import('react').ReactNode} [props.subtitle] abajo del título\n * @param {string|import('react').ReactNode} [props.avatar] un texto (de él salen las iniciales) o un ícono\n * @param {import('react').ReactNode} [props.status] a la derecha: StatusChip, IconBadge…\n * @param {Parameters<typeof import('@/ui/actions/ActionsMenu.jsx').default>[0]['actions']} [props.actions] el menú «⋯», con sus secciones,\n * separadores, ligas y submenús (los renglones de ActionsMenu)\n * @param {Array<{key?: string, title?: import('react').ReactNode, facts?: Array<{label: import('react').ReactNode, value: import('react').ReactNode, emphasis?: boolean}>, content?: import('react').ReactNode}>} [props.groups]\n * los datos, en grupos; emphasis los pone en negritas; content en lugar de facts para un grupo libre (una barra, unas\n * insignias, un enlace)\n * @param {import('react').ReactNode} [props.header] un encabezado propio en lugar del de la casa (avatar, títulos, estatus)\n * @param {import('react').ReactNode} [props.children] contenido libre entre el cuerpo y lo de abajo\n * @param {{label: import('react').ReactNode, value: import('react').ReactNode, caption?: import('react').ReactNode}} [props.highlight] el dato principal\n * @param {import('react').ReactNode} [props.trailing] la última columna del cuerpo, después de los grupos y del dato principal\n * (el estatus o unos botones, cuando el orden obligatorio los pone al final)\n * @param {string} [props.columns] en escritorio, el ancho de cada columna del cuerpo como en CSS ('1.2fr 1fr 1fr auto'); por\n * omisión, los grupos a partes iguales y el dato principal y trailing a su medida\n * @param {import('react').ReactNode} [props.footer] abajo, a todo lo ancho\n * @param {import('react').ReactNode} [props.details] lo que se despliega con «Ver detalle»\n * @param {string} [props.detailsLabel] ya traducido; por omisión «Ver detalle»\n * @param {boolean} [props.defaultDetailsOpen]\n * @param {(isOpen: boolean) => void} [props.onDetailsToggle]\n * @param {() => void} [props.onOpen] el botón para abrir el registro (su detalle)\n * @param {string} [props.openLabel] ya traducido: el nombre de ese botón («Abrir el pedido P-1203»)\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function RecordCard(props) {\n const {\n title, eyebrow, subtitle, avatar, status, actions, groups = [], highlight, trailing, columns, footer, details, detailsLabel, defaultDetailsOpen = false,\n onDetailsToggle, onOpen, openLabel, header, children, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const titleId = useId();\n const detailsId = useId();\n const [isDetailsOpen, setIsDetailsOpen] = useState(defaultDetailsOpen);\n const toggleDetails = () => {\n setIsDetailsOpen((current) => !current);\n onDetailsToggle?.(!isDetailsOpen);\n };\n const hasBody = groups.length > 0 || highlight || trailing;\n // las columnas en escritorio: las que se pidan, o los grupos a partes iguales y lo demás a su medida\n const desktopColumns = columns ?? `${groups.length > 0 ? `repeat(${groups.length}, minmax(0, 1fr))` : ''}${highlight ? ' auto' : ''}${trailing ? ' auto' : ''}`.trim();\n\n return (\n <RecordCardRoot variant=\"outlined\" component=\"article\" aria-labelledby={titleId} ownSx={{p: {xs: 2, sm: 2.5}, minWidth: 0, ...sx}}>\n {/* en el teléfono el estatus baja abajo del título (si no, le quitaba medio ancho) y arriba quedan los botones */}\n {header ?? (\n <RecordCardHeader direction=\"row\" ownSx={{gap: 1.5, alignItems: 'flex-start', flexWrap: {xs: 'wrap', sm: 'nowrap'}}}>\n {avatar && (\n <RecordCardAvatar ownSx={{width: 44, height: 44, bgcolor: 'action.selected', color: 'text.primary', fontSize: 16, fontWeight: 600}}>\n {typeof avatar === 'string' ? initialsOf(avatar) : avatar}\n </RecordCardAvatar>\n )}\n <RecordCardTitles ownSx={{flex: 1, minWidth: 0, order: 1}}>\n {eyebrow && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{fontVariantNumeric: 'tabular-nums'}}>{eyebrow}</Typography>}\n <Typography id={titleId} variant=\"subtitle1\" component=\"h3\" sx={{fontWeight: 600, overflowWrap: 'anywhere'}}>{title}</Typography>\n {subtitle && <Typography variant=\"body2\" color=\"text.secondary\">{subtitle}</Typography>}\n </RecordCardTitles>\n {status && (\n <RecordCardStatus direction=\"row\" ownSx={{\n gap: 0.75, alignItems: 'center', flexWrap: 'wrap', order: {xs: 3, sm: 2},\n flexBasis: {xs: '100%', sm: 'auto'}, justifyContent: {xs: 'flex-start', sm: 'flex-end'}, maxWidth: {sm: '50%'},\n }}>\n {status}\n </RecordCardStatus>\n )}\n <Stack direction=\"row\" sx={{gap: 0.25, alignItems: 'center', order: {xs: 2, sm: 3}, flexShrink: 0}}>\n {onOpen && (\n <Tooltip title={openLabel ?? t('recordCard.open')}>\n <IconButton size=\"small\" aria-label={openLabel ?? t('recordCard.open')} onClick={onOpen}><OpenInNewRoundedIcon fontSize=\"small\" /></IconButton>\n </Tooltip>\n )}\n {actions?.length > 0 && <ActionsMenu label={t('recordCard.actions')} actions={actions} size=\"small\" />}\n </Stack>\n </RecordCardHeader>\n )}\n\n {hasBody && (\n <RecordCardBody ownSx={{\n mt: 2, display: 'grid', gap: {xs: 1.5, md: 3}, alignItems: 'start',\n // en escritorio los grupos lado a lado y el dato principal al final; en el teléfono, uno debajo de otro\n gridTemplateColumns: {xs: '1fr', md: desktopColumns},\n }}>\n {groups.map((group, groupIndex) => (\n <RecordCardGroup key={group.key ?? groupIndex} ownSx={{\n minWidth: 0, pt: {xs: groupIndex > 0 ? 1.5 : 0, md: 0}, borderTop: {xs: groupIndex > 0 ? 1 : 0, md: 0}, borderColor: 'divider',\n }}>\n {group.title && <Typography variant=\"overline\" color=\"text.secondary\" component=\"p\" sx={{lineHeight: 1.8}}>{group.title}</Typography>}\n {group.content ?? (\n <Box component=\"dl\" sx={{m: 0, display: 'grid', gridTemplateColumns: 'auto 1fr', columnGap: 1.5, rowGap: 0.5}}>\n {(group.facts ?? []).map((fact, factIndex) => (\n <RecordCardFact key={factIndex} ownSx={{display: 'contents'}}>\n <Typography component=\"dt\" variant=\"body2\" color=\"text.secondary\">{fact.label}</Typography>\n <Typography component=\"dd\" variant=\"body2\" sx={{m: 0, textAlign: 'right', fontVariantNumeric: 'tabular-nums', fontWeight: fact.emphasis ? 600 : 400, overflowWrap: 'anywhere'}}>\n {fact.value}\n </Typography>\n </RecordCardFact>\n ))}\n </Box>\n )}\n </RecordCardGroup>\n ))}\n {highlight && (\n <RecordCardHighlight ownSx={{\n textAlign: {xs: 'left', md: 'right'}, pl: {md: 3}, borderLeft: {md: 1}, borderTop: {xs: 1, md: 0}, pt: {xs: 1.5, md: 0}, borderColor: 'divider',\n }}>\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"p\" sx={{lineHeight: 1.8}}>{highlight.label}</Typography>\n <Typography variant=\"h5\" component=\"p\" sx={{fontWeight: 700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap'}}>{highlight.value}</Typography>\n {highlight.caption && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">{highlight.caption}</Typography>}\n </RecordCardHighlight>\n )}\n {trailing && (\n <Box sx={{minWidth: 0, alignSelf: 'center', borderTop: {xs: 1, md: 0}, pt: {xs: 1.5, md: 0}, borderColor: 'divider'}}>{trailing}</Box>\n )}\n </RecordCardBody>\n )}\n\n {children}\n {(footer || details) && (\n <RecordCardFooter direction=\"row\" ownSx={{mt: 2, gap: 1, alignItems: 'center', flexWrap: 'wrap'}}>\n <Box sx={{flex: 1, minWidth: 0}}>{footer}</Box>\n {details && (\n <Button size=\"small\" onClick={toggleDetails} aria-expanded={isDetailsOpen} aria-controls={detailsId}\n endIcon={<ExpandMoreRoundedIcon sx={{transform: isDetailsOpen ? 'rotate(180deg)' : 'none', transition: 'transform 200ms'}} />}>\n {isDetailsOpen ? t('recordCard.hideDetails') : (detailsLabel ?? t('recordCard.showDetails'))}\n </Button>\n )}\n </RecordCardFooter>\n )}\n {details && (\n <Collapse in={isDetailsOpen} unmountOnExit>\n <RecordCardDetails id={detailsId} ownSx={{mt: 2, pt: 2, borderTop: 1, borderColor: 'divider'}}>{details}</RecordCardDetails>\n </Collapse>\n )}\n </RecordCardRoot>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RecordCard.js","sources":["../../../src/ui/cards/RecordCard.jsx"],"sourcesContent":["// La tarjeta de un registro (un pedido, un cliente, una solicitud) para listas donde una tabla se queda corta: cada registro\n// con su identidad arriba, sus datos agrupados en el cuerpo y lo que se puede hacer con él. Para una lista de tarjetas,\n// una debajo de otra; con RecordCardSkeleton mientras carga.\n//\n// encabezado avatar (iniciales o ícono), eyebrow (el folio), title, subtitle; a la derecha status (estatus e\n// insignias) y el menú «⋯» (actions)\n// cuerpo groups: grupos de datos {title?, facts: [{label, value}]} lado a lado en escritorio y uno debajo de otro en\n// el teléfono; highlight: el dato principal, grande (el total)\n// abajo footer (botones, una cuenta regresiva, un formulario corto) y details: lo que se despliega con «Ver\n// detalle» (una tabla, un historial), que avisa con onDetailsToggle. footerAlign acomoda ese renglón: el\n// footer a la izquierda y «Ver detalle» en la otra orilla (lo de siempre), o los dos juntos a la izquierda o a\n// la derecha (en una tarjeta ancha, dos controles en orillas opuestas se ven sueltos)\n//\n// El ORDEN es el que se da y nada se reacomoda solo: los grupos de izquierda a derecha (de arriba abajo en el teléfono) y\n// los datos de cada grupo en el orden de facts. Para un orden obligatorio (el de un sistema que ya existe, donde el estatus\n// va después de las fechas), trailing es la última columna del cuerpo y columns fija el ancho de cada una.\n//\n// La tarjeta NO se pica entera (la regla de la casa: una acción, un botón): para abrir el registro, onOpen pone su botón.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaRecordCard con defaultProps y\n// styleOverrides de sus partes: root, header, avatar, titles, status, body, group, fact, highlight, footer, details.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId, useState} from 'react';\nimport Avatar from '@mui/material/Avatar';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Collapse from '@mui/material/Collapse';\nimport IconButton from '@mui/material/IconButton';\nimport Paper from '@mui/material/Paper';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport ExpandMoreRoundedIcon from '@mui/icons-material/ExpandMoreRounded';\nimport OpenInNewRoundedIcon from '@mui/icons-material/OpenInNewRounded';\n\nimport {initialsOf} from '@/appShell/menuItemText.js';\nimport {piecePart, usePieceProps, withPieceSlots} from '@/theme/themePieces.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {warnUnknownProps} from '@/toolkit/unknownProps.js';\nimport ActionsMenu from '@/ui/actions/ActionsMenu.jsx';\n\n/**\n * Cómo se acomoda el renglón de abajo (footer y «Ver detalle»):\n * spaceBetween el footer a la izquierda y «Ver detalle» en la otra orilla (lo de siempre);\n * start los dos juntos, a la izquierda;\n * end los dos juntos, a la derecha.\n */\nexport const RECORD_CARD_FOOTER_ALIGN = Object.freeze({spaceBetween: 'spaceBetween', start: 'start', end: 'end'});\n// cómo se reparte el renglón en cada caso: con spaceBetween el footer se estira y empuja «Ver detalle» a la orilla\nconst FOOTER_JUSTIFY = Object.freeze({spaceBetween: 'flex-start', start: 'flex-start', end: 'flex-end'});\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaRecordCard)\nconst THEME_NAME = 'CalcitaRecordCard';\nconst RecordCardRoot = piecePart(THEME_NAME, 'Root', Paper);\nconst RecordCardHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst RecordCardAvatar = piecePart(THEME_NAME, 'Avatar', Avatar);\nconst RecordCardTitles = piecePart(THEME_NAME, 'Titles', Box);\nconst RecordCardStatus = piecePart(THEME_NAME, 'Status', Stack);\nconst RecordCardBody = piecePart(THEME_NAME, 'Body', Box);\nconst RecordCardGroup = piecePart(THEME_NAME, 'Group', Box);\nconst RecordCardFact = piecePart(THEME_NAME, 'Fact', Box);\nconst RecordCardHighlight = piecePart(THEME_NAME, 'Highlight', Box);\nconst RecordCardFooter = piecePart(THEME_NAME, 'Footer', Stack);\nconst RecordCardDetails = piecePart(THEME_NAME, 'Details', Box);\n\n/**\n * @param {object} props\n * @param {import('react').ReactNode} props.title el nombre del registro, ya traducido\n * @param {import('react').ReactNode} [props.eyebrow] arriba del título: el folio o el tipo\n * @param {import('react').ReactNode} [props.subtitle] abajo del título\n * @param {string|import('react').ReactNode} [props.avatar] un texto (de él salen las iniciales) o un ícono\n * @param {import('react').ReactNode} [props.status] a la derecha: StatusChip, IconBadge…\n * @param {Parameters<typeof import('@/ui/actions/ActionsMenu.jsx').default>[0]['actions']} [props.actions] el menú «⋯», con sus secciones,\n * separadores, ligas y submenús (los renglones de ActionsMenu)\n * @param {Array<{key?: string, title?: import('react').ReactNode, facts?: Array<{label: import('react').ReactNode, value: import('react').ReactNode, emphasis?: boolean}>, content?: import('react').ReactNode}>} [props.groups]\n * los datos, en grupos; emphasis los pone en negritas; content en lugar de facts para un grupo libre (una barra, unas\n * insignias, un enlace)\n * @param {import('react').ReactNode} [props.header] un encabezado propio en lugar del de la casa (avatar, títulos, estatus)\n * @param {import('react').ReactNode} [props.children] contenido libre entre el cuerpo y lo de abajo\n * @param {{label: import('react').ReactNode, value: import('react').ReactNode, caption?: import('react').ReactNode}} [props.highlight] el dato principal\n * @param {import('react').ReactNode} [props.trailing] la última columna del cuerpo, después de los grupos y del dato principal\n * (el estatus o unos botones, cuando el orden obligatorio los pone al final)\n * @param {string} [props.columns] en escritorio, el ancho de cada columna del cuerpo como en CSS ('1.2fr 1fr 1fr auto'); por\n * omisión, los grupos a partes iguales y el dato principal y trailing a su medida\n * @param {import('react').ReactNode} [props.footer] abajo, a todo lo ancho\n * @param {'spaceBetween'|'start'|'end'} [props.footerAlign] uno de RECORD_CARD_FOOTER_ALIGN; por omisión spaceBetween\n * @param {import('react').ReactNode} [props.details] lo que se despliega con «Ver detalle»\n * @param {string} [props.detailsLabel] ya traducido; por omisión «Ver detalle»\n * @param {boolean} [props.defaultDetailsOpen]\n * @param {(isOpen: boolean) => void} [props.onDetailsToggle]\n * @param {() => void} [props.onOpen] el botón para abrir el registro (su detalle)\n * @param {string} [props.openLabel] ya traducido: el nombre de ese botón («Abrir el pedido P-1203»)\n * @param {object} [props.sx]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nfunction RecordCardContent(props) {\n const {\n title, eyebrow, subtitle, avatar, status, actions, groups = [], highlight, trailing, columns, footer,\n footerAlign = RECORD_CARD_FOOTER_ALIGN.spaceBetween, details, detailsLabel, defaultDetailsOpen = false,\n onDetailsToggle, onOpen, openLabel, header, children, sx, texts,\n ...unknownProps\n } = usePieceProps(props, THEME_NAME);\n warnUnknownProps(THEME_NAME, unknownProps);\n const {t} = useToolkitTranslation('common', texts);\n const titleId = useId();\n const detailsId = useId();\n const [isDetailsOpen, setIsDetailsOpen] = useState(defaultDetailsOpen);\n const toggleDetails = () => {\n setIsDetailsOpen((current) => !current);\n onDetailsToggle?.(!isDetailsOpen);\n };\n const hasBody = groups.length > 0 || highlight || trailing;\n // las columnas en escritorio: las que se pidan, o los grupos a partes iguales y lo demás a su medida\n const desktopColumns = columns ?? `${groups.length > 0 ? `repeat(${groups.length}, minmax(0, 1fr))` : ''}${highlight ? ' auto' : ''}${trailing ? ' auto' : ''}`.trim();\n\n return (\n <RecordCardRoot variant=\"outlined\" component=\"article\" aria-labelledby={titleId} ownSx={{p: {xs: 2, sm: 2.5}, minWidth: 0}} sx={sx}>\n {/* en el teléfono el estatus baja abajo del título (si no, le quitaba medio ancho) y arriba quedan los botones */}\n {header ?? (\n <RecordCardHeader direction=\"row\" ownSx={{gap: 1.5, alignItems: 'flex-start', flexWrap: {xs: 'wrap', sm: 'nowrap'}}}>\n {avatar && (\n <RecordCardAvatar ownSx={{width: 44, height: 44, bgcolor: 'action.selected', color: 'text.primary', fontSize: 16, fontWeight: 600}}>\n {typeof avatar === 'string' ? initialsOf(avatar) : avatar}\n </RecordCardAvatar>\n )}\n <RecordCardTitles ownSx={{flex: 1, minWidth: 0, order: 1}}>\n {eyebrow && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{fontVariantNumeric: 'tabular-nums'}}>{eyebrow}</Typography>}\n <Typography id={titleId} variant=\"subtitle1\" component=\"h3\" sx={{fontWeight: 600, overflowWrap: 'anywhere'}}>{title}</Typography>\n {subtitle && <Typography variant=\"body2\" color=\"text.secondary\">{subtitle}</Typography>}\n </RecordCardTitles>\n {status && (\n <RecordCardStatus direction=\"row\" ownSx={{\n gap: 0.75, alignItems: 'center', flexWrap: 'wrap', order: {xs: 3, sm: 2},\n flexBasis: {xs: '100%', sm: 'auto'}, justifyContent: {xs: 'flex-start', sm: 'flex-end'}, maxWidth: {sm: '50%'},\n }}>\n {status}\n </RecordCardStatus>\n )}\n <Stack direction=\"row\" sx={{gap: 0.25, alignItems: 'center', order: {xs: 2, sm: 3}, flexShrink: 0}}>\n {onOpen && (\n <Tooltip title={openLabel ?? t('recordCard.open')}>\n <IconButton size=\"small\" aria-label={openLabel ?? t('recordCard.open')} onClick={onOpen}><OpenInNewRoundedIcon fontSize=\"small\" /></IconButton>\n </Tooltip>\n )}\n {actions?.length > 0 && <ActionsMenu label={t('recordCard.actions')} actions={actions} size=\"small\" />}\n </Stack>\n </RecordCardHeader>\n )}\n\n {hasBody && (\n <RecordCardBody ownSx={{\n mt: 2, display: 'grid', gap: {xs: 1.5, md: 3}, alignItems: 'start',\n // en escritorio los grupos lado a lado y el dato principal al final; en el teléfono, uno debajo de otro\n gridTemplateColumns: {xs: '1fr', md: desktopColumns},\n }}>\n {groups.map((group, groupIndex) => (\n <RecordCardGroup key={group.key ?? groupIndex} ownSx={{\n minWidth: 0, pt: {xs: groupIndex > 0 ? 1.5 : 0, md: 0}, borderTop: {xs: groupIndex > 0 ? 1 : 0, md: 0}, borderColor: 'divider',\n }}>\n {group.title && <Typography variant=\"overline\" color=\"text.secondary\" component=\"p\" sx={{lineHeight: 1.8}}>{group.title}</Typography>}\n {group.content ?? (\n <Box component=\"dl\" sx={{m: 0, display: 'grid', gridTemplateColumns: 'auto 1fr', columnGap: 1.5, rowGap: 0.5}}>\n {(group.facts ?? []).map((fact, factIndex) => (\n <RecordCardFact key={factIndex} ownSx={{display: 'contents'}}>\n <Typography component=\"dt\" variant=\"body2\" color=\"text.secondary\">{fact.label}</Typography>\n <Typography component=\"dd\" variant=\"body2\" sx={{m: 0, textAlign: 'right', fontVariantNumeric: 'tabular-nums', fontWeight: fact.emphasis ? 600 : 400, overflowWrap: 'anywhere'}}>\n {fact.value}\n </Typography>\n </RecordCardFact>\n ))}\n </Box>\n )}\n </RecordCardGroup>\n ))}\n {highlight && (\n <RecordCardHighlight ownSx={{\n textAlign: {xs: 'left', md: 'right'}, pl: {md: 3}, borderLeft: {md: 1}, borderTop: {xs: 1, md: 0}, pt: {xs: 1.5, md: 0}, borderColor: 'divider',\n }}>\n <Typography variant=\"overline\" color=\"text.secondary\" component=\"p\" sx={{lineHeight: 1.8}}>{highlight.label}</Typography>\n <Typography variant=\"h5\" component=\"p\" sx={{fontWeight: 700, fontVariantNumeric: 'tabular-nums', whiteSpace: 'nowrap'}}>{highlight.value}</Typography>\n {highlight.caption && <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\">{highlight.caption}</Typography>}\n </RecordCardHighlight>\n )}\n {trailing && (\n <Box sx={{minWidth: 0, alignSelf: 'center', borderTop: {xs: 1, md: 0}, pt: {xs: 1.5, md: 0}, borderColor: 'divider'}}>{trailing}</Box>\n )}\n </RecordCardBody>\n )}\n\n {children}\n {(footer || details) && (\n <RecordCardFooter direction=\"row\" ownSx={{mt: 2, gap: 1, alignItems: 'center', flexWrap: 'wrap', justifyContent: FOOTER_JUSTIFY[footerAlign] ?? 'flex-start'}}>\n <Box sx={{flex: footerAlign === RECORD_CARD_FOOTER_ALIGN.spaceBetween ? 1 : '0 1 auto', minWidth: 0}}>{footer}</Box>\n {details && (\n <Button size=\"small\" onClick={toggleDetails} aria-expanded={isDetailsOpen} aria-controls={detailsId}\n endIcon={<ExpandMoreRoundedIcon sx={{transform: isDetailsOpen ? 'rotate(180deg)' : 'none', transition: 'transform 200ms'}} />}>\n {isDetailsOpen ? t('recordCard.hideDetails') : (detailsLabel ?? t('recordCard.showDetails'))}\n </Button>\n )}\n </RecordCardFooter>\n )}\n {details && (\n <Collapse in={isDetailsOpen} unmountOnExit>\n <RecordCardDetails id={detailsId} ownSx={{mt: 2, pt: 2, borderTop: 1, borderColor: 'divider'}}>{details}</RecordCardDetails>\n </Collapse>\n )}\n </RecordCardRoot>\n );\n}\n\n// lo que se exporta: la pieza con slots y slotProps para cambiar sus partes (themePieces.js)\nconst RecordCard = withPieceSlots(RecordCardContent, THEME_NAME);\nexport default RecordCard;\n"],"names":[],"mappings":"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"}
|