@stackcraftmx/calcita 0.23.0 → 0.25.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 +111 -22
- package/cli/projectFiles.js +5 -3
- package/cli/templates/project/README.md.tpl +28 -0
- package/cli/templates/project/public/manifest.webmanifest.tpl +6 -6
- package/cli/templates/project/src/app/AppProviders.jsx.tpl +9 -4
- package/cli/templates/project/src/app/appRouter.jsx.tpl +4 -2
- package/cli/templates/project/src/app/appSettings.js.tpl +14 -0
- package/cli/templates/project/src/core/AppLogo.jsx.tpl +3 -1
- package/cli/templates/project/src/core/index.js.tpl +1 -0
- package/cli/templates/project/src/core/publicPaths.js.tpl +24 -0
- package/cli/templates/project/vite.config.js.tpl +27 -1
- package/dist/DataTable/AuditTrailTable.js +5 -3
- package/dist/DataTable/AuditTrailTable.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +2 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/EmptyState.js +2 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +2 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +4 -3
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +153 -83
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/rowSelection.js +61 -0
- package/dist/DataTable/rowSelection.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountSecuritySection.js +2 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/ForgotPasswordScreen.js +5 -4
- package/dist/account/ForgotPasswordScreen.js.map +1 -1
- package/dist/account/ProfileScreen.js +39 -13
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/ResetPasswordScreen.js +3 -2
- package/dist/account/ResetPasswordScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +7 -6
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/account/accountConnector.js.map +1 -1
- package/dist/account/restAccountConnector.js +2 -1
- package/dist/account/restAccountConnector.js.map +1 -1
- package/dist/adapters/reactRouterRoutes.js +7 -6
- package/dist/adapters/reactRouterRoutes.js.map +1 -1
- package/dist/api/createApiClient.js +35 -5
- package/dist/api/createApiClient.js.map +1 -1
- package/dist/appShell/AppLayout.js +27 -7
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +29 -6
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +2 -0
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +3 -3
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +19 -8
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +19 -4
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +92 -0
- package/dist/appShell/BarSearch.js.map +1 -0
- package/dist/appShell/MobileTabBar.js +3 -1
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/shellSizes.js +2 -0
- package/dist/appShell/shellSizes.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +2 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +16 -5
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +74 -0
- package/dist/calendar/EventDetails.js.map +1 -0
- package/dist/calendar/calendarEvents.js +31 -0
- package/dist/calendar/calendarEvents.js.map +1 -0
- package/dist/calendar.js +8 -1
- package/dist/calendar.js.map +1 -1
- package/dist/charts.js +10 -1
- package/dist/charts.js.map +1 -1
- package/dist/datePickers/DateInput.js +3 -2
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/downloads/DownloadManager.js +14 -20
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/downloads/downloadQueue.js +4 -2
- package/dist/downloads/downloadQueue.js.map +1 -1
- package/dist/downloads/downloadsMachine.js +4 -1
- package/dist/downloads/downloadsMachine.js.map +1 -1
- package/dist/downloads/fileTypes.js +22 -0
- package/dist/downloads/fileTypes.js.map +1 -0
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +2 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +2 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +5 -3
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/formatting.js +171 -143
- package/dist/formatting.js.map +1 -1
- package/dist/forms/FormWizard.js +18 -45
- package/dist/forms/FormWizard.js.map +1 -1
- package/dist/forms/auth/ChangePasswordForm.js +5 -4
- package/dist/forms/auth/ChangePasswordForm.js.map +1 -1
- package/dist/forms/auth/ForgotPasswordForm.js +2 -2
- package/dist/forms/auth/ForgotPasswordForm.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +2 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/security/SecondStepForm.js +2 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +8 -6
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/hookForm/FormWizard.js +59 -0
- package/dist/hookForm/FormWizard.js.map +1 -0
- package/dist/inbox/InboxLayout.js +44 -8
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +71 -21
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +80 -29
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/inbox/inboxDates.js +4 -3
- package/dist/inbox/inboxDates.js.map +1 -1
- package/dist/index.js +157 -133
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +8 -4
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +3 -2
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FormSection.js +3 -3
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/NumberInput.js +3 -2
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/SelectInput.js +29 -11
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +6 -2
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +54 -0
- package/dist/inputs/WizardLayout.js.map +1 -0
- package/dist/kanban/KanbanBoard.js +239 -14
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/kanban/kanbanCards.js +19 -0
- package/dist/kanban/kanbanCards.js.map +1 -0
- package/dist/kanban.js +7 -1
- package/dist/kanban.js.map +1 -1
- package/dist/locales/en.js +74 -1
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +74 -1
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +28 -8
- package/dist/maps/MapView.js.map +1 -1
- package/dist/observability/usePublishedBuildWatch.js +4 -3
- package/dist/observability/usePublishedBuildWatch.js.map +1 -1
- package/dist/pwa/InstallAppSetting.js +6 -5
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +2 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +15 -9
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/pwa/useInstallPrompt.js +2 -1
- package/dist/pwa/useInstallPrompt.js.map +1 -1
- package/dist/react-hook-form.js +6 -4
- package/dist/react-hook-form.js.map +1 -1
- package/dist/security/ActivityLog.js +4 -3
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +4 -3
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +8 -6
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +2 -0
- package/dist/table.js.map +1 -1
- package/dist/theme/components/MuiDialog.js +13 -0
- package/dist/theme/components/MuiDialog.js.map +1 -0
- package/dist/theme/createToolkitTheme.js.map +1 -1
- package/dist/theme/toolkitComponents.js +2 -0
- package/dist/theme/toolkitComponents.js.map +1 -1
- package/dist/toolkit/ComponentTexts.js +13 -0
- package/dist/toolkit/ComponentTexts.js.map +1 -0
- package/dist/toolkit/ToolkitProvider.js +36 -28
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -20
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/componentTextsContext.js +15 -0
- package/dist/toolkit/componentTextsContext.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +40 -0
- package/dist/toolkit/hashNavigation.js.map +1 -0
- package/dist/toolkit/navigationPaths.js +49 -0
- package/dist/toolkit/navigationPaths.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +24 -4
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/toolkit/useFormatters.js +12 -0
- package/dist/toolkit/useFormatters.js.map +1 -0
- package/dist/toolkit/useToolkitTranslation.js +11 -7
- package/dist/toolkit/useToolkitTranslation.js.map +1 -1
- package/dist/types/DataTable/AuditTrailTable.d.ts +3 -1
- package/dist/types/DataTable/EmptyState.d.ts +7 -2
- package/dist/types/DataTable/FilterChips.d.ts +3 -1
- package/dist/types/DataTable/GoToPage.d.ts +7 -2
- package/dist/types/DataTable/SelectionBar.d.ts +32 -0
- package/dist/types/DataTable/ServerTable.d.ts +64 -3
- package/dist/types/DataTable/index.d.ts +1 -1
- package/dist/types/DataTable/rowSelection.d.ts +113 -0
- package/dist/types/access/PermissionMatrix.d.ts +3 -1
- package/dist/types/account/AccountSecuritySection.d.ts +3 -1
- package/dist/types/account/ForgotPasswordScreen.d.ts +3 -1
- package/dist/types/account/ProfileScreen.d.ts +31 -1
- package/dist/types/account/ResetPasswordScreen.d.ts +3 -1
- package/dist/types/account/SignInScreen.d.ts +3 -1
- package/dist/types/account/accountConnector.d.ts +7 -2
- package/dist/types/adapters/reactRouterRoutes.d.ts +14 -2
- package/dist/types/api/createApiClient.d.ts +29 -1
- package/dist/types/appShell/AppLayout.d.ts +11 -1
- package/dist/types/appShell/AppearanceSettings.d.ts +8 -1
- package/dist/types/appShell/BarActionButton.d.ts +2 -2
- package/dist/types/appShell/BarActions.d.ts +6 -1
- package/dist/types/appShell/BarCapsule.d.ts +8 -0
- package/dist/types/appShell/BarSearch.d.ts +24 -0
- package/dist/types/appShell/MobileTabBar.d.ts +4 -1
- package/dist/types/appShell/shellSizes.d.ts +1 -0
- package/dist/types/auth/PasswordChecklist.d.ts +3 -1
- package/dist/types/calendar/EventCalendar.d.ts +18 -1
- package/dist/types/calendar/EventDetails.d.ts +36 -0
- package/dist/types/calendar/calendarEvents.d.ts +27 -0
- package/dist/types/calendar.d.ts +2 -0
- package/dist/types/charts.d.ts +4 -0
- package/dist/types/datePickers/DateInput.d.ts +3 -1
- package/dist/types/downloads/DownloadManager.d.ts +7 -1
- package/dist/types/downloads/downloadQueue.d.ts +8 -3
- package/dist/types/downloads/fileTypes.d.ts +39 -0
- package/dist/types/downloads/index.d.ts +1 -0
- package/dist/types/feedback/ConfirmDialog.d.ts +3 -1
- package/dist/types/feedback/ModalWaiting.d.ts +7 -2
- package/dist/types/feedback/NewBuildNotice.d.ts +3 -1
- package/dist/types/filepond/FileDropzone.d.ts +3 -1
- package/dist/types/formatting.d.ts +45 -90
- package/dist/types/forms/FormWizard.d.ts +3 -1
- package/dist/types/forms/auth/ChangePasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/ForgotPasswordForm.d.ts +3 -1
- package/dist/types/forms/auth/SignInForm.d.ts +3 -1
- package/dist/types/forms/security/SecondStepForm.d.ts +3 -1
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +3 -1
- package/dist/types/hookForm/FormWizard.d.ts +22 -0
- package/dist/types/inbox/InboxLayout.d.ts +14 -2
- package/dist/types/inbox/MessageList.d.ts +30 -5
- package/dist/types/inbox/MessageView.d.ts +35 -3
- package/dist/types/inbox/inboxDates.d.ts +6 -1
- package/dist/types/index.d.ts +12 -5
- package/dist/types/inputs/CatalogInput.d.ts +9 -5
- package/dist/types/inputs/ColorInput.d.ts +3 -1
- package/dist/types/inputs/FormSection.d.ts +2 -2
- package/dist/types/inputs/NumberInput.d.ts +3 -1
- package/dist/types/inputs/SelectInput.d.ts +9 -5
- package/dist/types/inputs/TextInput.d.ts +3 -1
- package/dist/types/inputs/WizardLayout.d.ts +34 -0
- package/dist/types/kanban/KanbanBoard.d.ts +61 -6
- package/dist/types/kanban/kanbanCards.d.ts +22 -0
- package/dist/types/kanban.d.ts +1 -0
- package/dist/types/locales/en.d.ts +77 -0
- package/dist/types/locales/es.d.ts +77 -0
- package/dist/types/maps/MapView.d.ts +12 -3
- package/dist/types/observability/usePublishedBuildWatch.d.ts +3 -1
- package/dist/types/pwa/InstallAppSetting.d.ts +7 -1
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +3 -1
- package/dist/types/pwa/InstallPromptBanner.d.ts +10 -1
- package/dist/types/pwa/useInstallPrompt.d.ts +3 -2
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/security/ActivityLog.d.ts +3 -1
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -1
- package/dist/types/security/SessionsCard.d.ts +3 -1
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/components/MuiDialog.d.ts +9 -0
- package/dist/types/toolkit/ComponentTexts.d.ts +11 -0
- package/dist/types/toolkit/ToolkitProvider.d.ts +18 -2
- package/dist/types/toolkit/browserNavigation.d.ts +23 -28
- package/dist/types/toolkit/componentTextsContext.d.ts +20 -0
- package/dist/types/toolkit/hashNavigation.d.ts +12 -0
- package/dist/types/toolkit/navigationPaths.d.ts +43 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +49 -4
- package/dist/types/toolkit/useFormatters.d.ts +6 -0
- package/dist/types/toolkit/useToolkitTranslation.d.ts +2 -1
- package/dist/types/ui/actions/ActionsMenu.d.ts +20 -0
- package/dist/types/ui/actions/CopyButton.d.ts +3 -1
- package/dist/types/ui/actions/DownloadMenu.d.ts +8 -3
- package/dist/types/ui/actions/SecretField.d.ts +5 -1
- package/dist/types/ui/actions/SelectionActions.d.ts +39 -0
- package/dist/types/ui/filters/DateRangeFilter.d.ts +3 -1
- package/dist/types/ui/filters/QuickFilters.d.ts +3 -1
- package/dist/types/ui/index.d.ts +1 -1
- package/dist/types/ui/labels/tagOf.d.ts +13 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +4 -1
- package/dist/types/ui/layout/ContentDialog.d.ts +3 -1
- package/dist/types/ui/layout/ErrorPage.d.ts +14 -1
- package/dist/types/ui/layout/FullPageLoader.d.ts +7 -1
- package/dist/types/ui/layout/PageHeading.d.ts +3 -1
- package/dist/types/ui/metrics/BarList.d.ts +7 -1
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +7 -1
- package/dist/types/ui/metrics/ChartTooltip.d.ts +9 -3
- package/dist/types/ui/metrics/ColumnChart.d.ts +23 -11
- package/dist/types/ui/metrics/LineChart.d.ts +41 -0
- package/dist/types/ui/metrics/MetricCard.d.ts +9 -1
- package/dist/types/ui/metrics/TableToggle.d.ts +9 -2
- package/dist/types/ui/metrics/chartParts.d.ts +112 -0
- package/dist/types/ui/navigation/BackButton.d.ts +3 -1
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +8 -2
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +7 -2
- package/dist/types/ui/navigation/LanguageToggle.d.ts +8 -3
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +5 -2
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +4 -1
- package/dist/types/upload/FileDropzone.d.ts +3 -1
- package/dist/ui/actions/ActionsMenu.js +51 -0
- package/dist/ui/actions/ActionsMenu.js.map +1 -0
- package/dist/ui/actions/CopyButton.js +2 -2
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +11 -12
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -4
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +142 -0
- package/dist/ui/actions/SelectionActions.js.map +1 -0
- package/dist/ui/filters/DateRangeFilter.js +4 -3
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +2 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/labels/tagOf.js +7 -0
- package/dist/ui/labels/tagOf.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +6 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +2 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +21 -7
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +2 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +2 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/metrics/BarList.js +18 -13
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +11 -6
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +1 -1
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +59 -63
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +117 -0
- package/dist/ui/metrics/LineChart.js.map +1 -0
- package/dist/ui/metrics/MetricCard.js +41 -8
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +4 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +93 -0
- package/dist/ui/metrics/chartParts.js.map +1 -0
- package/dist/ui/navigation/BackButton.js +2 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +6 -3
- 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 +40 -19
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +2 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +10 -8
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -3
- package/dist/upload/FileDropzone.js.map +1 -1
- package/package.json +14 -6
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shellSizes.js","sources":["../../src/appShell/shellSizes.js"],"sourcesContent":["// Las medidas y tiempos del marco de la app (AppLayout, del marco de walk-ot), en un solo lugar para que el menú, la barra y\n// el botón flotante se muevan juntos.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {GLASS_INSET_PIXELS} from '@/theme/glassSurface.js';\n\n/** El id del contenido: a donde lleva «Saltar al contenido». */\nexport const MAIN_CONTENT_ID = 'main-content';\n\n/** El menú lateral abierto (ícono y nombre; 240 como las plantillas de MUI) y encogido (solo íconos, el rail). */\nexport const DRAWER_WIDTH_EXPANDED = 240;\nexport const DRAWER_WIDTH_COLLAPSED = 64;\n\n/** El alto de la barra de arriba (sin avisos). */\nexport const APP_BAR_HEIGHT_PIXELS = 56;\n\n/**\n * Con vidrio (createToolkitTheme({glass: true})), el menú lateral y la barra flotan separados de la orilla de la ventana\n * y entre ellos, como los paneles de macOS 26: así el contenido pasa por detrás y el vidrio tiene qué desenfocar. Es la\n * misma separación que el tema les da a los cajones y a la hoja de abajo (GLASS_INSET_PIXELS), para que todo flote igual.\n */\nexport const GLASS_PANEL_GAP_PIXELS = GLASS_INSET_PIXELS;\n\n/**\n * Teléfono o escritorio. Teléfono: angosto (menos de 900 px, el «md» de MUI) O bajito (menos de 500 px de alto: un teléfono\n * acostado). Solo con el ancho, un iPhone Pro Max acostado (932 × 430) salía con el marco de escritorio y su menú lateral\n * no cabía (lo reportaron). Van como media queries para usarse en sx: [PHONE_MEDIA_QUERY]: {...}.\n */\nexport const PHONE_MEDIA_QUERY = '@media (max-width: 899.95px), (max-height: 499.95px)';\nexport const DESKTOP_MEDIA_QUERY = '@media (min-width: 900px) and (min-height: 500px)';\n/** El teléfono acostado: la barra de pestañas va compacta (ícono y nombre lado a lado) para dejarle el alto al contenido. */\nexport const PHONE_LANDSCAPE_MEDIA_QUERY = '@media (max-height: 499.95px)';\n\n/** La barra de pestañas del teléfono con vidrio (MobileTabBar): su alto parado y acostado. */\nexport const MOBILE_TAB_BAR_HEIGHT_PIXELS = Object.freeze({portrait: 60, landscape: 46});\n\n/**\n * Lo que flota abajo al centro en el teléfono con vidrio (el aviso de versión nueva, la invitación a instalar): encima de\n * la barra de pestañas, con la separación de la barra a la orilla, su alto, la separación pedida y la zona del gesto de\n * inicio del iPhone. Para el sx: ...aboveMobileTabBarOf(8).\n *\n * @param {number} gapPixels lo que se separa de la barra de pestañas (px)\n * @returns {object}\n */\nexport function aboveMobileTabBarOf(gapPixels) {\n return {\n [PHONE_MEDIA_QUERY]: {\n top: 'auto',\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n };\n}\n\n/**\n * La transición que comparten el menú, la barra y el botón flotante: si cada uno usara la suya, terminarían en frames\n * distintos y el ojo lo lee como un salto.\n *\n * @param {object} theme el tema de MUI\n * @param {string[]} [properties] lo que se anima\n * @returns {string}\n */\nexport function drawerTransitionOf(theme, properties = ['width']) {\n return theme.transitions.create(properties, {easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen});\n}\n"],"names":[],"mappings":";AAOY,MAAC,kBAAkB;AAGnB,MAAC,wBAAwB;AACzB,MAAC,yBAAyB;AAG1B,MAAC,wBAAwB;AAOzB,MAAC,yBAAyB;AAO1B,MAAC,oBAAoB;AACrB,MAAC,sBAAsB;AAEvB,MAAC,8BAA8B;AAG/B,MAAC,+BAA+B,OAAO,OAAO,EAAC,UAAU,IAAI,WAAW,GAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"shellSizes.js","sources":["../../src/appShell/shellSizes.js"],"sourcesContent":["// Las medidas y tiempos del marco de la app (AppLayout, del marco de walk-ot), en un solo lugar para que el menú, la barra y\n// el botón flotante se muevan juntos.\n//\n// Funciones puras, sin React: function (regla de la casa).\nimport {GLASS_INSET_PIXELS} from '@/theme/glassSurface.js';\n\n/** El id del contenido: a donde lleva «Saltar al contenido». */\nexport const MAIN_CONTENT_ID = 'main-content';\n\n/** El menú lateral abierto (ícono y nombre; 240 como las plantillas de MUI) y encogido (solo íconos, el rail). */\nexport const DRAWER_WIDTH_EXPANDED = 240;\nexport const DRAWER_WIDTH_COLLAPSED = 64;\n\n/** El alto de la barra de arriba (sin avisos). */\nexport const APP_BAR_HEIGHT_PIXELS = 56;\n\n/**\n * Con vidrio (createToolkitTheme({glass: true})), el menú lateral y la barra flotan separados de la orilla de la ventana\n * y entre ellos, como los paneles de macOS 26: así el contenido pasa por detrás y el vidrio tiene qué desenfocar. Es la\n * misma separación que el tema les da a los cajones y a la hoja de abajo (GLASS_INSET_PIXELS), para que todo flote igual.\n */\nexport const GLASS_PANEL_GAP_PIXELS = GLASS_INSET_PIXELS;\n\n/**\n * Teléfono o escritorio. Teléfono: angosto (menos de 900 px, el «md» de MUI) O bajito (menos de 500 px de alto: un teléfono\n * acostado). Solo con el ancho, un iPhone Pro Max acostado (932 × 430) salía con el marco de escritorio y su menú lateral\n * no cabía (lo reportaron). Van como media queries para usarse en sx: [PHONE_MEDIA_QUERY]: {...}.\n */\nexport const PHONE_MEDIA_QUERY = '@media (max-width: 899.95px), (max-height: 499.95px)';\nexport const DESKTOP_MEDIA_QUERY = '@media (min-width: 900px) and (min-height: 500px)';\n/** El teléfono acostado: la barra de pestañas va compacta (ícono y nombre lado a lado) para dejarle el alto al contenido. */\nexport const PHONE_LANDSCAPE_MEDIA_QUERY = '@media (max-height: 499.95px)';\n\n/** La barra de pestañas del teléfono con vidrio (MobileTabBar): su alto parado y acostado. */\nexport const MOBILE_TAB_BAR_HEIGHT_PIXELS = Object.freeze({portrait: 60, landscape: 46});\n\n// Lo que flota abajo encima de la barra de pestañas y dura un rato (las acciones de los elegidos de una tabla) pide su alto\n// aquí mientras se ve: el marco lo suma al espacio de abajo del contenido y el final de la página (el paginador) no queda\n// tapado. Lo pone quien flota con GlobalStyles; sin nada, vale 0.\nexport const FLOATING_BOTTOM_SPACE_CSS_VARIABLE = '--calcita-floating-bottom-space';\n\n/**\n * Lo que flota abajo al centro en el teléfono con vidrio (el aviso de versión nueva, la invitación a instalar): encima de\n * la barra de pestañas, con la separación de la barra a la orilla, su alto, la separación pedida y la zona del gesto de\n * inicio del iPhone. Para el sx: ...aboveMobileTabBarOf(8).\n *\n * @param {number} gapPixels lo que se separa de la barra de pestañas (px)\n * @returns {object}\n */\nexport function aboveMobileTabBarOf(gapPixels) {\n return {\n [PHONE_MEDIA_QUERY]: {\n top: 'auto',\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.portrait + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n [PHONE_LANDSCAPE_MEDIA_QUERY]: {\n bottom: `calc(${GLASS_PANEL_GAP_PIXELS + MOBILE_TAB_BAR_HEIGHT_PIXELS.landscape + gapPixels}px + env(safe-area-inset-bottom))`,\n },\n };\n}\n\n/**\n * La transición que comparten el menú, la barra y el botón flotante: si cada uno usara la suya, terminarían en frames\n * distintos y el ojo lo lee como un salto.\n *\n * @param {object} theme el tema de MUI\n * @param {string[]} [properties] lo que se anima\n * @returns {string}\n */\nexport function drawerTransitionOf(theme, properties = ['width']) {\n return theme.transitions.create(properties, {easing: theme.transitions.easing.sharp, duration: theme.transitions.duration.enteringScreen});\n}\n"],"names":[],"mappings":";AAOY,MAAC,kBAAkB;AAGnB,MAAC,wBAAwB;AACzB,MAAC,yBAAyB;AAG1B,MAAC,wBAAwB;AAOzB,MAAC,yBAAyB;AAO1B,MAAC,oBAAoB;AACrB,MAAC,sBAAsB;AAEvB,MAAC,8BAA8B;AAG/B,MAAC,+BAA+B,OAAO,OAAO,EAAC,UAAU,IAAI,WAAW,GAAE,CAAC;AAK3E,MAAC,qCAAqC;AAU3C,SAAS,oBAAoB,WAAW;AAC3C,SAAO;AAAA,IACH,CAAC,iBAAiB,GAAG;AAAA,MACjB,KAAK;AAAA,MACL,QAAQ,QAAQ,yBAAyB,6BAA6B,WAAW,SAAS;AAAA,IACtG;AAAA,IACQ,CAAC,2BAA2B,GAAG;AAAA,MAC3B,QAAQ,QAAQ,yBAAyB,6BAA6B,YAAY,SAAS;AAAA,IACvG;AAAA,EACA;AACA;AAUO,SAAS,mBAAmB,OAAO,aAAa,CAAC,OAAO,GAAG;AAC9D,SAAO,MAAM,YAAY,OAAO,YAAY,EAAC,QAAQ,MAAM,YAAY,OAAO,OAAO,UAAU,MAAM,YAAY,SAAS,eAAc,CAAC;AAC7I;"}
|
|
@@ -17,8 +17,8 @@ const SCREEN_READER_ONLY_SX = {
|
|
|
17
17
|
whiteSpace: "nowrap",
|
|
18
18
|
border: 0
|
|
19
19
|
};
|
|
20
|
-
function PasswordChecklist({ password, policy = DEFAULT_PASSWORD_POLICY, id }) {
|
|
21
|
-
const { t } = useToolkitTranslation("auth");
|
|
20
|
+
function PasswordChecklist({ password, policy = DEFAULT_PASSWORD_POLICY, id, texts }) {
|
|
21
|
+
const { t } = useToolkitTranslation("auth", texts);
|
|
22
22
|
return /* @__PURE__ */ jsxs(Box, { id, children: [
|
|
23
23
|
/* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", component: "p", sx: { mb: 0.5 }, children: t("passwordRules.title") }),
|
|
24
24
|
/* @__PURE__ */ jsx(Box, { component: "ul", sx: { listStyle: "none", m: 0, p: 0, display: "grid", gap: 0.5 }, children: passwordChecksOf(password, policy).map((check) => /* @__PURE__ */ jsxs(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PasswordChecklist.js","sources":["../../src/auth/PasswordChecklist.jsx"],"sourcesContent":["// La lista de lo que debe tener una contraseña nueva, que se va palomeando mientras la persona escribe: dice qué falta\n// ANTES de enviar, en lugar de un «contraseña inválida» después.\n//\n// Para el lector de pantalla cada renglón dice si ya cumple o falta (con el ícono solo, se oía la regla sin saber cómo\n// iba). Va justo debajo del campo de la contraseña nueva.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';\n\nimport {DEFAULT_PASSWORD_POLICY, passwordChecksOf} from '@/auth/passwordRules.js';\n\n// texto que solo oye el lector de pantalla (el mismo truco que usa MUI)\nconst SCREEN_READER_ONLY_SX = {\n position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap', border: 0,\n};\n\n/**\n * @param {object} props\n * @param {string} props.password lo que lleva escrito la persona\n * @param {object} [props.policy] las reglas; por omisión DEFAULT_PASSWORD_POLICY\n * @param {string} [props.id]\n */\nexport default function PasswordChecklist({password, policy = DEFAULT_PASSWORD_POLICY, id}) {\n const {t} = useToolkitTranslation('auth');\n return (\n <Box id={id}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{mb: 0.5}}>{t('passwordRules.title')}</Typography>\n <Box component=\"ul\" sx={{listStyle: 'none', m: 0, p: 0, display: 'grid', gap: 0.5}}>\n {passwordChecksOf(password, policy).map((check) => (\n <Box component=\"li\" key={check.rule} data-rule={check.rule} data-met={check.isMet}\n sx={{display: 'flex', alignItems: 'center', gap: 1, color: check.isMet ? 'text.primary' : 'text.secondary'}}>\n {check.isMet\n ? <CheckCircleIcon fontSize=\"small\" color=\"success\" aria-hidden />\n : <RadioButtonUncheckedIcon fontSize=\"small\" color=\"disabled\" aria-hidden />}\n <Typography variant=\"body2\" component=\"span\">{t(`passwordRules.${check.rule}`, check.values)}</Typography>\n <Box component=\"span\" sx={SCREEN_READER_ONLY_SX}>{check.isMet ? t('passwordRules.met') : t('passwordRules.missing')}</Box>\n </Box>\n ))}\n </Box>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAgBA,MAAM,wBAAwB;AAAA,EAC1B,UAAU;AAAA,EAAY,OAAO;AAAA,EAAO,QAAQ;AAAA,EAAO,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAQ,UAAU;AAAA,EAAU,MAAM;AAAA,EAAiB,YAAY;AAAA,EAAU,QAAQ;AAC5J;
|
|
1
|
+
{"version":3,"file":"PasswordChecklist.js","sources":["../../src/auth/PasswordChecklist.jsx"],"sourcesContent":["// La lista de lo que debe tener una contraseña nueva, que se va palomeando mientras la persona escribe: dice qué falta\n// ANTES de enviar, en lugar de un «contraseña inválida» después.\n//\n// Para el lector de pantalla cada renglón dice si ya cumple o falta (con el ícono solo, se oía la regla sin saber cómo\n// iba). Va justo debajo del campo de la contraseña nueva.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport Box from '@mui/material/Box';\nimport Typography from '@mui/material/Typography';\nimport CheckCircleIcon from '@mui/icons-material/CheckCircle';\nimport RadioButtonUncheckedIcon from '@mui/icons-material/RadioButtonUnchecked';\n\nimport {DEFAULT_PASSWORD_POLICY, passwordChecksOf} from '@/auth/passwordRules.js';\n\n// texto que solo oye el lector de pantalla (el mismo truco que usa MUI)\nconst SCREEN_READER_ONLY_SX = {\n position: 'absolute', width: '1px', height: '1px', padding: 0, margin: '-1px', overflow: 'hidden', clip: 'rect(0 0 0 0)', whiteSpace: 'nowrap', border: 0,\n};\n\n/**\n * @param {object} props\n * @param {string} props.password lo que lleva escrito la persona\n * @param {object} [props.policy] las reglas; por omisión DEFAULT_PASSWORD_POLICY\n * @param {string} [props.id]\n * @param {object} [props.texts] los textos que cambian, con la forma de `auth` en los textos del paquete\n */\nexport default function PasswordChecklist({password, policy = DEFAULT_PASSWORD_POLICY, id, texts}) {\n const {t} = useToolkitTranslation('auth', texts);\n return (\n <Box id={id}>\n <Typography variant=\"caption\" color=\"text.secondary\" component=\"p\" sx={{mb: 0.5}}>{t('passwordRules.title')}</Typography>\n <Box component=\"ul\" sx={{listStyle: 'none', m: 0, p: 0, display: 'grid', gap: 0.5}}>\n {passwordChecksOf(password, policy).map((check) => (\n <Box component=\"li\" key={check.rule} data-rule={check.rule} data-met={check.isMet}\n sx={{display: 'flex', alignItems: 'center', gap: 1, color: check.isMet ? 'text.primary' : 'text.secondary'}}>\n {check.isMet\n ? <CheckCircleIcon fontSize=\"small\" color=\"success\" aria-hidden />\n : <RadioButtonUncheckedIcon fontSize=\"small\" color=\"disabled\" aria-hidden />}\n <Typography variant=\"body2\" component=\"span\">{t(`passwordRules.${check.rule}`, check.values)}</Typography>\n <Box component=\"span\" sx={SCREEN_READER_ONLY_SX}>{check.isMet ? t('passwordRules.met') : t('passwordRules.missing')}</Box>\n </Box>\n ))}\n </Box>\n </Box>\n );\n}\n"],"names":[],"mappings":";;;;;;;;AAgBA,MAAM,wBAAwB;AAAA,EAC1B,UAAU;AAAA,EAAY,OAAO;AAAA,EAAO,QAAQ;AAAA,EAAO,SAAS;AAAA,EAAG,QAAQ;AAAA,EAAQ,UAAU;AAAA,EAAU,MAAM;AAAA,EAAiB,YAAY;AAAA,EAAU,QAAQ;AAC5J;AASA,SAAwB,kBAAkB,EAAC,UAAU,SAAS,yBAAyB,IAAI,SAAQ;AAC/F,QAAM,EAAC,EAAA,IAAK,sBAAsB,QAAQ,KAAK;AAC/C,SACI,qBAAC,OAAI,IACD,UAAA;AAAA,IAAA,oBAAC,YAAA,EAAW,SAAQ,WAAU,OAAM,kBAAiB,WAAU,KAAI,IAAI,EAAC,IAAI,IAAA,GAAO,UAAA,EAAE,qBAAqB,GAAE;AAAA,IAC5G,oBAAC,OAAI,WAAU,MAAK,IAAI,EAAC,WAAW,QAAQ,GAAG,GAAG,GAAG,GAAG,SAAS,QAAQ,KAAK,IAAA,GACzE,UAAA,iBAAiB,UAAU,MAAM,EAAE,IAAI,CAAC,UACrC;AAAA,MAAC;AAAA,MAAA;AAAA,QAAI,WAAU;AAAA,QAAsB,aAAW,MAAM;AAAA,QAAM,YAAU,MAAM;AAAA,QACvE,IAAI,EAAC,SAAS,QAAQ,YAAY,UAAU,KAAK,GAAG,OAAO,MAAM,QAAQ,iBAAiB,iBAAA;AAAA,QAC1F,UAAA;AAAA,UAAA,MAAM,QACD,oBAAC,iBAAA,EAAgB,UAAS,SAAQ,OAAM,WAAU,eAAW,KAAA,CAAC,wBAC7D,0BAAA,EAAyB,UAAS,SAAQ,OAAM,YAAW,eAAW,MAAC;AAAA,UAC9E,oBAAC,YAAA,EAAW,SAAQ,SAAQ,WAAU,QAAQ,UAAA,EAAE,iBAAiB,MAAM,IAAI,IAAI,MAAM,MAAM,GAAE;AAAA,UAC7F,oBAAC,KAAA,EAAI,WAAU,QAAO,IAAI,uBAAwB,UAAA,MAAM,QAAQ,EAAE,mBAAmB,IAAI,EAAE,uBAAuB,EAAA,CAAE;AAAA,QAAA;AAAA,MAAA;AAAA,MAN/F,MAAM;AAAA,IAAA,CAQlC,EAAA,CACL;AAAA,EAAA,GACJ;AAER;"}
|
|
@@ -61,11 +61,16 @@ function EventCalendar({
|
|
|
61
61
|
onDateSelect,
|
|
62
62
|
onEventChange,
|
|
63
63
|
height = "auto",
|
|
64
|
-
ariaLabel
|
|
64
|
+
ariaLabel,
|
|
65
|
+
onDatesChange,
|
|
66
|
+
onViewChange,
|
|
67
|
+
renderEvent,
|
|
68
|
+
fullCalendarProps,
|
|
69
|
+
texts
|
|
65
70
|
}) {
|
|
66
71
|
const theme = useTheme();
|
|
67
72
|
const palette = paletteOf(theme);
|
|
68
|
-
const { t } = useToolkitTranslation("calendar");
|
|
73
|
+
const { t } = useToolkitTranslation("calendar", texts);
|
|
69
74
|
const { language } = useToolkitSettings();
|
|
70
75
|
const calendarRef = useRef(null);
|
|
71
76
|
const [title, setTitle] = useState("");
|
|
@@ -103,20 +108,26 @@ function EventCalendar({
|
|
|
103
108
|
/* @__PURE__ */ jsx(Box, { sx: calendarStylesOf, children: /* @__PURE__ */ jsx(
|
|
104
109
|
FullCalendar,
|
|
105
110
|
{
|
|
111
|
+
locale: language === "es" ? spanishLocale : "en",
|
|
112
|
+
firstDay: MONDAY,
|
|
113
|
+
...fullCalendarProps,
|
|
106
114
|
ref: calendarRef,
|
|
107
|
-
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin],
|
|
115
|
+
plugins: [dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, ...fullCalendarProps?.plugins ?? []],
|
|
108
116
|
initialView,
|
|
109
117
|
headerToolbar: false,
|
|
110
|
-
locale: language === "es" ? spanishLocale : "en",
|
|
111
|
-
firstDay: MONDAY,
|
|
112
118
|
height,
|
|
113
119
|
nowIndicator: true,
|
|
114
120
|
dayMaxEvents: DAY_MAX_EVENTS,
|
|
115
121
|
events: coloredEvents,
|
|
116
122
|
eventDisplay: "block",
|
|
123
|
+
eventContent: renderEvent ? (contentInfo) => renderEvent(events.find((event) => event.id === contentInfo.event.id) ?? contentInfo.event) : void 0,
|
|
117
124
|
datesSet: (dateInfo) => {
|
|
118
125
|
setTitle(dateInfo.view.title);
|
|
126
|
+
if (dateInfo.view.type !== view) {
|
|
127
|
+
onViewChange?.(dateInfo.view.type);
|
|
128
|
+
}
|
|
119
129
|
setView(dateInfo.view.type);
|
|
130
|
+
onDatesChange?.({ start: dateInfo.startStr, end: dateInfo.endStr, view: dateInfo.view.type });
|
|
120
131
|
},
|
|
121
132
|
eventClick: onEventClick ? (clickInfo) => onEventClick(clickInfo.event.id) : void 0,
|
|
122
133
|
selectable: Boolean(onDateSelect),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EventCalendar.js","sources":["../../src/calendar/EventCalendar.jsx"],"sourcesContent":["// Un calendario de eventos (@stackcraftmx/calcita/calendar): FullCalendar por dentro, con la barra del framework (anterior y\n// siguiente, «Hoy», el título y la vista con el control segmentado) y los colores del tema. En el idioma de\n// ToolkitProvider, la semana empieza en lunes y marca la hora actual. Las citas van en bloques de su color (el color es\n// información: el tipo de cita), no en puntitos.\n//\n// <EventCalendar events={[{id: '1', title: 'Entrega P-2375', start: '2026-10-20T10:00', end: '2026-10-20T11:00', tone: 'info'}]}\n// onEventClick={openDetail} onDateSelect={openNewEvent} onEventChange={(change) => api.patch(...)} />\n//\n// Arrastrar un evento lo cambia de día u hora: onEventChange recibe lo nuevo y, si truena (el servidor dijo que no), el\n// evento regresa a donde estaba. Sin onEventChange no se puede arrastrar; sin onDateSelect no se pueden elegir días.\n//\n// OJO: las horas se pintan en la zona del navegador (FullCalendar pide un complemento aparte para zonas con nombre).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useRef, useState} from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/daygrid';\nimport interactionPlugin from '@fullcalendar/interaction';\nimport listPlugin from '@fullcalendar/list';\nimport timeGridPlugin from '@fullcalendar/timegrid';\nimport spanishLocale from '@fullcalendar/core/locales/es';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\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 ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';\nimport ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';\n\nimport {paletteOf} from '@/theme/components/paletteOf.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SegmentedControl from '@/ui/filters/SegmentedControl.jsx';\n\n/** Las vistas del calendario (los nombres de FullCalendar). */\nexport const CALENDAR_VIEW = Object.freeze({month: 'dayGridMonth', week: 'timeGridWeek', day: 'timeGridDay', list: 'listWeek'});\n\n// el texto de cada vista en los textos del paquete (calendar.views.*)\nconst VIEW_TEXT_KEY = Object.freeze({\n [CALENDAR_VIEW.month]: 'month', [CALENDAR_VIEW.week]: 'week', [CALENDAR_VIEW.day]: 'day', [CALENDAR_VIEW.list]: 'list',\n});\n// cuántos eventos caben en un día del mes antes del «+2 más»\nconst DAY_MAX_EVENTS = 3;\nconst MONDAY = 1;\n\n/**\n * La primera letra en mayúscula y lo demás como viene: «octubre de 2026» → «Octubre de 2026». OJO: con CSS\n * (text-transform: capitalize) salía «Octubre De 2026»: en español solo va la primera.\n *\n * @param {string} text\n * @returns {string}\n */\nfunction capitalizeFirst(text) {\n return text ? text.charAt(0).toLocaleUpperCase() + text.slice(1) : text;\n}\n\n/**\n * Los colores del calendario sobre el tema (las variables CSS de FullCalendar), en claro y en oscuro.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction calendarStylesOf(theme) {\n const palette = paletteOf(theme);\n return {\n '--fc-border-color': palette.divider,\n '--fc-page-bg-color': 'transparent',\n '--fc-neutral-bg-color': palette.action.hover,\n '--fc-today-bg-color': `color-mix(in srgb, ${palette.primary.main} 8%, transparent)`,\n '--fc-list-event-hover-bg-color': palette.action.hover,\n '--fc-now-indicator-color': palette.error.main,\n '--fc-highlight-color': `color-mix(in srgb, ${palette.primary.main} 14%, transparent)`,\n '& .fc': {fontFamily: 'inherit', fontSize: theme.typography.body2.fontSize},\n // los números de los días y los encabezados son ligas en FullCalendar: sin subrayado y del color del texto\n '& .fc a': {color: 'inherit', textDecoration: 'none'},\n '& .fc-col-header-cell-cushion': {fontWeight: 600, py: 1},\n '& .fc-event': {borderRadius: '6px', px: 0.5},\n // la hora no se encoge: en un día angosto se recortaba («1C» en lugar de «10»); lo que se corta es el nombre\n '& .fc-event-time': {flexShrink: 0, overflow: 'visible'},\n '& .fc-scrollgrid': {borderRadius: '12px', overflow: 'hidden'},\n '& .fc-list': {borderRadius: '12px'},\n };\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, title: string, start: string, end?: string, allDay?: boolean, tone?: string}>} props.events\n * start y end como texto ISO; tone: un color del tema (por omisión primary)\n * @param {string} [props.initialView] una de CALENDAR_VIEW\n * @param {string[]} [props.views] las que se ofrecen en el control segmentado\n * @param {(eventId: string) => void} [props.onEventClick]\n * @param {(range: {start: string, end: string, allDay: boolean}) => void} [props.onDateSelect] al elegir días u horas\n * @param {(change: {id: string, start: string, end: string|null, allDay: boolean}) => Promise<unknown>|void} [props.onEventChange]\n * al arrastrar un evento; si truena, el evento regresa\n * @param {number|'auto'} [props.height]\n * @param {string} props.ariaLabel ya traducido\n */\nexport default function EventCalendar({\n events, initialView = CALENDAR_VIEW.month, views = Object.values(CALENDAR_VIEW), onEventClick, onDateSelect, onEventChange, height = 'auto', ariaLabel,\n}) {\n const theme = useTheme();\n const palette = paletteOf(theme);\n const {t} = useToolkitTranslation('calendar');\n const {language} = useToolkitSettings();\n const calendarRef = useRef(null);\n const [title, setTitle] = useState('');\n const [view, setView] = useState(initialView);\n const calendarApi = () => calendarRef.current?.getApi();\n\n const coloredEvents = events.map((event) => {\n const color = palette[event.tone] ?? palette.primary;\n return {...event, backgroundColor: color.main, borderColor: color.main, textColor: color.contrastText};\n });\n\n const changeEvent = async ({event, revert}) => {\n try {\n await onEventChange({id: event.id, start: event.startStr, end: event.endStr || null, allDay: event.allDay});\n } catch {\n // el servidor dijo que no: el evento regresa a donde estaba (quien llama ya avisó el error)\n revert();\n }\n };\n\n return (\n <Stack spacing={2} role=\"region\" aria-label={ariaLabel}>\n <Stack direction={{xs: 'column', md: 'row'}} spacing={1.5} sx={{alignItems: {md: 'center'}, justifyContent: 'space-between'}}>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <Tooltip title={t('previous')}>\n <IconButton aria-label={t('previous')} onClick={() => calendarApi()?.prev()}><ChevronLeftRoundedIcon /></IconButton>\n </Tooltip>\n <Tooltip title={t('next')}>\n <IconButton aria-label={t('next')} onClick={() => calendarApi()?.next()}><ChevronRightRoundedIcon /></IconButton>\n </Tooltip>\n <Button variant=\"outlined\" size=\"small\" onClick={() => calendarApi()?.today()}>{t('today')}</Button>\n <Typography variant=\"h6\" component=\"h2\" sx={{pl: 1}} aria-live=\"polite\">{capitalizeFirst(title)}</Typography>\n </Stack>\n {views.length > 1 && (\n <SegmentedControl ariaLabel={t('view')} value={view} onChange={(nextView) => calendarApi()?.changeView(nextView)}\n options={views.map((value) => ({value, label: t(`views.${VIEW_TEXT_KEY[value]}`)}))} />\n )}\n </Stack>\n <Box sx={calendarStylesOf}>\n <FullCalendar ref={calendarRef} plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin]} initialView={initialView}\n headerToolbar={false} locale={language === 'es' ? spanishLocale : 'en'} firstDay={MONDAY} height={height}\n nowIndicator dayMaxEvents={DAY_MAX_EVENTS} events={coloredEvents} eventDisplay=\"block\"\n datesSet={(dateInfo) => {\n setTitle(dateInfo.view.title);\n setView(dateInfo.view.type);\n }}\n eventClick={onEventClick ? (clickInfo) => onEventClick(clickInfo.event.id) : undefined}\n selectable={Boolean(onDateSelect)} selectMirror\n select={onDateSelect ? (selectInfo) => {\n onDateSelect({start: selectInfo.startStr, end: selectInfo.endStr, allDay: selectInfo.allDay});\n calendarApi()?.unselect();\n } : undefined}\n editable={Boolean(onEventChange)} eventChange={onEventChange ? changeEvent : undefined} />\n </Box>\n </Stack>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAqCO,MAAM,gBAAgB,OAAO,OAAO,EAAC,OAAO,gBAAgB,MAAM,gBAAgB,KAAK,eAAe,MAAM,WAAA,CAAW;AAG9H,MAAM,gBAAgB,OAAO,OAAO;AAAA,EAChC,CAAC,cAAc,KAAK,GAAG;AAAA,EAAS,CAAC,cAAc,IAAI,GAAG;AAAA,EAAQ,CAAC,cAAc,GAAG,GAAG;AAAA,EAAO,CAAC,cAAc,IAAI,GAAG;AACpH,CAAC;AAED,MAAM,iBAAiB;AACvB,MAAM,SAAS;AASf,SAAS,gBAAgB,MAAM;AAC3B,SAAO,OAAO,KAAK,OAAO,CAAC,EAAE,sBAAsB,KAAK,MAAM,CAAC,IAAI;AACvE;AAQA,SAAS,iBAAiB,OAAO;AAC7B,QAAM,UAAU,UAAU,KAAK;AAC/B,SAAO;AAAA,IACH,qBAAqB,QAAQ;AAAA,IAC7B,sBAAsB;AAAA,IACtB,yBAAyB,QAAQ,OAAO;AAAA,IACxC,uBAAuB,sBAAsB,QAAQ,QAAQ,IAAI;AAAA,IACjE,kCAAkC,QAAQ,OAAO;AAAA,IACjD,4BAA4B,QAAQ,MAAM;AAAA,IAC1C,wBAAwB,sBAAsB,QAAQ,QAAQ,IAAI;AAAA,IAClE,SAAS,EAAC,YAAY,WAAW,UAAU,MAAM,WAAW,MAAM,SAAA;AAAA;AAAA,IAElE,WAAW,EAAC,OAAO,WAAW,gBAAgB,OAAA;AAAA,IAC9C,iCAAiC,EAAC,YAAY,KAAK,IAAI,EAAA;AAAA,IACvD,eAAe,EAAC,cAAc,OAAO,IAAI,IAAA;AAAA;AAAA,IAEzC,oBAAoB,EAAC,YAAY,GAAG,UAAU,UAAA;AAAA,IAC9C,oBAAoB,EAAC,cAAc,QAAQ,UAAU,SAAA;AAAA,IACrD,cAAc,EAAC,cAAc,OAAA;AAAA,EAAM;AAE3C;AAeA,SAAwB,cAAc;AAAA,EAClC;AAAA,EAAQ,cAAc,cAAc;AAAA,EAAO,QAAQ,OAAO,OAAO,aAAa;AAAA,EAAG;AAAA,EAAc;AAAA,EAAc;AAAA,EAAe,SAAS;AAAA,EAAQ;AACjJ,GAAG;AACC,QAAM,QAAQ,SAAA;AACd,QAAM,UAAU,UAAU,KAAK;AAC/B,QAAM,EAAC,EAAA,IAAK,sBAAsB,UAAU;AAC5C,QAAM,EAAC,SAAA,IAAY,mBAAA;AACnB,QAAM,cAAc,OAAO,IAAI;AAC/B,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,MAAM,OAAO,IAAI,SAAS,WAAW;AAC5C,QAAM,cAAc,MAAM,YAAY,SAAS,OAAA;AAE/C,QAAM,gBAAgB,OAAO,IAAI,CAAC,UAAU;AACxC,UAAM,QAAQ,QAAQ,MAAM,IAAI,KAAK,QAAQ;AAC7C,WAAO,EAAC,GAAG,OAAO,iBAAiB,MAAM,MAAM,aAAa,MAAM,MAAM,WAAW,MAAM,aAAA;AAAA,EAC7F,CAAC;AAED,QAAM,cAAc,OAAO,EAAC,OAAO,aAAY;AAC3C,QAAI;AACA,YAAM,cAAc,EAAC,IAAI,MAAM,IAAI,OAAO,MAAM,UAAU,KAAK,MAAM,UAAU,MAAM,QAAQ,MAAM,QAAO;AAAA,IAC9G,QAAQ;AAEJ,aAAA;AAAA,IACJ;AAAA,EACJ;AAEA,8BACK,OAAA,EAAM,SAAS,GAAG,MAAK,UAAS,cAAY,WACzC,UAAA;AAAA,IAAA,qBAAC,SAAM,WAAW,EAAC,IAAI,UAAU,IAAI,SAAQ,SAAS,KAAK,IAAI,EAAC,YAAY,EAAC,IAAI,YAAW,gBAAgB,mBACxG,UAAA;AAAA,MAAA,qBAAC,OAAA,EAAM,WAAU,OAAM,SAAS,GAAG,IAAI,EAAC,YAAY,SAAA,GAChD,UAAA;AAAA,QAAA,oBAAC,SAAA,EAAQ,OAAO,EAAE,UAAU,GACxB,UAAA,oBAAC,YAAA,EAAW,cAAY,EAAE,UAAU,GAAG,SAAS,MAAM,eAAe,KAAA,GAAQ,UAAA,oBAAC,wBAAA,EAAuB,GAAE,EAAA,CAC3G;AAAA,QACA,oBAAC,WAAQ,OAAO,EAAE,MAAM,GACpB,UAAA,oBAAC,cAAW,cAAY,EAAE,MAAM,GAAG,SAAS,MAAM,YAAA,GAAe,QAAQ,UAAA,oBAAC,yBAAA,EAAwB,EAAA,CAAE,EAAA,CACxG;AAAA,QACA,oBAAC,QAAA,EAAO,SAAQ,YAAW,MAAK,SAAQ,SAAS,MAAM,YAAA,GAAe,MAAA,GAAU,UAAA,EAAE,OAAO,GAAE;AAAA,QAC3F,oBAAC,YAAA,EAAW,SAAQ,MAAK,WAAU,MAAK,IAAI,EAAC,IAAI,KAAI,aAAU,UAAU,UAAA,gBAAgB,KAAK,EAAA,CAAE;AAAA,MAAA,GACpG;AAAA,MACC,MAAM,SAAS,KACZ;AAAA,QAAC;AAAA,QAAA;AAAA,UAAiB,WAAW,EAAE,MAAM;AAAA,UAAG,OAAO;AAAA,UAAM,UAAU,CAAC,aAAa,YAAA,GAAe,WAAW,QAAQ;AAAA,UAC7F,SAAS,MAAM,IAAI,CAAC,WAAW,EAAC,OAAO,OAAO,EAAE,SAAS,cAAc,KAAK,CAAC,EAAE,IAAG;AAAA,QAAA;AAAA,MAAA;AAAA,IAAG,GAE/G;AAAA,IACA,oBAAC,KAAA,EAAI,IAAI,kBACL,UAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QAAa,KAAK;AAAA,QAAa,SAAS,CAAC,eAAe,gBAAgB,YAAY,iBAAiB;AAAA,QAAG;AAAA,QAC3F,eAAe;AAAA,QAAO,QAAQ,aAAa,OAAO,gBAAgB;AAAA,QAAM,UAAU;AAAA,QAAQ;AAAA,QAC1F,cAAY;AAAA,QAAC,cAAc;AAAA,QAAgB,QAAQ;AAAA,QAAe,cAAa;AAAA,QAC/E,UAAU,CAAC,aAAa;AACpB,mBAAS,SAAS,KAAK,KAAK;AAC5B,kBAAQ,SAAS,KAAK,IAAI;AAAA,QAC9B;AAAA,QACA,YAAY,eAAe,CAAC,cAAc,aAAa,UAAU,MAAM,EAAE,IAAI;AAAA,QAC7E,YAAY,QAAQ,YAAY;AAAA,QAAG,cAAY;AAAA,QAC/C,QAAQ,eAAe,CAAC,eAAe;AACnC,uBAAa,EAAC,OAAO,WAAW,UAAU,KAAK,WAAW,QAAQ,QAAQ,WAAW,OAAA,CAAO;AAC5F,sBAAA,GAAe,SAAA;AAAA,QACnB,IAAI;AAAA,QACJ,UAAU,QAAQ,aAAa;AAAA,QAAG,aAAa,gBAAgB,cAAc;AAAA,MAAA;AAAA,IAAA,EAAW,CAC1G;AAAA,EAAA,GACJ;AAER;"}
|
|
1
|
+
{"version":3,"file":"EventCalendar.js","sources":["../../src/calendar/EventCalendar.jsx"],"sourcesContent":["// Un calendario de eventos (@stackcraftmx/calcita/calendar): FullCalendar por dentro, con la barra del framework (anterior y\n// siguiente, «Hoy», el título y la vista con el control segmentado) y los colores del tema. En el idioma de\n// ToolkitProvider, la semana empieza en lunes y marca la hora actual. Las citas van en bloques de su color (el color es\n// información: el tipo de cita), no en puntitos.\n//\n// <EventCalendar events={[{id: '1', title: 'Entrega P-2375', start: '2026-10-20T10:00', end: '2026-10-20T11:00', tone: 'info'}]}\n// onEventClick={openDetail} onDateSelect={openNewEvent} onEventChange={(change) => api.patch(...)} />\n//\n// Arrastrar un evento lo cambia de día u hora: onEventChange recibe lo nuevo y, si truena (el servidor dijo que no), el\n// evento regresa a donde estaba. Sin onEventChange no se puede arrastrar; sin onDateSelect no se pueden elegir días.\n//\n// Se extiende sin tocar el framework: onDatesChange avisa el rango que se ve (para que el backend mande solo esas citas),\n// onViewChange la vista, renderEvent pinta cada cita a su manera (recibe la cita tal cual, con sus campos propios),\n// fullCalendarProps pasa lo demás de FullCalendar (horario laboral, días ocultos, horas visibles…) y texts los textos. El\n// detalle de una cita para su ventana: EventDetails.\n//\n// OJO: las horas se pintan en la zona del navegador (FullCalendar pide un complemento aparte para zonas con nombre).\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useRef, useState} from 'react';\nimport FullCalendar from '@fullcalendar/react';\nimport dayGridPlugin from '@fullcalendar/daygrid';\nimport interactionPlugin from '@fullcalendar/interaction';\nimport listPlugin from '@fullcalendar/list';\nimport timeGridPlugin from '@fullcalendar/timegrid';\nimport spanishLocale from '@fullcalendar/core/locales/es';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\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 ChevronLeftRoundedIcon from '@mui/icons-material/ChevronLeftRounded';\nimport ChevronRightRoundedIcon from '@mui/icons-material/ChevronRightRounded';\n\nimport {paletteOf} from '@/theme/components/paletteOf.js';\nimport {useToolkitSettings} from '@/toolkit/toolkitContext.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport SegmentedControl from '@/ui/filters/SegmentedControl.jsx';\n\n/** Las vistas del calendario (los nombres de FullCalendar). */\nexport const CALENDAR_VIEW = Object.freeze({month: 'dayGridMonth', week: 'timeGridWeek', day: 'timeGridDay', list: 'listWeek'});\n\n// el texto de cada vista en los textos del paquete (calendar.views.*)\nconst VIEW_TEXT_KEY = Object.freeze({\n [CALENDAR_VIEW.month]: 'month', [CALENDAR_VIEW.week]: 'week', [CALENDAR_VIEW.day]: 'day', [CALENDAR_VIEW.list]: 'list',\n});\n// cuántos eventos caben en un día del mes antes del «+2 más»\nconst DAY_MAX_EVENTS = 3;\nconst MONDAY = 1;\n\n/**\n * La primera letra en mayúscula y lo demás como viene: «octubre de 2026» → «Octubre de 2026». OJO: con CSS\n * (text-transform: capitalize) salía «Octubre De 2026»: en español solo va la primera.\n *\n * @param {string} text\n * @returns {string}\n */\nfunction capitalizeFirst(text) {\n return text ? text.charAt(0).toLocaleUpperCase() + text.slice(1) : text;\n}\n\n/**\n * Los colores del calendario sobre el tema (las variables CSS de FullCalendar), en claro y en oscuro.\n *\n * @param {object} theme\n * @returns {object}\n */\nfunction calendarStylesOf(theme) {\n const palette = paletteOf(theme);\n return {\n '--fc-border-color': palette.divider,\n '--fc-page-bg-color': 'transparent',\n '--fc-neutral-bg-color': palette.action.hover,\n '--fc-today-bg-color': `color-mix(in srgb, ${palette.primary.main} 8%, transparent)`,\n '--fc-list-event-hover-bg-color': palette.action.hover,\n '--fc-now-indicator-color': palette.error.main,\n '--fc-highlight-color': `color-mix(in srgb, ${palette.primary.main} 14%, transparent)`,\n '& .fc': {fontFamily: 'inherit', fontSize: theme.typography.body2.fontSize},\n // los números de los días y los encabezados son ligas en FullCalendar: sin subrayado y del color del texto\n '& .fc a': {color: 'inherit', textDecoration: 'none'},\n '& .fc-col-header-cell-cushion': {fontWeight: 600, py: 1},\n '& .fc-event': {borderRadius: '6px', px: 0.5},\n // la hora no se encoge: en un día angosto se recortaba («1C» en lugar de «10»); lo que se corta es el nombre\n '& .fc-event-time': {flexShrink: 0, overflow: 'visible'},\n '& .fc-scrollgrid': {borderRadius: '12px', overflow: 'hidden'},\n '& .fc-list': {borderRadius: '12px'},\n };\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, title: string, start: string, end?: string, allDay?: boolean, tone?: string}>} props.events\n * start y end como texto ISO; tone: un color del tema (por omisión primary)\n * @param {string} [props.initialView] una de CALENDAR_VIEW\n * @param {string[]} [props.views] las que se ofrecen en el control segmentado\n * @param {(eventId: string) => void} [props.onEventClick]\n * @param {(range: {start: string, end: string, allDay: boolean}) => void} [props.onDateSelect] al elegir días u horas\n * @param {(change: {id: string, start: string, end: string|null, allDay: boolean}) => Promise<unknown>|void} [props.onEventChange]\n * al arrastrar un evento; si truena, el evento regresa\n * @param {number|'auto'} [props.height]\n * @param {string} props.ariaLabel ya traducido\n * @param {(range: {start: string, end: string, view: string}) => void} [props.onDatesChange] cada que cambia lo que se ve (al\n * abrir, al ir al anterior o siguiente, al cambiar de vista): para pedir al backend solo las citas de ese rango\n * @param {(view: string) => void} [props.onViewChange] al cambiar de vista (CALENDAR_VIEW)\n * @param {(event: any) => import('react').ReactNode} [props.renderEvent] lo de adentro de cada cita (recibe la cita de events\n * tal cual, con sus campos propios)\n * @param {object} [props.fullCalendarProps] lo demás de FullCalendar (businessHours, hiddenDays, slotMinTime, locale…); lo\n * que el calendario controla (las citas, la barra, los eventos de arriba) no se reemplaza desde aquí\n * @param {object} [props.texts] los textos que cambian, con la forma de `calendar` en los textos del paquete\n */\nexport default function EventCalendar({\n events, initialView = CALENDAR_VIEW.month, views = Object.values(CALENDAR_VIEW), onEventClick, onDateSelect, onEventChange, height = 'auto', ariaLabel,\n onDatesChange, onViewChange, renderEvent, fullCalendarProps, texts,\n}) {\n const theme = useTheme();\n const palette = paletteOf(theme);\n const {t} = useToolkitTranslation('calendar', texts);\n const {language} = useToolkitSettings();\n const calendarRef = useRef(null);\n const [title, setTitle] = useState('');\n const [view, setView] = useState(initialView);\n const calendarApi = () => calendarRef.current?.getApi();\n\n const coloredEvents = events.map((event) => {\n const color = palette[event.tone] ?? palette.primary;\n return {...event, backgroundColor: color.main, borderColor: color.main, textColor: color.contrastText};\n });\n\n const changeEvent = async ({event, revert}) => {\n try {\n await onEventChange({id: event.id, start: event.startStr, end: event.endStr || null, allDay: event.allDay});\n } catch {\n // el servidor dijo que no: el evento regresa a donde estaba (quien llama ya avisó el error)\n revert();\n }\n };\n\n return (\n <Stack spacing={2} role=\"region\" aria-label={ariaLabel}>\n <Stack direction={{xs: 'column', md: 'row'}} spacing={1.5} sx={{alignItems: {md: 'center'}, justifyContent: 'space-between'}}>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <Tooltip title={t('previous')}>\n <IconButton aria-label={t('previous')} onClick={() => calendarApi()?.prev()}><ChevronLeftRoundedIcon /></IconButton>\n </Tooltip>\n <Tooltip title={t('next')}>\n <IconButton aria-label={t('next')} onClick={() => calendarApi()?.next()}><ChevronRightRoundedIcon /></IconButton>\n </Tooltip>\n <Button variant=\"outlined\" size=\"small\" onClick={() => calendarApi()?.today()}>{t('today')}</Button>\n <Typography variant=\"h6\" component=\"h2\" sx={{pl: 1}} aria-live=\"polite\">{capitalizeFirst(title)}</Typography>\n </Stack>\n {views.length > 1 && (\n <SegmentedControl ariaLabel={t('view')} value={view} onChange={(nextView) => calendarApi()?.changeView(nextView)}\n options={views.map((value) => ({value, label: t(`views.${VIEW_TEXT_KEY[value]}`)}))} />\n )}\n </Stack>\n <Box sx={calendarStylesOf}>\n {/* primero lo del proyecto (fullCalendarProps); lo de abajo es lo que el calendario controla y le gana */}\n <FullCalendar locale={language === 'es' ? spanishLocale : 'en'} firstDay={MONDAY} {...fullCalendarProps}\n ref={calendarRef} plugins={[dayGridPlugin, timeGridPlugin, listPlugin, interactionPlugin, ...(fullCalendarProps?.plugins ?? [])]}\n initialView={initialView} headerToolbar={false} height={height}\n nowIndicator dayMaxEvents={DAY_MAX_EVENTS} events={coloredEvents} eventDisplay=\"block\"\n eventContent={renderEvent ? (contentInfo) => renderEvent(events.find((event) => event.id === contentInfo.event.id) ?? contentInfo.event) : undefined}\n datesSet={(dateInfo) => {\n setTitle(dateInfo.view.title);\n if (dateInfo.view.type !== view) {\n onViewChange?.(dateInfo.view.type);\n }\n setView(dateInfo.view.type);\n onDatesChange?.({start: dateInfo.startStr, end: dateInfo.endStr, view: dateInfo.view.type});\n }}\n eventClick={onEventClick ? (clickInfo) => onEventClick(clickInfo.event.id) : undefined}\n selectable={Boolean(onDateSelect)} selectMirror\n select={onDateSelect ? (selectInfo) => {\n onDateSelect({start: selectInfo.startStr, end: selectInfo.endStr, allDay: selectInfo.allDay});\n calendarApi()?.unselect();\n } : undefined}\n editable={Boolean(onEventChange)} eventChange={onEventChange ? changeEvent : undefined} />\n </Box>\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
3
|
+
import Avatar from "@mui/material/Avatar";
|
|
4
|
+
import Box from "@mui/material/Box";
|
|
5
|
+
import Button from "@mui/material/Button";
|
|
6
|
+
import Chip from "@mui/material/Chip";
|
|
7
|
+
import Stack from "@mui/material/Stack";
|
|
8
|
+
import Typography from "@mui/material/Typography";
|
|
9
|
+
import AccessTimeRoundedIcon from "@mui/icons-material/AccessTimeRounded";
|
|
10
|
+
import NotesRoundedIcon from "@mui/icons-material/NotesRounded";
|
|
11
|
+
import NotificationsNoneRoundedIcon from "@mui/icons-material/NotificationsNoneRounded";
|
|
12
|
+
import PeopleOutlineRoundedIcon from "@mui/icons-material/PeopleOutlineRounded";
|
|
13
|
+
import PlaceOutlinedIcon from "@mui/icons-material/PlaceOutlined";
|
|
14
|
+
import VideocamOutlinedIcon from "@mui/icons-material/VideocamOutlined";
|
|
15
|
+
import { initialsOf } from "../appShell/menuItemText.js";
|
|
16
|
+
import { reminderOf, isOpenableMeetingUrl, REMINDER_UNIT } from "./calendarEvents.js";
|
|
17
|
+
import { useFormatters } from "../toolkit/useFormatters.js";
|
|
18
|
+
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
19
|
+
const ATTENDEE_RESPONSE = Object.freeze({ accepted: "accepted", declined: "declined", pending: "pending" });
|
|
20
|
+
const RESPONSE_COLOR = Object.freeze({
|
|
21
|
+
[ATTENDEE_RESPONSE.accepted]: "success",
|
|
22
|
+
[ATTENDEE_RESPONSE.declined]: "error",
|
|
23
|
+
[ATTENDEE_RESPONSE.pending]: "default"
|
|
24
|
+
});
|
|
25
|
+
const WHOLE_DAY_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
26
|
+
const ICON_SIZE = 20;
|
|
27
|
+
function DetailRow({ Icon, label, children }) {
|
|
28
|
+
return /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.5, sx: { alignItems: "flex-start" }, "aria-label": label, role: "group", children: [
|
|
29
|
+
/* @__PURE__ */ jsx(Icon, { sx: { fontSize: ICON_SIZE, color: "text.secondary", mt: 0.25 }, "aria-hidden": "true" }),
|
|
30
|
+
/* @__PURE__ */ jsx(Box, { sx: { minWidth: 0, flexGrow: 1 }, children })
|
|
31
|
+
] });
|
|
32
|
+
}
|
|
33
|
+
function EventDetails({ event, actions, titleId, texts }) {
|
|
34
|
+
const { t } = useToolkitTranslation("calendar", texts);
|
|
35
|
+
const { formatDate, formatTime } = useFormatters();
|
|
36
|
+
const isWholeDay = event.allDay || WHOLE_DAY_PATTERN.test(event.start);
|
|
37
|
+
let when = formatDate(event.start);
|
|
38
|
+
if (!isWholeDay) {
|
|
39
|
+
when = t("details.timeRange", { date: formatDate(event.start), start: formatTime(event.start), end: event.end ? formatTime(event.end) : "" });
|
|
40
|
+
}
|
|
41
|
+
const reminder = reminderOf(event.reminderMinutes);
|
|
42
|
+
const canJoin = isOpenableMeetingUrl(event.meetingUrl);
|
|
43
|
+
return /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
|
|
44
|
+
/* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1.5, sx: { alignItems: "center" }, children: [
|
|
45
|
+
/* @__PURE__ */ jsx(Box, { "aria-hidden": "true", sx: { width: 12, height: 12, borderRadius: "4px", flexShrink: 0, bgcolor: `${event.tone ?? "primary"}.main` } }),
|
|
46
|
+
/* @__PURE__ */ jsx(Typography, { id: titleId, variant: "h6", component: "h2", children: event.title })
|
|
47
|
+
] }),
|
|
48
|
+
/* @__PURE__ */ jsx(DetailRow, { Icon: AccessTimeRoundedIcon, label: t("details.when"), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: isWholeDay ? t("details.allDay", { date: when }) : when }) }),
|
|
49
|
+
event.location && /* @__PURE__ */ jsx(DetailRow, { Icon: PlaceOutlinedIcon, label: t("details.location"), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: event.location }) }),
|
|
50
|
+
event.meetingUrl && /* @__PURE__ */ jsx(DetailRow, { Icon: VideocamOutlinedIcon, label: t("details.meeting"), children: /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center", flexWrap: "wrap" }, useFlexGap: true, children: [
|
|
51
|
+
canJoin && /* @__PURE__ */ jsx(Button, { variant: "contained", size: "small", href: event.meetingUrl, target: "_blank", rel: "noreferrer", children: t("details.join") }),
|
|
52
|
+
/* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", sx: { wordBreak: "break-all" }, children: event.meetingUrl })
|
|
53
|
+
] }) }),
|
|
54
|
+
event.attendees?.length > 0 && /* @__PURE__ */ jsxs(DetailRow, { Icon: PeopleOutlineRoundedIcon, label: t("details.attendees"), children: [
|
|
55
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { mb: 0.75 }, children: t("details.attendeesCount", { count: event.attendees.length }) }),
|
|
56
|
+
/* @__PURE__ */ jsx(Stack, { spacing: 0.75, children: event.attendees.map((attendee) => {
|
|
57
|
+
const response = attendee.response ?? ATTENDEE_RESPONSE.pending;
|
|
58
|
+
return /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { alignItems: "center" }, children: [
|
|
59
|
+
/* @__PURE__ */ jsx(Avatar, { sx: { width: 24, height: 24, fontSize: 11 }, children: initialsOf(attendee.name) }),
|
|
60
|
+
/* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { flexGrow: 1, minWidth: 0 }, noWrap: true, children: attendee.name }),
|
|
61
|
+
/* @__PURE__ */ jsx(Chip, { size: "small", variant: "outlined", color: RESPONSE_COLOR[response] ?? "default", label: t(`details.response.${response}`) })
|
|
62
|
+
] }, attendee.id);
|
|
63
|
+
}) })
|
|
64
|
+
] }),
|
|
65
|
+
reminder.unit !== REMINDER_UNIT.none && /* @__PURE__ */ jsx(DetailRow, { Icon: NotificationsNoneRoundedIcon, label: t("details.reminder"), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", children: t(`details.reminderUnit.${reminder.unit}`, { count: reminder.count }) }) }),
|
|
66
|
+
event.description && /* @__PURE__ */ jsx(DetailRow, { Icon: NotesRoundedIcon, label: t("details.notes"), children: /* @__PURE__ */ jsx(Typography, { variant: "body2", sx: { whiteSpace: "pre-wrap" }, children: event.description }) }),
|
|
67
|
+
actions && /* @__PURE__ */ jsx(Stack, { direction: "row", spacing: 1, useFlexGap: true, sx: { justifyContent: "flex-end", flexWrap: "wrap" }, children: actions })
|
|
68
|
+
] });
|
|
69
|
+
}
|
|
70
|
+
export {
|
|
71
|
+
ATTENDEE_RESPONSE,
|
|
72
|
+
EventDetails as default
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=EventDetails.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"EventDetails.js","sources":["../../src/calendar/EventDetails.jsx"],"sourcesContent":["// El detalle de una cita, para la ventana que se abre al picarla en el calendario: su título con el color de su tipo,\n// cuándo (el día y las horas), dónde, la liga de la reunión con «Unirse», los invitados con su respuesta, el recordatorio y\n// las notas. Pinta lo que la cita traiga; lo que no trae no ocupa lugar.\n//\n// <EventDetails event={{title: 'Junta de cierre', start: '2026-10-20T10:00', end: '2026-10-20T11:00', location: 'Sala 2',\n// meetingUrl: 'https://meet.example.com/abc', attendees: [{id: 'ana', name: 'Ana', response: 'accepted'}],\n// reminderMinutes: 15}}\n// actions={<Button onClick={edit}>Editar</Button>} />\n//\n// No llama a ningún servidor ni abre la reunión por su cuenta: «Unirse» es una liga (solo http o https) y lo demás lo pone la\n// pantalla en actions. Sus textos se cambian con texts (la forma de `calendar` en los textos del paquete).\n//\n// Genérico del paquete: function (regla de la casa).\nimport Avatar from '@mui/material/Avatar';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Chip from '@mui/material/Chip';\nimport Stack from '@mui/material/Stack';\nimport Typography from '@mui/material/Typography';\nimport AccessTimeRoundedIcon from '@mui/icons-material/AccessTimeRounded';\nimport NotesRoundedIcon from '@mui/icons-material/NotesRounded';\nimport NotificationsNoneRoundedIcon from '@mui/icons-material/NotificationsNoneRounded';\nimport PeopleOutlineRoundedIcon from '@mui/icons-material/PeopleOutlineRounded';\nimport PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';\nimport VideocamOutlinedIcon from '@mui/icons-material/VideocamOutlined';\n\nimport {initialsOf} from '@/appShell/menuItemText.js';\nimport {REMINDER_UNIT, isOpenableMeetingUrl, reminderOf} from '@/calendar/calendarEvents.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\n/** Las respuestas de un invitado. */\nexport const ATTENDEE_RESPONSE = Object.freeze({accepted: 'accepted', declined: 'declined', pending: 'pending'});\n// el color de cada respuesta (el color es información: verde va, rojo no va, gris no ha contestado)\nconst RESPONSE_COLOR = Object.freeze({\n [ATTENDEE_RESPONSE.accepted]: 'success', [ATTENDEE_RESPONSE.declined]: 'error', [ATTENDEE_RESPONSE.pending]: 'default',\n});\n// un día completo viene sin hora («2026-10-20»)\nconst WHOLE_DAY_PATTERN = /^\\d{4}-\\d{2}-\\d{2}$/;\nconst ICON_SIZE = 20;\n\n/** Un renglón del detalle: su ícono a la izquierda y lo de adentro. */\nfunction DetailRow({Icon, label, children}) {\n return (\n <Stack direction=\"row\" spacing={1.5} sx={{alignItems: 'flex-start'}} aria-label={label} role=\"group\">\n <Icon sx={{fontSize: ICON_SIZE, color: 'text.secondary', mt: 0.25}} aria-hidden=\"true\" />\n <Box sx={{minWidth: 0, flexGrow: 1}}>{children}</Box>\n </Stack>\n );\n}\n\n/**\n * @param {object} props\n * @param {{title: string, start: string, end?: string|null, allDay?: boolean, tone?: string, location?: string, meetingUrl?: string, attendees?: Array<{id: string, name: string, email?: string, response?: 'accepted'|'declined'|'pending'}>, reminderMinutes?: number|null, description?: string}} props.event\n * start y end como texto ISO (o AAAA-MM-DD un día completo); tone: el color de su tipo\n * @param {import('react').ReactNode} [props.actions] botones de la pantalla (editar, borrar, responder…)\n * @param {string} [props.titleId] el id del título: la ventana que lo contiene se nombra con él (aria-labelledby)\n * @param {object} [props.texts] los textos que cambian, con la forma de `calendar` en los textos del paquete\n */\nexport default function EventDetails({event, actions, titleId, texts}) {\n const {t} = useToolkitTranslation('calendar', texts);\n const {formatDate, formatTime} = useFormatters();\n const isWholeDay = event.allDay || WHOLE_DAY_PATTERN.test(event.start);\n let when = formatDate(event.start);\n if (!isWholeDay) {\n when = t('details.timeRange', {date: formatDate(event.start), start: formatTime(event.start), end: event.end ? formatTime(event.end) : ''});\n }\n const reminder = reminderOf(event.reminderMinutes);\n const canJoin = isOpenableMeetingUrl(event.meetingUrl);\n\n return (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1.5} sx={{alignItems: 'center'}}>\n <Box aria-hidden=\"true\" sx={{width: 12, height: 12, borderRadius: '4px', flexShrink: 0, bgcolor: `${event.tone ?? 'primary'}.main`}} />\n <Typography id={titleId} variant=\"h6\" component=\"h2\">{event.title}</Typography>\n </Stack>\n <DetailRow Icon={AccessTimeRoundedIcon} label={t('details.when')}>\n <Typography variant=\"body2\">{isWholeDay ? t('details.allDay', {date: when}) : when}</Typography>\n </DetailRow>\n {event.location && (\n <DetailRow Icon={PlaceOutlinedIcon} label={t('details.location')}>\n <Typography variant=\"body2\">{event.location}</Typography>\n </DetailRow>\n )}\n {event.meetingUrl && (\n <DetailRow Icon={VideocamOutlinedIcon} label={t('details.meeting')}>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center', flexWrap: 'wrap'}} useFlexGap>\n {canJoin && (\n <Button variant=\"contained\" size=\"small\" href={event.meetingUrl} target=\"_blank\" rel=\"noreferrer\">{t('details.join')}</Button>\n )}\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{wordBreak: 'break-all'}}>{event.meetingUrl}</Typography>\n </Stack>\n </DetailRow>\n )}\n {event.attendees?.length > 0 && (\n <DetailRow Icon={PeopleOutlineRoundedIcon} label={t('details.attendees')}>\n <Typography variant=\"body2\" sx={{mb: 0.75}}>{t('details.attendeesCount', {count: event.attendees.length})}</Typography>\n <Stack spacing={0.75}>\n {event.attendees.map((attendee) => {\n const response = attendee.response ?? ATTENDEE_RESPONSE.pending;\n return (\n <Stack key={attendee.id} direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <Avatar sx={{width: 24, height: 24, fontSize: 11}}>{initialsOf(attendee.name)}</Avatar>\n <Typography variant=\"body2\" sx={{flexGrow: 1, minWidth: 0}} noWrap>{attendee.name}</Typography>\n <Chip size=\"small\" variant=\"outlined\" color={RESPONSE_COLOR[response] ?? 'default'} label={t(`details.response.${response}`)} />\n </Stack>\n );\n })}\n </Stack>\n </DetailRow>\n )}\n {reminder.unit !== REMINDER_UNIT.none && (\n <DetailRow Icon={NotificationsNoneRoundedIcon} label={t('details.reminder')}>\n <Typography variant=\"body2\">{t(`details.reminderUnit.${reminder.unit}`, {count: reminder.count})}</Typography>\n </DetailRow>\n )}\n {event.description && (\n <DetailRow Icon={NotesRoundedIcon} label={t('details.notes')}>\n <Typography variant=\"body2\" sx={{whiteSpace: 'pre-wrap'}}>{event.description}</Typography>\n </DetailRow>\n )}\n {actions && <Stack direction=\"row\" spacing={1} useFlexGap sx={{justifyContent: 'flex-end', flexWrap: 'wrap'}}>{actions}</Stack>}\n </Stack>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const MINUTES_PER_HOUR = 60;
|
|
2
|
+
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
|
3
|
+
const REMINDER_UNIT = Object.freeze({ none: "none", atStart: "atStart", minutes: "minutes", hours: "hours", days: "days" });
|
|
4
|
+
function reminderOf(minutes) {
|
|
5
|
+
if (minutes === null || minutes === void 0 || minutes < 0) {
|
|
6
|
+
return { unit: REMINDER_UNIT.none, count: 0 };
|
|
7
|
+
}
|
|
8
|
+
if (minutes === 0) {
|
|
9
|
+
return { unit: REMINDER_UNIT.atStart, count: 0 };
|
|
10
|
+
}
|
|
11
|
+
if (minutes % MINUTES_PER_DAY === 0) {
|
|
12
|
+
return { unit: REMINDER_UNIT.days, count: minutes / MINUTES_PER_DAY };
|
|
13
|
+
}
|
|
14
|
+
if (minutes % MINUTES_PER_HOUR === 0) {
|
|
15
|
+
return { unit: REMINDER_UNIT.hours, count: minutes / MINUTES_PER_HOUR };
|
|
16
|
+
}
|
|
17
|
+
return { unit: REMINDER_UNIT.minutes, count: minutes };
|
|
18
|
+
}
|
|
19
|
+
function isOpenableMeetingUrl(url) {
|
|
20
|
+
try {
|
|
21
|
+
return ["http:", "https:"].includes(new URL(url).protocol);
|
|
22
|
+
} catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
REMINDER_UNIT,
|
|
28
|
+
isOpenableMeetingUrl,
|
|
29
|
+
reminderOf
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=calendarEvents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendarEvents.js","sources":["../../src/calendar/calendarEvents.js"],"sourcesContent":["// Lo que el detalle de una cita decide sin pintar nada: cómo se dice su recordatorio (minutos, horas o días antes) y si su\n// liga de reunión es una dirección web de verdad. Funciones puras: se prueban sin navegador (calendarEvents.test.js).\n//\n// Funciones puras, no renderizan: function (regla de la casa).\n\nconst MINUTES_PER_HOUR = 60;\nconst MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;\n\n/** La unidad del recordatorio (la llave de su texto: calendar.reminder.<unidad>). */\nexport const REMINDER_UNIT = Object.freeze({none: 'none', atStart: 'atStart', minutes: 'minutes', hours: 'hours', days: 'days'});\n\n/**\n * Cómo se dice un recordatorio: «sin recordatorio», «al empezar», «15 minutos antes», «2 horas antes», «1 día antes». Usa la\n * unidad más grande que lo dice exacto (90 minutos se quedan en minutos).\n *\n * @param {number|null|undefined} minutes cuántos minutos antes; null sin recordatorio\n * @returns {{unit: string, count: number}}\n */\nexport function reminderOf(minutes) {\n if (minutes === null || minutes === undefined || minutes < 0) {\n return {unit: REMINDER_UNIT.none, count: 0};\n }\n if (minutes === 0) {\n return {unit: REMINDER_UNIT.atStart, count: 0};\n }\n if (minutes % MINUTES_PER_DAY === 0) {\n return {unit: REMINDER_UNIT.days, count: minutes / MINUTES_PER_DAY};\n }\n if (minutes % MINUTES_PER_HOUR === 0) {\n return {unit: REMINDER_UNIT.hours, count: minutes / MINUTES_PER_HOUR};\n }\n return {unit: REMINDER_UNIT.minutes, count: minutes};\n}\n\n/**\n * ¿La liga de reunión se puede abrir? Solo http y https: una liga «javascript:» o con otro esquema no se ofrece para abrir\n * (la pinta quien la escribió, pero nadie le da clic a algo raro desde la app).\n *\n * @param {string|null|undefined} url\n * @returns {boolean}\n */\nexport function isOpenableMeetingUrl(url) {\n try {\n return ['http:', 'https:'].includes(new URL(url).protocol);\n } catch {\n return false;\n }\n}\n"],"names":[],"mappings":"AAKA,MAAM,mBAAmB;AACzB,MAAM,kBAAkB,KAAK;AAGjB,MAAC,gBAAgB,OAAO,OAAO,EAAC,MAAM,QAAQ,SAAS,WAAW,SAAS,WAAW,OAAO,SAAS,MAAM,OAAM,CAAC;AASxH,SAAS,WAAW,SAAS;AAChC,MAAI,YAAY,QAAQ,YAAY,UAAa,UAAU,GAAG;AAC1D,WAAO,EAAC,MAAM,cAAc,MAAM,OAAO,EAAC;AAAA,EAC9C;AACA,MAAI,YAAY,GAAG;AACf,WAAO,EAAC,MAAM,cAAc,SAAS,OAAO,EAAC;AAAA,EACjD;AACA,MAAI,UAAU,oBAAoB,GAAG;AACjC,WAAO,EAAC,MAAM,cAAc,MAAM,OAAO,UAAU,gBAAe;AAAA,EACtE;AACA,MAAI,UAAU,qBAAqB,GAAG;AAClC,WAAO,EAAC,MAAM,cAAc,OAAO,OAAO,UAAU,iBAAgB;AAAA,EACxE;AACA,SAAO,EAAC,MAAM,cAAc,SAAS,OAAO,QAAO;AACvD;AASO,SAAS,qBAAqB,KAAK;AACtC,MAAI;AACA,WAAO,CAAC,SAAS,QAAQ,EAAE,SAAS,IAAI,IAAI,GAAG,EAAE,QAAQ;AAAA,EAC7D,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;"}
|
package/dist/calendar.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { CALENDAR_VIEW, default as default2 } from "./calendar/EventCalendar.js";
|
|
2
|
+
import { ATTENDEE_RESPONSE, default as default3 } from "./calendar/EventDetails.js";
|
|
3
|
+
import { REMINDER_UNIT, isOpenableMeetingUrl, reminderOf } from "./calendar/calendarEvents.js";
|
|
2
4
|
export {
|
|
5
|
+
ATTENDEE_RESPONSE,
|
|
3
6
|
CALENDAR_VIEW,
|
|
4
|
-
default2 as EventCalendar
|
|
7
|
+
default2 as EventCalendar,
|
|
8
|
+
default3 as EventDetails,
|
|
9
|
+
REMINDER_UNIT,
|
|
10
|
+
isOpenableMeetingUrl,
|
|
11
|
+
reminderOf
|
|
5
12
|
};
|
|
6
13
|
//# sourceMappingURL=calendar.js.map
|
package/dist/calendar.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"calendar.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"calendar.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
package/dist/charts.js
CHANGED
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { default as default2 } from "./ui/metrics/BarList.js";
|
|
2
2
|
import { default as default3 } from "./ui/metrics/ColumnChart.js";
|
|
3
|
+
import { default as default4 } from "./ui/metrics/LineChart.js";
|
|
4
|
+
import { default as default5 } from "./ui/metrics/ChartTooltip.js";
|
|
5
|
+
import { CHART_LINE_DASHES, CHART_SERIES_TONES } from "./ui/metrics/chartParts.js";
|
|
6
|
+
import { CHART_VIEW } from "./ui/metrics/TableToggle.js";
|
|
3
7
|
export {
|
|
4
8
|
default2 as BarList,
|
|
5
|
-
|
|
9
|
+
CHART_LINE_DASHES,
|
|
10
|
+
CHART_SERIES_TONES,
|
|
11
|
+
CHART_VIEW,
|
|
12
|
+
default5 as ChartTooltip,
|
|
13
|
+
default3 as ColumnChart,
|
|
14
|
+
default4 as LineChart
|
|
6
15
|
};
|
|
7
16
|
//# sourceMappingURL=charts.js.map
|
package/dist/charts.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"charts.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"charts.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;"}
|
|
@@ -32,9 +32,10 @@ function DateInput({
|
|
|
32
32
|
disablePast = false,
|
|
33
33
|
openTo = "day",
|
|
34
34
|
disabled = false,
|
|
35
|
-
inputRef
|
|
35
|
+
inputRef,
|
|
36
|
+
texts
|
|
36
37
|
}) {
|
|
37
|
-
const { t, language } = useToolkitTranslation("common");
|
|
38
|
+
const { t, language } = useToolkitTranslation("common", texts);
|
|
38
39
|
const generatedId = useId();
|
|
39
40
|
const inputId = `${name ?? "date"}-${generatedId}`;
|
|
40
41
|
const errorText = translateValidationMessage(t, error);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DateInput.js","sources":["../../src/datePickers/DateInput.jsx"],"sourcesContent":["// Campo de FECHA suelto con el DatePicker de MUI X (@stackcraftmx/calcita/date-pickers), con la etiqueta arriba como los\n// demás campos del paquete.\n//\n// Al formulario (y al servidor) viaja la fecha de calendario 'AAAA-MM-DD' en texto; en pantalla se ve DD/MM/AAAA en español\n// y MM/DD/AAAA en inglés, con el calendario y sus textos en el idioma activo. '' = sin fecha. Una fecha de calendario no\n// tiene hora ni zona: nunca se pasa por Date (que la movería de día según la zona).\n//\n// Cada campo trae su LocalizationProvider con dayjs: la pantalla no tiene que poner nada alrededor.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/es';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs';\nimport {DatePicker} from '@mui/x-date-pickers/DatePicker';\nimport {LocalizationProvider} from '@mui/x-date-pickers/LocalizationProvider';\nimport {enUS, esES} from '@mui/x-date-pickers/locales';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\n\n/** Cómo viaja una fecha de calendario. */\nexport const CALENDAR_DATE_FORMAT = 'YYYY-MM-DD';\n\n// cómo se ve y con qué textos sale el calendario en cada idioma\nconst DATE_LOOK_BY_LANGUAGE = Object.freeze({\n es: {adapterLocale: 'es', displayFormat: 'DD/MM/YYYY', localeText: esES.components.MuiLocalizationProvider.defaultProps.localeText},\n en: {adapterLocale: 'en', displayFormat: 'MM/DD/YYYY', localeText: enUS.components.MuiLocalizationProvider.defaultProps.localeText},\n});\n\n/**\n * Una fecha de calendario 'AAAA-MM-DD' como la quiere el DatePicker; null si está vacía.\n *\n * @param {string|null|undefined} calendarDate\n * @returns {import('dayjs').Dayjs|null}\n */\nfunction pickerValueOf(calendarDate) {\n // dayjs lee 'AAAA-MM-DD' como día local (a diferencia de new Date, que lo toma en UTC y lo mueve de día)\n return calendarDate ? dayjs(calendarDate) : null;\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value 'AAAA-MM-DD' o ''\n * @param {(value: string) => void} props.onChange recibe 'AAAA-MM-DD' ('' al borrar o con una fecha a medias)\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {string} [props.minDate] 'AAAA-MM-DD'\n * @param {string} [props.maxDate] 'AAAA-MM-DD'\n * @param {boolean} [props.disableFuture] p. ej. una fecha de nacimiento\n * @param {boolean} [props.disablePast] p. ej. una cita\n * @param {'day'|'month'|'year'} [props.openTo] por dónde abre el calendario (year para un nacimiento)\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n */\nexport default function DateInput({\n name, label, value, onChange, onBlur, error, helperText = ' ', minDate, maxDate, disableFuture = false, disablePast = false,\n openTo = 'day', disabled = false, inputRef,\n}) {\n const {t, language} = useToolkitTranslation('common');\n const generatedId = useId();\n const inputId = `${name ?? 'date'}-${generatedId}`;\n const errorText = translateValidationMessage(t, error);\n const look = DATE_LOOK_BY_LANGUAGE[language] ?? DATE_LOOK_BY_LANGUAGE.es;\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={look.adapterLocale} localeText={look.localeText}>\n <DatePicker\n format={look.displayFormat}\n value={pickerValueOf(value)}\n minDate={pickerValueOf(minDate) ?? undefined}\n maxDate={pickerValueOf(maxDate) ?? undefined}\n disableFuture={disableFuture}\n disablePast={disablePast}\n disabled={disabled}\n openTo={openTo}\n views={openTo === 'year' ? ['year', 'month', 'day'] : undefined}\n inputRef={inputRef}\n // una fecha a medias (25/1_/____) todavía no es fecha: viaja como vacía hasta que se complete\n onChange={(pickedDate) => onChange(pickedDate?.isValid() ? pickedDate.format(CALENDAR_DATE_FORMAT) : '')}\n onClose={onBlur}\n slotProps={{\n textField: {\n id: inputId, name, size: 'small', fullWidth: true, onBlur, error: Boolean(errorText), helperText: errorText ?? helperText,\n },\n }}\n />\n </LocalizationProvider>\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAuBO,MAAM,uBAAuB;AAGpC,MAAM,wBAAwB,OAAO,OAAO;AAAA,EACxC,IAAI,EAAC,eAAe,MAAM,eAAe,cAAc,YAAY,KAAK,WAAW,wBAAwB,aAAa,WAAA;AAAA,EACxH,IAAI,EAAC,eAAe,MAAM,eAAe,cAAc,YAAY,KAAK,WAAW,wBAAwB,aAAa,WAAA;AAC5H,CAAC;AAQD,SAAS,cAAc,cAAc;AAEjC,SAAO,eAAe,MAAM,YAAY,IAAI;AAChD;
|
|
1
|
+
{"version":3,"file":"DateInput.js","sources":["../../src/datePickers/DateInput.jsx"],"sourcesContent":["// Campo de FECHA suelto con el DatePicker de MUI X (@stackcraftmx/calcita/date-pickers), con la etiqueta arriba como los\n// demás campos del paquete.\n//\n// Al formulario (y al servidor) viaja la fecha de calendario 'AAAA-MM-DD' en texto; en pantalla se ve DD/MM/AAAA en español\n// y MM/DD/AAAA en inglés, con el calendario y sus textos en el idioma activo. '' = sin fecha. Una fecha de calendario no\n// tiene hora ni zona: nunca se pasa por Date (que la movería de día según la zona).\n//\n// Cada campo trae su LocalizationProvider con dayjs: la pantalla no tiene que poner nada alrededor.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useId} from 'react';\nimport dayjs from 'dayjs';\nimport 'dayjs/locale/es';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {AdapterDayjs} from '@mui/x-date-pickers/AdapterDayjs';\nimport {DatePicker} from '@mui/x-date-pickers/DatePicker';\nimport {LocalizationProvider} from '@mui/x-date-pickers/LocalizationProvider';\nimport {enUS, esES} from '@mui/x-date-pickers/locales';\n\nimport {translateValidationMessage} from '@/forms/validationMessages.js';\nimport FieldFrame from '@/inputs/FieldFrame.jsx';\n\n/** Cómo viaja una fecha de calendario. */\nexport const CALENDAR_DATE_FORMAT = 'YYYY-MM-DD';\n\n// cómo se ve y con qué textos sale el calendario en cada idioma\nconst DATE_LOOK_BY_LANGUAGE = Object.freeze({\n es: {adapterLocale: 'es', displayFormat: 'DD/MM/YYYY', localeText: esES.components.MuiLocalizationProvider.defaultProps.localeText},\n en: {adapterLocale: 'en', displayFormat: 'MM/DD/YYYY', localeText: enUS.components.MuiLocalizationProvider.defaultProps.localeText},\n});\n\n/**\n * Una fecha de calendario 'AAAA-MM-DD' como la quiere el DatePicker; null si está vacía.\n *\n * @param {string|null|undefined} calendarDate\n * @returns {import('dayjs').Dayjs|null}\n */\nfunction pickerValueOf(calendarDate) {\n // dayjs lee 'AAAA-MM-DD' como día local (a diferencia de new Date, que lo toma en UTC y lo mueve de día)\n return calendarDate ? dayjs(calendarDate) : null;\n}\n\n/**\n * @param {object} props\n * @param {string} [props.name]\n * @param {string} props.label ya traducido\n * @param {string|null|undefined} props.value 'AAAA-MM-DD' o ''\n * @param {(value: string) => void} props.onChange recibe 'AAAA-MM-DD' ('' al borrar o con una fecha a medias)\n * @param {() => void} [props.onBlur]\n * @param {string|{key: string, values?: object}} [props.error]\n * @param {string} [props.helperText]\n * @param {string} [props.minDate] 'AAAA-MM-DD'\n * @param {string} [props.maxDate] 'AAAA-MM-DD'\n * @param {boolean} [props.disableFuture] p. ej. una fecha de nacimiento\n * @param {boolean} [props.disablePast] p. ej. una cita\n * @param {'day'|'month'|'year'} [props.openTo] por dónde abre el calendario (year para un nacimiento)\n * @param {boolean} [props.disabled]\n * @param {import('react').Ref<HTMLInputElement>} [props.inputRef]\n * @param {object} [props.texts] los textos que cambian, con la forma de `common` en los textos del paquete\n */\nexport default function DateInput({\n name, label, value, onChange, onBlur, error, helperText = ' ', minDate, maxDate, disableFuture = false, disablePast = false,\n openTo = 'day', disabled = false, inputRef, texts,\n}) {\n const {t, language} = useToolkitTranslation('common', texts);\n const generatedId = useId();\n const inputId = `${name ?? 'date'}-${generatedId}`;\n const errorText = translateValidationMessage(t, error);\n const look = DATE_LOOK_BY_LANGUAGE[language] ?? DATE_LOOK_BY_LANGUAGE.es;\n\n return (\n <FieldFrame inputId={inputId} label={label}>\n <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale={look.adapterLocale} localeText={look.localeText}>\n <DatePicker\n format={look.displayFormat}\n value={pickerValueOf(value)}\n minDate={pickerValueOf(minDate) ?? undefined}\n maxDate={pickerValueOf(maxDate) ?? undefined}\n disableFuture={disableFuture}\n disablePast={disablePast}\n disabled={disabled}\n openTo={openTo}\n views={openTo === 'year' ? ['year', 'month', 'day'] : undefined}\n inputRef={inputRef}\n // una fecha a medias (25/1_/____) todavía no es fecha: viaja como vacía hasta que se complete\n onChange={(pickedDate) => onChange(pickedDate?.isValid() ? pickedDate.format(CALENDAR_DATE_FORMAT) : '')}\n onClose={onBlur}\n slotProps={{\n textField: {\n id: inputId, name, size: 'small', fullWidth: true, onBlur, error: Boolean(errorText), helperText: errorText ?? helperText,\n },\n }}\n />\n </LocalizationProvider>\n </FieldFrame>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AAuBO,MAAM,uBAAuB;AAGpC,MAAM,wBAAwB,OAAO,OAAO;AAAA,EACxC,IAAI,EAAC,eAAe,MAAM,eAAe,cAAc,YAAY,KAAK,WAAW,wBAAwB,aAAa,WAAA;AAAA,EACxH,IAAI,EAAC,eAAe,MAAM,eAAe,cAAc,YAAY,KAAK,WAAW,wBAAwB,aAAa,WAAA;AAC5H,CAAC;AAQD,SAAS,cAAc,cAAc;AAEjC,SAAO,eAAe,MAAM,YAAY,IAAI;AAChD;AAoBA,SAAwB,UAAU;AAAA,EAC9B;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAU;AAAA,EAAQ;AAAA,EAAO,aAAa;AAAA,EAAK;AAAA,EAAS;AAAA,EAAS,gBAAgB;AAAA,EAAO,cAAc;AAAA,EACtH,SAAS;AAAA,EAAO,WAAW;AAAA,EAAO;AAAA,EAAU;AAChD,GAAG;AACC,QAAM,EAAC,GAAG,SAAA,IAAY,sBAAsB,UAAU,KAAK;AAC3D,QAAM,cAAc,MAAA;AACpB,QAAM,UAAU,GAAG,QAAQ,MAAM,IAAI,WAAW;AAChD,QAAM,YAAY,2BAA2B,GAAG,KAAK;AACrD,QAAM,OAAO,sBAAsB,QAAQ,KAAK,sBAAsB;AAEtE,SACI,oBAAC,YAAA,EAAW,SAAkB,OAC1B,UAAA,oBAAC,sBAAA,EAAqB,aAAa,cAAc,eAAe,KAAK,eAAe,YAAY,KAAK,YACjG,UAAA;AAAA,IAAC;AAAA,IAAA;AAAA,MACG,QAAQ,KAAK;AAAA,MACb,OAAO,cAAc,KAAK;AAAA,MAC1B,SAAS,cAAc,OAAO,KAAK;AAAA,MACnC,SAAS,cAAc,OAAO,KAAK;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,OAAO,WAAW,SAAS,CAAC,QAAQ,SAAS,KAAK,IAAI;AAAA,MACtD;AAAA,MAEA,UAAU,CAAC,eAAe,SAAS,YAAY,YAAY,WAAW,OAAO,oBAAoB,IAAI,EAAE;AAAA,MACvG,SAAS;AAAA,MACT,WAAW;AAAA,QACP,WAAW;AAAA,UACP,IAAI;AAAA,UAAS;AAAA,UAAM,MAAM;AAAA,UAAS,WAAW;AAAA,UAAM;AAAA,UAAQ,OAAO,QAAQ,SAAS;AAAA,UAAG,YAAY,aAAa;AAAA,QAAA;AAAA,MACnH;AAAA,IACJ;AAAA,EAAA,GAER,EAAA,CACJ;AAER;"}
|
|
@@ -21,30 +21,23 @@ import CloseIcon from "@mui/icons-material/Close";
|
|
|
21
21
|
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
|
|
22
22
|
import ExpandLessIcon from "@mui/icons-material/ExpandLess";
|
|
23
23
|
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
|
|
24
|
-
import InsertDriveFileOutlinedIcon from "@mui/icons-material/InsertDriveFileOutlined";
|
|
25
|
-
import PictureAsPdfOutlinedIcon from "@mui/icons-material/PictureAsPdfOutlined";
|
|
26
|
-
import TableViewOutlinedIcon from "@mui/icons-material/TableViewOutlined";
|
|
27
|
-
import TextSnippetOutlinedIcon from "@mui/icons-material/TextSnippetOutlined";
|
|
28
24
|
import { downloadsActor, countActiveDownloads, clearFinishedDownloads, cancelDownload } from "./downloadQueue.js";
|
|
25
|
+
import { useFileTypes, UNKNOWN_FILE_TYPE } from "./fileTypes.js";
|
|
29
26
|
import { DOWNLOAD_STATUS } from "./downloadsMachine.js";
|
|
30
|
-
import {
|
|
27
|
+
import { useFormatters } from "../toolkit/useFormatters.js";
|
|
31
28
|
import { isGlassTheme, glassStylesOf } from "../theme/glassSurface.js";
|
|
32
|
-
|
|
33
|
-
xlsx: { Icon: TableViewOutlinedIcon, color: "success.main" },
|
|
34
|
-
csv: { Icon: TextSnippetOutlinedIcon, color: "info.main" },
|
|
35
|
-
pdf: { Icon: PictureAsPdfOutlinedIcon, color: "error.main" }
|
|
36
|
-
};
|
|
37
|
-
const DEFAULT_FILE_TYPE_APPEARANCE = { Icon: InsertDriveFileOutlinedIcon, color: "text.secondary" };
|
|
29
|
+
import ComponentTexts from "../toolkit/ComponentTexts.js";
|
|
38
30
|
const AUTO_DISMISS_MILLISECONDS = 5e3;
|
|
39
31
|
const SCREEN_EDGE_SPACING = 3;
|
|
40
32
|
const PANEL_WIDTH_PIXELS = 360;
|
|
41
33
|
const LIST_MAX_HEIGHT = "40vh";
|
|
42
34
|
const PROGRESS_RING_PIXELS = 22;
|
|
43
|
-
function fileTypeAppearanceOf(download) {
|
|
35
|
+
function fileTypeAppearanceOf(download, fileTypes) {
|
|
44
36
|
const extension = download.fileName?.split(".").pop()?.toLowerCase();
|
|
45
|
-
|
|
37
|
+
const fileType = fileTypes[download.format ?? extension];
|
|
38
|
+
return fileType ? { Icon: fileType.Icon ?? UNKNOWN_FILE_TYPE.Icon, color: fileType.color ?? UNKNOWN_FILE_TYPE.color } : UNKNOWN_FILE_TYPE;
|
|
46
39
|
}
|
|
47
|
-
function detailTextOf(download, t) {
|
|
40
|
+
function detailTextOf(download, t, formatFileSize) {
|
|
48
41
|
if (download.status === DOWNLOAD_STATUS.completed) {
|
|
49
42
|
return formatFileSize(download.loadedBytes);
|
|
50
43
|
}
|
|
@@ -108,7 +101,8 @@ function DownloadStatusIndicator({ download, displayName }) {
|
|
|
108
101
|
}
|
|
109
102
|
function DownloadRow({ download }) {
|
|
110
103
|
const { t } = useToolkitTranslation("common");
|
|
111
|
-
const {
|
|
104
|
+
const { formatFileSize } = useFormatters();
|
|
105
|
+
const { Icon, color } = fileTypeAppearanceOf(download, useFileTypes());
|
|
112
106
|
const displayName = download.fileName ?? download.label;
|
|
113
107
|
const isFailed = download.status === DOWNLOAD_STATUS.failed;
|
|
114
108
|
return (
|
|
@@ -124,7 +118,7 @@ function DownloadRow({ download }) {
|
|
|
124
118
|
component: "p",
|
|
125
119
|
noWrap: !isFailed,
|
|
126
120
|
sx: { color: isFailed ? "error.main" : "text.secondary" },
|
|
127
|
-
children: detailTextOf(download, t)
|
|
121
|
+
children: detailTextOf(download, t, formatFileSize)
|
|
128
122
|
}
|
|
129
123
|
)
|
|
130
124
|
] }),
|
|
@@ -132,8 +126,8 @@ function DownloadRow({ download }) {
|
|
|
132
126
|
] })
|
|
133
127
|
);
|
|
134
128
|
}
|
|
135
|
-
function DownloadManager() {
|
|
136
|
-
const { t } = useToolkitTranslation("common");
|
|
129
|
+
function DownloadManager({ texts } = {}) {
|
|
130
|
+
const { t } = useToolkitTranslation("common", texts);
|
|
137
131
|
const downloads = useSelector(downloadsActor, (snapshot) => snapshot.context.downloads);
|
|
138
132
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
139
133
|
const previousCountRef = useRef(downloads.length);
|
|
@@ -152,7 +146,7 @@ function DownloadManager() {
|
|
|
152
146
|
const dismissTimer = setTimeout(clearFinishedDownloads, AUTO_DISMISS_MILLISECONDS);
|
|
153
147
|
return () => clearTimeout(dismissTimer);
|
|
154
148
|
}, [allSucceeded, downloads.length]);
|
|
155
|
-
return /* @__PURE__ */ jsx(Slide, { direction: "up", in: downloads.length > 0, mountOnEnter: true, unmountOnExit: true, children: /* @__PURE__ */ jsxs(
|
|
149
|
+
return /* @__PURE__ */ jsx(ComponentTexts, { namespace: "common", texts, children: /* @__PURE__ */ jsx(Slide, { direction: "up", in: downloads.length > 0, mountOnEnter: true, unmountOnExit: true, children: /* @__PURE__ */ jsxs(
|
|
156
150
|
Paper,
|
|
157
151
|
{
|
|
158
152
|
elevation: 8,
|
|
@@ -193,7 +187,7 @@ function DownloadManager() {
|
|
|
193
187
|
] })
|
|
194
188
|
]
|
|
195
189
|
}
|
|
196
|
-
) });
|
|
190
|
+
) }) });
|
|
197
191
|
}
|
|
198
192
|
export {
|
|
199
193
|
DownloadManager as default
|