@stackcraftmx/calcita 0.47.0 → 0.51.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 +173 -12
- package/cli/calcita.js +15 -0
- package/cli/checkTexts.js +209 -0
- package/cli/packageVersions.js +6 -1
- package/cli/projectFiles.js +4 -2
- package/cli/templates/project/README.md.tpl +21 -1
- package/cli/templates/project/eslint.config.js.tpl +27 -0
- package/cli/templates/project/src/app/appTexts.js.tpl +2 -1
- package/cli/templates/project/src/core/publicPaths.js.tpl +10 -2
- package/cli/templates/project/vite.config.js.tpl +8 -1
- package/dist/DataTable/AuditTrailDialog.js +3 -2
- package/dist/DataTable/AuditTrailDialog.js.map +1 -1
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +3 -2
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +3 -2
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +3 -2
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
- package/dist/DataTable/EmptyState.js +3 -2
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +3 -2
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +3 -2
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +95 -28
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/ServerTableFilterDrawer.js +8 -3
- package/dist/DataTable/ServerTableFilterDrawer.js.map +1 -1
- package/dist/DataTable/StackedCell.js +60 -0
- package/dist/DataTable/StackedCell.js.map +1 -0
- package/dist/DataTable/TableCardsView.js +88 -0
- package/dist/DataTable/TableCardsView.js.map +1 -0
- package/dist/DataTable/columnWidths.js +30 -0
- package/dist/DataTable/columnWidths.js.map +1 -0
- package/dist/DataTable/columns.js +47 -1
- package/dist/DataTable/columns.js.map +1 -1
- package/dist/DataTable/fixedColumns.js +24 -0
- package/dist/DataTable/fixedColumns.js.map +1 -0
- package/dist/DataTable/useTableView.js +29 -0
- package/dist/DataTable/useTableView.js.map +1 -0
- package/dist/access/PermissionMatrix.js +3 -2
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountProvider.js +1 -1
- package/dist/account/AccountSecuritySection.js +3 -2
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/DemoTotpCodeButton.js +3 -2
- package/dist/account/DemoTotpCodeButton.js.map +1 -1
- package/dist/account/ProfileScreen.js +3 -2
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +3 -2
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/appShell/AccountMenu.js +3 -2
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +3 -2
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +3 -2
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +3 -2
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActionButton.js +11 -4
- package/dist/appShell/BarActionButton.js.map +1 -1
- package/dist/appShell/BarActions.js +3 -2
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +3 -2
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +3 -2
- package/dist/appShell/BarSearch.js.map +1 -1
- package/dist/appShell/HeaderTools.js +3 -2
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +3 -2
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +3 -2
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +3 -2
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +3 -2
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +3 -2
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TabBarEditor.js +3 -2
- package/dist/appShell/TabBarEditor.js.map +1 -1
- package/dist/appShell/TopMenu.js +3 -2
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +3 -2
- package/dist/appShell/Wordmark.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +3 -2
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +3 -2
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +3 -2
- package/dist/calendar/EventDetails.js.map +1 -1
- package/dist/datePickers/DateInput.js +28 -22
- package/dist/datePickers/DateInput.js.map +1 -1
- package/dist/datePickers/DateTimeInput.js +33 -27
- package/dist/datePickers/DateTimeInput.js.map +1 -1
- package/dist/datePickers/MonthInput.js +27 -21
- package/dist/datePickers/MonthInput.js.map +1 -1
- package/dist/datePickers/TimeInput.js +26 -20
- package/dist/datePickers/TimeInput.js.map +1 -1
- package/dist/datePickers/usePickerField.js +5 -4
- package/dist/datePickers/usePickerField.js.map +1 -1
- package/dist/downloads/DownloadManager.js +3 -2
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/feedback/ConfirmDialog.js +3 -2
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +3 -2
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +3 -2
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +3 -2
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/forms/FormProblemAlert.js +3 -2
- package/dist/forms/FormProblemAlert.js.map +1 -1
- package/dist/forms/SubmitButton.js +7 -2
- package/dist/forms/SubmitButton.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +3 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/fieldRules.js +30 -0
- package/dist/forms/fieldRules.js.map +1 -0
- package/dist/forms/security/SecondStepForm.js +3 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +3 -2
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/forms/useValidationText.js +18 -0
- package/dist/forms/useValidationText.js.map +1 -0
- package/dist/forms/validation.js +35 -31
- package/dist/forms/validation.js.map +1 -1
- package/dist/forms/validationMessages.js +64 -7
- package/dist/forms/validationMessages.js.map +1 -1
- package/dist/forms.js +3 -0
- package/dist/forms.js.map +1 -1
- package/dist/hookForm/FormProblemAlert.js +3 -2
- package/dist/hookForm/FormProblemAlert.js.map +1 -1
- package/dist/hookForm/SubmitButton.js +7 -2
- package/dist/hookForm/SubmitButton.js.map +1 -1
- package/dist/inbox/InboxLayout.js +3 -2
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +3 -2
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +3 -2
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/index.js +186 -163
- package/dist/index.js.map +1 -1
- package/dist/inputs/CatalogInput.js +32 -25
- package/dist/inputs/CatalogInput.js.map +1 -1
- package/dist/inputs/CheckboxGroupInput.js +8 -5
- package/dist/inputs/CheckboxGroupInput.js.map +1 -1
- package/dist/inputs/ColorInput.js +7 -4
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FieldFrame.js +10 -4
- package/dist/inputs/FieldFrame.js.map +1 -1
- package/dist/inputs/FormSection.js +1 -1
- package/dist/inputs/MaskedInput.js +1 -0
- package/dist/inputs/MaskedInput.js.map +1 -1
- package/dist/inputs/NumberInput.js +29 -24
- package/dist/inputs/NumberInput.js.map +1 -1
- package/dist/inputs/RepeatingFields.js +8 -5
- package/dist/inputs/RepeatingFields.js.map +1 -1
- package/dist/inputs/SelectInput.js +8 -5
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +5 -3
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/TransferListInput.js +8 -5
- package/dist/inputs/TransferListInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +3 -2
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/kanban/KanbanBoard.js +3 -2
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/legal/CookieNotice.js +3 -2
- package/dist/legal/CookieNotice.js.map +1 -1
- package/dist/legal/TermsAcceptanceDialog.js +3 -2
- package/dist/legal/TermsAcceptanceDialog.js.map +1 -1
- package/dist/locales/en.js +24 -0
- package/dist/locales/en.js.map +1 -1
- package/dist/locales/es.js +24 -0
- package/dist/locales/es.js.map +1 -1
- package/dist/maps/MapView.js +3 -2
- package/dist/maps/MapView.js.map +1 -1
- package/dist/mexico/CfdiSummary.js +3 -2
- package/dist/mexico/CfdiSummary.js.map +1 -1
- package/dist/mexico/EfirmaFields.js +3 -2
- package/dist/mexico/EfirmaFields.js.map +1 -1
- package/dist/mexico/mexicoIdentifiers.js +5 -0
- package/dist/mexico/mexicoIdentifiers.js.map +1 -1
- package/dist/mexico/mexicoRules.js +9 -4
- package/dist/mexico/mexicoRules.js.map +1 -1
- package/dist/mexico.js +4 -2
- package/dist/pwa/InstallAppSetting.js +3 -2
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +3 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +3 -2
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/react-hook-form.js +3 -0
- package/dist/react-hook-form.js.map +1 -1
- package/dist/richText/RichTextEditor.js +7 -4
- package/dist/richText/RichTextEditor.js.map +1 -1
- package/dist/richText/RichTextView.js +3 -2
- package/dist/richText/RichTextView.js.map +1 -1
- package/dist/security/ActivityLog.js +3 -2
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +3 -2
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +3 -2
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/table.js +21 -2
- package/dist/table.js.map +1 -1
- package/dist/theme/themePieces.js +70 -4
- package/dist/theme/themePieces.js.map +1 -1
- package/dist/toolkit/ToolkitProvider.js +8 -1
- package/dist/toolkit/ToolkitProvider.js.map +1 -1
- package/dist/toolkit/browserNavigation.js +19 -4
- package/dist/toolkit/browserNavigation.js.map +1 -1
- package/dist/toolkit/combineTexts.js +32 -0
- package/dist/toolkit/combineTexts.js.map +1 -0
- package/dist/toolkit/hashNavigation.js +17 -3
- package/dist/toolkit/hashNavigation.js.map +1 -1
- package/dist/toolkit/navigationPaths.js +15 -0
- package/dist/toolkit/navigationPaths.js.map +1 -1
- package/dist/toolkit/pathIndex.js +66 -0
- package/dist/toolkit/pathIndex.js.map +1 -0
- package/dist/toolkit/toolkitSettings.js +19 -1
- package/dist/toolkit/toolkitSettings.js.map +1 -1
- package/dist/types/DataTable/AuditTrailDialog.d.ts +6 -17
- package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +6 -5
- package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +6 -9
- package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +6 -14
- package/dist/types/DataTable/EmptyState.d.ts +6 -9
- package/dist/types/DataTable/FilterChips.d.ts +6 -11
- package/dist/types/DataTable/GoToPage.d.ts +6 -9
- package/dist/types/DataTable/ServerTable.d.ts +16 -80
- package/dist/types/DataTable/ServerTableFilterDrawer.d.ts +6 -13
- package/dist/types/DataTable/StackedCell.d.ts +12 -0
- package/dist/types/DataTable/TableCardsView.d.ts +27 -0
- package/dist/types/DataTable/columnWidths.d.ts +21 -0
- package/dist/types/DataTable/columns.d.ts +111 -0
- package/dist/types/DataTable/fixedColumns.d.ts +29 -0
- package/dist/types/DataTable/index.d.ts +6 -1
- package/dist/types/DataTable/useTableView.d.ts +22 -0
- package/dist/types/access/PermissionMatrix.d.ts +6 -10
- package/dist/types/account/AccountSecuritySection.d.ts +6 -7
- package/dist/types/account/DemoTotpCodeButton.d.ts +6 -5
- package/dist/types/account/ProfileScreen.d.ts +6 -18
- package/dist/types/account/SignInScreen.d.ts +6 -13
- package/dist/types/appShell/AccountMenu.d.ts +6 -11
- package/dist/types/appShell/AppLayout.d.ts +3 -40
- package/dist/types/appShell/AppearanceSettings.d.ts +6 -12
- package/dist/types/appShell/AuthLayout.d.ts +3 -11
- package/dist/types/appShell/BarActionButton.d.ts +6 -10
- package/dist/types/appShell/BarActions.d.ts +6 -15
- package/dist/types/appShell/BarCapsule.d.ts +6 -9
- package/dist/types/appShell/BarSearch.d.ts +6 -15
- package/dist/types/appShell/HeaderTools.d.ts +6 -16
- package/dist/types/appShell/MobileTabBar.d.ts +6 -13
- package/dist/types/appShell/SettingsMenu.d.ts +6 -10
- package/dist/types/appShell/SideMenu.d.ts +6 -20
- package/dist/types/appShell/SideMenuEntry.d.ts +6 -11
- package/dist/types/appShell/SubmenuActionsMenu.d.ts +6 -12
- package/dist/types/appShell/TabBarEditor.d.ts +6 -9
- package/dist/types/appShell/TopMenu.d.ts +6 -5
- package/dist/types/appShell/Wordmark.d.ts +6 -7
- package/dist/types/auth/PasswordChecklist.d.ts +6 -9
- package/dist/types/calendar/EventCalendar.d.ts +6 -23
- package/dist/types/calendar/EventDetails.d.ts +6 -10
- package/dist/types/datePickers/DateInput.d.ts +7 -22
- package/dist/types/datePickers/DateTimeInput.d.ts +7 -27
- package/dist/types/datePickers/MonthInput.d.ts +7 -21
- package/dist/types/datePickers/TimeInput.d.ts +7 -21
- package/dist/types/datePickers/usePickerField.d.ts +5 -1
- package/dist/types/downloads/DownloadManager.d.ts +6 -6
- package/dist/types/feedback/ConfirmDialog.d.ts +6 -18
- package/dist/types/feedback/ModalWaiting.d.ts +6 -9
- package/dist/types/feedback/NewBuildNotice.d.ts +6 -10
- package/dist/types/filepond/FileDropzone.d.ts +6 -29
- package/dist/types/forms/FormProblemAlert.d.ts +6 -5
- package/dist/types/forms/SubmitButton.d.ts +6 -6
- package/dist/types/forms/auth/SignInForm.d.ts +6 -11
- package/dist/types/forms/fieldRules.d.ts +44 -0
- package/dist/types/forms/index.d.ts +1 -0
- package/dist/types/forms/security/SecondStepForm.d.ts +6 -14
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +6 -21
- package/dist/types/forms/useValidationText.d.ts +12 -0
- package/dist/types/forms/validation.d.ts +117 -174
- package/dist/types/forms/validationMessages.d.ts +54 -6
- package/dist/types/forms.d.ts +1 -1
- package/dist/types/hookForm/FormProblemAlert.d.ts +6 -5
- package/dist/types/hookForm/SubmitButton.d.ts +6 -13
- package/dist/types/inbox/InboxLayout.d.ts +6 -17
- package/dist/types/inbox/MessageList.d.ts +6 -18
- package/dist/types/inbox/MessageView.d.ts +6 -14
- package/dist/types/index.d.ts +8 -1
- package/dist/types/inputs/CatalogInput.d.ts +7 -24
- package/dist/types/inputs/CheckboxGroupInput.d.ts +7 -18
- package/dist/types/inputs/ColorInput.d.ts +7 -16
- package/dist/types/inputs/FieldFrame.d.ts +6 -10
- package/dist/types/inputs/MaskedInput.d.ts +3 -0
- package/dist/types/inputs/NumberInput.d.ts +7 -20
- package/dist/types/inputs/RepeatingFields.d.ts +7 -28
- package/dist/types/inputs/SelectInput.d.ts +7 -20
- package/dist/types/inputs/TextInput.d.ts +3 -0
- package/dist/types/inputs/TransferListInput.d.ts +7 -20
- package/dist/types/inputs/WizardLayout.d.ts +6 -19
- package/dist/types/kanban/KanbanBoard.d.ts +6 -25
- package/dist/types/legal/CookieNotice.d.ts +6 -16
- package/dist/types/legal/TermsAcceptanceDialog.d.ts +6 -18
- package/dist/types/locales/en.d.ts +24 -0
- package/dist/types/locales/es.d.ts +24 -0
- package/dist/types/maps/MapView.d.ts +6 -17
- package/dist/types/mexico/CfdiSummary.d.ts +6 -11
- package/dist/types/mexico/EfirmaFields.d.ts +6 -16
- package/dist/types/mexico/mexicoIdentifiers.d.ts +7 -0
- package/dist/types/mexico/mexicoRules.d.ts +19 -3
- package/dist/types/mexico.d.ts +2 -2
- package/dist/types/pwa/InstallAppSetting.d.ts +6 -6
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +6 -9
- package/dist/types/pwa/InstallPromptBanner.d.ts +6 -11
- package/dist/types/react-hook-form.d.ts +1 -0
- package/dist/types/richText/RichTextEditor.d.ts +7 -22
- package/dist/types/richText/RichTextView.d.ts +6 -9
- package/dist/types/security/ActivityLog.d.ts +6 -9
- package/dist/types/security/RecoveryCodesDialog.d.ts +6 -11
- package/dist/types/security/SessionsCard.d.ts +6 -11
- package/dist/types/table.d.ts +3 -1
- package/dist/types/theme/themePieces.d.ts +55 -3
- package/dist/types/toolkit/ToolkitProvider.d.ts +13 -1
- package/dist/types/toolkit/browserNavigation.d.ts +7 -3
- package/dist/types/toolkit/combineTexts.d.ts +6 -0
- package/dist/types/toolkit/hashNavigation.d.ts +4 -2
- package/dist/types/toolkit/navigationPaths.d.ts +35 -0
- package/dist/types/toolkit/pathIndex.d.ts +27 -0
- package/dist/types/toolkit/toolkitSettings.d.ts +7 -4
- package/dist/types/ui/actions/ActionsMenu.d.ts +9 -11
- package/dist/types/ui/actions/CopyButton.d.ts +6 -12
- package/dist/types/ui/actions/DownloadMenu.d.ts +6 -29
- package/dist/types/ui/actions/SecretField.d.ts +6 -11
- package/dist/types/ui/actions/SelectionActions.d.ts +6 -13
- package/dist/types/ui/cards/RecordCard.d.ts +6 -31
- package/dist/types/ui/cards/RecordCardSkeleton.d.ts +6 -11
- package/dist/types/ui/countdown/Countdown.d.ts +6 -15
- package/dist/types/ui/documents/PdfViewerDialog.d.ts +6 -13
- package/dist/types/ui/filters/DateRangeFilter.d.ts +6 -10
- package/dist/types/ui/filters/FilterDrawer.d.ts +6 -17
- package/dist/types/ui/filters/PeriodRangeSelector.d.ts +6 -13
- package/dist/types/ui/filters/QuickFilters.d.ts +6 -12
- package/dist/types/ui/help/HelpButton.d.ts +6 -10
- package/dist/types/ui/help/HelpCarouselDialog.d.ts +6 -14
- package/dist/types/ui/labels/MarkedText.d.ts +10 -0
- package/dist/types/ui/labels/Tag.d.ts +6 -12
- package/dist/types/ui/labels/markedTextPieces.d.ts +22 -0
- package/dist/types/ui/layout/CollapsibleSection.d.ts +6 -12
- package/dist/types/ui/layout/ContentDialog.d.ts +6 -10
- package/dist/types/ui/layout/DefinitionList.d.ts +6 -9
- package/dist/types/ui/layout/DetailHeader.d.ts +6 -10
- package/dist/types/ui/layout/ErrorPage.d.ts +6 -17
- package/dist/types/ui/layout/FullPageLoader.d.ts +6 -6
- package/dist/types/ui/layout/GlassPanel.d.ts +6 -8
- package/dist/types/ui/layout/PageHeader.d.ts +6 -10
- package/dist/types/ui/layout/PageHeading.d.ts +6 -11
- package/dist/types/ui/layout/ProfileHeader.d.ts +6 -11
- package/dist/types/ui/layout/ScrollableRow.d.ts +6 -13
- package/dist/types/ui/layout/SectionCard.d.ts +6 -8
- package/dist/types/ui/metrics/BarList.d.ts +6 -12
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +6 -19
- package/dist/types/ui/metrics/ChartTooltip.d.ts +6 -6
- package/dist/types/ui/metrics/ColumnChart.d.ts +6 -25
- package/dist/types/ui/metrics/LineChart.d.ts +6 -20
- package/dist/types/ui/metrics/MetricCard.d.ts +6 -17
- package/dist/types/ui/metrics/TableToggle.d.ts +6 -6
- package/dist/types/ui/metrics/chartParts.d.ts +15 -30
- package/dist/types/ui/navigation/BackButton.d.ts +6 -9
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +6 -9
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +6 -8
- package/dist/types/ui/navigation/LanguageToggle.d.ts +6 -8
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +6 -7
- package/dist/types/ui/navigation/RouteTabs.d.ts +7 -12
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +6 -23
- package/dist/types/upload/AttachmentsInput.d.ts +11 -1
- package/dist/types/upload/FileDropzone.d.ts +6 -21
- package/dist/types/upload/FileInput.d.ts +7 -24
- package/dist/types/upload/FileUploadInput.d.ts +7 -31
- package/dist/types/upload/ImageInput.d.ts +7 -25
- package/dist/types/upload/TemplateDownloadButton.d.ts +6 -18
- package/dist/ui/actions/ActionsMenu.js +14 -5
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/CopyButton.js +8 -3
- package/dist/ui/actions/CopyButton.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +3 -2
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SecretField.js +8 -3
- package/dist/ui/actions/SecretField.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +3 -2
- package/dist/ui/actions/SelectionActions.js.map +1 -1
- package/dist/ui/cards/RecordCard.js +4 -3
- package/dist/ui/cards/RecordCard.js.map +1 -1
- package/dist/ui/cards/RecordCardSkeleton.js +8 -3
- package/dist/ui/cards/RecordCardSkeleton.js.map +1 -1
- package/dist/ui/countdown/Countdown.js +3 -2
- package/dist/ui/countdown/Countdown.js.map +1 -1
- package/dist/ui/documents/PdfViewerDialog.js +3 -2
- package/dist/ui/documents/PdfViewerDialog.js.map +1 -1
- package/dist/ui/filters/DateRangeFilter.js +3 -2
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/FilterDrawer.js +3 -2
- package/dist/ui/filters/FilterDrawer.js.map +1 -1
- package/dist/ui/filters/PeriodRangeSelector.js +3 -2
- package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +3 -2
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/help/HelpButton.js +8 -3
- package/dist/ui/help/HelpButton.js.map +1 -1
- package/dist/ui/help/HelpCarouselDialog.js +3 -2
- package/dist/ui/help/HelpCarouselDialog.js.map +1 -1
- package/dist/ui/labels/MarkedText.js +31 -0
- package/dist/ui/labels/MarkedText.js.map +1 -0
- package/dist/ui/labels/Tag.js +3 -2
- package/dist/ui/labels/Tag.js.map +1 -1
- package/dist/ui/labels/markedTextPieces.js +64 -0
- package/dist/ui/labels/markedTextPieces.js.map +1 -0
- package/dist/ui/layout/CollapsibleSection.js +3 -2
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +3 -2
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/DefinitionList.js +3 -2
- package/dist/ui/layout/DefinitionList.js.map +1 -1
- package/dist/ui/layout/DetailHeader.js +3 -2
- package/dist/ui/layout/DetailHeader.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +3 -2
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +3 -2
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/GlassPanel.js +3 -2
- package/dist/ui/layout/GlassPanel.js.map +1 -1
- package/dist/ui/layout/PageHeader.js +3 -2
- package/dist/ui/layout/PageHeader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +3 -2
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/layout/ProfileHeader.js +3 -2
- package/dist/ui/layout/ProfileHeader.js.map +1 -1
- package/dist/ui/layout/ScrollableRow.js +3 -2
- package/dist/ui/layout/ScrollableRow.js.map +1 -1
- package/dist/ui/layout/SectionCard.js +3 -2
- package/dist/ui/layout/SectionCard.js.map +1 -1
- package/dist/ui/metrics/BarList.js +3 -2
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +3 -2
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +3 -2
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +3 -2
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +3 -2
- package/dist/ui/metrics/LineChart.js.map +1 -1
- package/dist/ui/metrics/MetricCard.js +3 -2
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +3 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +7 -4
- package/dist/ui/metrics/chartParts.js.map +1 -1
- package/dist/ui/navigation/BackButton.js +7 -2
- package/dist/ui/navigation/BackButton.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +3 -2
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +3 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +3 -2
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +3 -2
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/navigation/RouteTabs.js +8 -4
- package/dist/ui/navigation/RouteTabs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +3 -2
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/AttachmentsInput.js +9 -6
- package/dist/upload/AttachmentsInput.js.map +1 -1
- package/dist/upload/FileChip.js +2 -1
- package/dist/upload/FileChip.js.map +1 -1
- package/dist/upload/FileDropzone.js +5 -4
- package/dist/upload/FileDropzone.js.map +1 -1
- package/dist/upload/FileInput.js +9 -6
- package/dist/upload/FileInput.js.map +1 -1
- package/dist/upload/FileUploadInput.js +9 -6
- package/dist/upload/FileUploadInput.js.map +1 -1
- package/dist/upload/ImageInput.js +8 -5
- package/dist/upload/ImageInput.js.map +1 -1
- package/dist/upload/TemplateDownloadButton.js +3 -2
- package/dist/upload/TemplateDownloadButton.js.map +1 -1
- package/package.json +1 -1
- package/cli/templates/project/src/app/combineTexts.js.tpl +0 -31
package/README.md
CHANGED
|
@@ -53,6 +53,11 @@ El proyecto trae pruebas de pantalla (`pnpm test`, Vitest con jsdom y Testing Li
|
|
|
53
53
|
dirección con el servidor de mentira (`openScreen` en `src/testing`) y pica como una persona. Vienen las del inicio, la de
|
|
54
54
|
entrar y las del módulo de ejemplo, y `make:module` arma la de cada módulo nuevo.
|
|
55
55
|
|
|
56
|
+
Y dos revisiones para antes de subir: `pnpm lint` (ESLint: una variable que no existe, «t is not defined», se caza al
|
|
57
|
+
escribir) y `pnpm check:texts` (`calcita check:texts`: que cada `t('llave')` escrita tal cual exista en todos los idiomas y
|
|
58
|
+
que los idiomas tengan las mismas llaves). Sirve también en un proyecto que no salió del CLI: lee `src/**/*Texts.js` y
|
|
59
|
+
`src/**/texts.js`.
|
|
60
|
+
|
|
56
61
|
## Arrancar en un proyecto que ya existe
|
|
57
62
|
|
|
58
63
|
```bash
|
|
@@ -97,12 +102,42 @@ Sin nada más, todo sale en español, con la zona horaria del navegador, montos
|
|
|
97
102
|
| `canAccess` | quién ve qué (menú, rutas, botones): la regla del proyecto, normalmente `createAccessChecker(...)` | solo lo que no tiene regla |
|
|
98
103
|
| `buildId` | la compilación del front que corre en la pestaña, para el aviso de versión nueva | ninguna (sin aviso) |
|
|
99
104
|
| `fileTypes` | tipos de archivo propios para las descargas (su ícono y su color), además de Excel, CSV y PDF | los de la casa |
|
|
105
|
+
| `onMissingText` | a quién avisarle de un texto que no existe (la pantalla enseña la llave): mandarlo a Sentry, tronar una prueba | un aviso en la consola, una vez por idioma y llave |
|
|
106
|
+
| `readValidationMessage` | cómo leer los mensajes de validación de un sistema que ya existe (ver «Mensajes de error propios») | `{key}`, `{msg}`, `{message}` y texto |
|
|
100
107
|
|
|
101
108
|
Un `ToolkitProvider` dentro de otro (una parte de la app en otro idioma, una vista previa) toma del de afuera lo que no le
|
|
102
109
|
digan, y lo de adentro usa el suyo: el más cercano. No le cambia nada al resto de la app. Dentro de un componente,
|
|
103
110
|
`useFormatters()` da los formatos (`formatDate`, `formatCurrency`…) con el idioma, la zona y la moneda del proveedor más
|
|
104
111
|
cercano; los sueltos usan los del proveedor de la raíz.
|
|
105
112
|
|
|
113
|
+
**Fuera de un componente** (una función que arma columnas, un diccionario, un aviso): `translateToolkit(namespace, llave,
|
|
114
|
+
valores)` y los formatos sueltos (`formatDate`, `formatCurrency`, `formatNumber`…) usan el idioma, la zona y la moneda del
|
|
115
|
+
proveedor de la raíz, ya actualizados en la misma pintada en que cambian. Así una función auxiliar no tiene que recibir `t`
|
|
116
|
+
ni los formatos por parámetro:
|
|
117
|
+
|
|
118
|
+
```js
|
|
119
|
+
import {formatDate, translateToolkit} from '@stackcraftmx/calcita';
|
|
120
|
+
|
|
121
|
+
export const columnsOf = () => [
|
|
122
|
+
textColumn({field: 'registered_at', label: translateToolkit('clients', 'columns.registered'), valueFormatter: (value) => formatDate(value)}),
|
|
123
|
+
];
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Dos cuidados: con dos proveedores (dos islas en otro idioma) los sueltos usan el de la raíz; y si la pantalla guarda el
|
|
127
|
+
resultado (`useMemo(() => columnsOf(), [])`), ponga el idioma en las dependencias o se queda en el de antes.
|
|
128
|
+
|
|
129
|
+
Un texto que no existe se pinta con su llave (`clients:columns.registered`) y Calcita lo avisa en la consola una vez; con
|
|
130
|
+
`onMissingText` se le avisa al proyecto. Un texto opcional se pide con `defaultValue` (`t('tips.extra', {defaultValue: ''})`)
|
|
131
|
+
y no avisa.
|
|
132
|
+
|
|
133
|
+
Para un texto con una palabra resaltada, una sola llave con marcas y `MarkedText` (como `<Trans>` de react-i18next, sin
|
|
134
|
+
i18next). No pinta HTML: solo las marcas que se nombran; lo demás sale como texto.
|
|
135
|
+
|
|
136
|
+
```jsx
|
|
137
|
+
// es: {home: {headline: 'Optimiza tu negocio con <highlight>financiamiento</highlight> inteligente'}}
|
|
138
|
+
<MarkedText text={t('home.headline')} marks={{highlight: <Box component="strong" sx={{color: 'primary.main'}} />}} />
|
|
139
|
+
```
|
|
140
|
+
|
|
106
141
|
Con react-router y i18next:
|
|
107
142
|
|
|
108
143
|
```jsx
|
|
@@ -310,6 +345,23 @@ tema del proyecto y al final el `sx` que pase la pantalla. Los números se leen
|
|
|
310
345
|
Sirve con `createToolkitTheme` y con cualquier tema de MUI (el que ya tenía un sistema, tal cual): así un sistema puede
|
|
311
346
|
dejar Calcita con su propia cara, parte por parte.
|
|
312
347
|
|
|
348
|
+
Además, como en MUI, cada parte con nombre se puede **cambiar por otro componente** (`slots`) o **recibir props de más**
|
|
349
|
+
(`slotProps`), con el nombre de la parte de la tabla de abajo; desde la pantalla o para toda la app desde el tema:
|
|
350
|
+
|
|
351
|
+
```jsx
|
|
352
|
+
<RecordCard slots={{avatar: StoreAvatar}} slotProps={{titles: {component: 'header'}}} … />
|
|
353
|
+
createTheme({components: {CalcitaRecordCard: {defaultProps: {slotProps: {root: {elevation: 3}}}}}});
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
`className`, `style` y `sx` de `slotProps` se suman a los de Calcita; una pieza dentro de otra igual no toma los `slots` de la
|
|
357
|
+
de afuera. `TextInput` es la excepción: su `slotProps` es el del `TextField` de MUI (`input`, `htmlInput`…). Una pieza propia
|
|
358
|
+
del proyecto se arma con lo mismo: `piecePart`, `usePieceProps` y `withPieceSlots`.
|
|
359
|
+
|
|
360
|
+
El `sx` de la pantalla se **combina regla por regla** con el de la pieza, no la reemplaza: en la tabla,
|
|
361
|
+
`gridSx={{'& .MuiDataGrid-cell': {alignItems: 'flex-start'}}}` cambia solo la alineación y la celda conserva lo demás.
|
|
362
|
+
Para hacer lo mismo en un componente propio: `sx={[{p: 2}, ...sxListOf(sx)]}` (una lista de sx, como pide MUI; nunca
|
|
363
|
+
`{p: 2, ...sx}`, que pierde la llave entera cuando las dos traen la misma).
|
|
364
|
+
|
|
313
365
|
<details>
|
|
314
366
|
<summary>Las piezas, con su nombre en el tema y sus partes</summary>
|
|
315
367
|
|
|
@@ -324,11 +376,14 @@ dejar Calcita con su propia cara, parte por parte.
|
|
|
324
376
|
| `CalcitaAttachmentsInput` | `root`, `actions`, `button`, `list`, `fileChip`, `limits` |
|
|
325
377
|
| `CalcitaAuditTrailDialog` | `description` |
|
|
326
378
|
| `CalcitaAuthLayout` | `root`, `tools`, `body`, `brand`, `logo`, `brandName`, `card`, `description`, `content`, `footer` |
|
|
379
|
+
| `CalcitaBackButton` | `root` |
|
|
380
|
+
| `CalcitaBarActionButton` | `button`, `iconButton`, `menuItem` |
|
|
327
381
|
| `CalcitaBarActions` | `group` |
|
|
328
382
|
| `CalcitaBarCapsule` | `root` |
|
|
329
383
|
| `CalcitaBarList` | `row`, `chart` |
|
|
330
384
|
| `CalcitaBarSearch` | `field` |
|
|
331
385
|
| `CalcitaCalendarHeatmap` | `header`, `toggle`, `tableArea`, `dateCell`, `valueCell`, `descriptionCell`, `gridArea`, `grid`, `monthLabel`, `week`, `dayLabel`, `cell`, `legend`, `legendLess`, `legendCell`, `legendMore` |
|
|
386
|
+
| `CalcitaCatalogInput` | `field` |
|
|
332
387
|
| `CalcitaCfdiSummary` | `root`, `header`, `amounts`, `concepts` |
|
|
333
388
|
| `CalcitaChart` | `tableArea`, `legend`, `legendItem`, `legendLine`, `legendSwatch`, `reference`, `referenceLine` |
|
|
334
389
|
| `CalcitaChartTooltip` | `root` |
|
|
@@ -340,23 +395,27 @@ dejar Calcita con su propia cara, parte por parte.
|
|
|
340
395
|
| `CalcitaConfirmDialog` | `problem`, `actions` |
|
|
341
396
|
| `CalcitaContentDialog` | `header`, `title`, `closeButton`, `body` |
|
|
342
397
|
| `CalcitaCookieNotice` | `root`, `text`, `actions` |
|
|
398
|
+
| `CalcitaCopyButton` | `button` |
|
|
343
399
|
| `CalcitaCountdown` | `root` |
|
|
400
|
+
| `CalcitaDateInput` | `picker` |
|
|
344
401
|
| `CalcitaDateRangeFilter` | `chip`, `panel`, `actions` |
|
|
402
|
+
| `CalcitaDateTimeInput` | `picker` |
|
|
345
403
|
| `CalcitaDefinitionList` | `root`, `entry`, `label`, `value` |
|
|
346
404
|
| `CalcitaDemoTotpCode` | `root`, `code` |
|
|
347
405
|
| `CalcitaDetailHeader` | `root`, `titleArea`, `title`, `badges`, `actions` |
|
|
348
406
|
| `CalcitaDownloadManager` | `doneIcon`, `failedIcon`, `canceledIcon`, `cancelButton`, `progress`, `cancelIcon`, `item`, `itemText`, `itemStatus`, `itemActions`, `panel`, `header`, `title`, `list` |
|
|
349
407
|
| `CalcitaDownloadMenu` | `button` |
|
|
350
|
-
| `CalcitaEmptyState` | `root`, `icon` |
|
|
351
408
|
| `CalcitaEfirmaFields` | `root`, `certificate`, `verify` |
|
|
409
|
+
| `CalcitaEmptyState` | `root`, `icon` |
|
|
352
410
|
| `CalcitaErrorPage` | `root`, `actions` |
|
|
353
411
|
| `CalcitaEventCalendar` | `toolbar`, `navigation`, `title`, `calendar` |
|
|
354
412
|
| `CalcitaEventDetails` | `row`, `rowContent`, `titleRow`, `toneDot`, `linkRow`, `link`, `guestsTitle`, `guest`, `guestAvatar`, `guestName`, `notes`, `actions` |
|
|
413
|
+
| `CalcitaFieldFrame` | `root`, `label` |
|
|
355
414
|
| `CalcitaFileDropzone` | `root`, `fileArea`, `fileChip`, `hint`, `actions`, `limits`, `extra` |
|
|
356
415
|
| `CalcitaFileInput` | `root`, `button`, `fileChip`, `empty` |
|
|
357
416
|
| `CalcitaFileUploadInput` | `root`, `fileName`, `status`, `button`, `helper` |
|
|
358
417
|
| `CalcitaFilterChips` | `root`, `header`, `clearButton` |
|
|
359
|
-
| `CalcitaFilterDrawer` | `header`, `body`, `footer`
|
|
418
|
+
| `CalcitaFilterDrawer` | `header`, `body`, `footer` |
|
|
360
419
|
| `CalcitaFontSizeToggle` | `button` |
|
|
361
420
|
| `CalcitaFormProblemAlert` | `root` |
|
|
362
421
|
| `CalcitaFormRow` | `root`, `label`, `control` |
|
|
@@ -367,6 +426,7 @@ dejar Calcita con su propia cara, parte por parte.
|
|
|
367
426
|
| `CalcitaGroupedTimeline` | `group`, `groupHeader`, `groupButton`, `groupTitle`, `expandIcon`, `timeline`, `opposite`, `connector`, `content`, `entryTitle` |
|
|
368
427
|
| `CalcitaHeaderFilter` | `button`, `badge`, `panel` |
|
|
369
428
|
| `CalcitaHeaderTools` | `divider`, `root` |
|
|
429
|
+
| `CalcitaHelpButton` | `root` |
|
|
370
430
|
| `CalcitaHelpCarouselDialog` | `media`, `content`, `dots`, `actions` |
|
|
371
431
|
| `CalcitaIconBadge` | `root`, `icon`, `label` |
|
|
372
432
|
| `CalcitaImageInput` | `root`, `preview`, `image`, `placeholder`, `actions` |
|
|
@@ -383,32 +443,37 @@ dejar Calcita con su propia cara, parte por parte.
|
|
|
383
443
|
| `CalcitaMetricCard` | `root`, `action`, `header`, `icon`, `label`, `value`, `caption`, `extra`, `trend` |
|
|
384
444
|
| `CalcitaMobileTabBar` | `tab`, `tabIconPlaceholder`, `tabLabel`, `root`, `bar`, `minimized`, `account`, `sheetHeader` |
|
|
385
445
|
| `CalcitaModalWaiting` | `root`, `title`, `body`, `text` |
|
|
446
|
+
| `CalcitaMonthInput` | `picker` |
|
|
386
447
|
| `CalcitaMultiSelectFilter` | `list`, `option` |
|
|
387
448
|
| `CalcitaNewBuildNotice` | `floating`, `icon`, `message`, `reloadButton`, `banner` |
|
|
449
|
+
| `CalcitaNumberInput` | `field` |
|
|
388
450
|
| `CalcitaPageBreadcrumbs` | `root`, `link`, `current` |
|
|
389
451
|
| `CalcitaPageHeader` | `root`, `subtitle`, `action` |
|
|
390
452
|
| `CalcitaPageHeading` | `root`, `breadcrumbs`, `titleRow`, `title`, `actions`, `description` |
|
|
391
453
|
| `CalcitaPasswordChecklist` | `title`, `list`, `item`, `hidden` |
|
|
454
|
+
| `CalcitaPdfViewerDialog` | `actions`, `frame` |
|
|
392
455
|
| `CalcitaPeriodRangeSelector` | `field`, `select`, `root` |
|
|
393
456
|
| `CalcitaPermissionMatrix` | `permission`, `checkbox`, `group`, `groupHeader`, `groupTitle`, `grid` |
|
|
394
457
|
| `CalcitaPondDropzone` | `root`, `summary`, `limits`, `actions`, `extra` |
|
|
395
458
|
| `CalcitaProfileHeader` | `root`, `cover`, `body`, `avatar`, `text`, `name`, `chips`, `actions` |
|
|
396
459
|
| `CalcitaProfileScreen` | `sectionTitle`, `form`, `preferences` |
|
|
397
460
|
| `CalcitaQuickFilters` | `root`, `caption`, `chip`, `chipLabel`, `chipCount` |
|
|
398
|
-
| `CalcitaPdfViewerDialog` | `actions`, `frame` |
|
|
399
461
|
| `CalcitaRecordCard` | `root`, `header`, `avatar`, `titles`, `status`, `body`, `group`, `fact`, `highlight`, `footer`, `details` |
|
|
462
|
+
| `CalcitaRecordCardSkeleton` | `root` |
|
|
400
463
|
| `CalcitaRecoveryCodesDialog` | `codes`, `actions` |
|
|
401
|
-
| `CalcitaRouteTabs` | `root`, `tab`, `badge` |
|
|
402
|
-
| `CalcitaScrollableRow` | `root` |
|
|
403
464
|
| `CalcitaRepeatingFields` | `root`, `row`, `rowHeader`, `rowContent`, `addButton`, `helper` |
|
|
404
465
|
| `CalcitaRichTextEditor` | `root`, `toolbar`, `content`, `footer` |
|
|
405
466
|
| `CalcitaRichTextView` | `root` |
|
|
467
|
+
| `CalcitaRouteTabs` | `root`, `tab`, `badge` |
|
|
468
|
+
| `CalcitaScrollableRow` | `root` |
|
|
406
469
|
| `CalcitaSecondStepForm` | `actions` |
|
|
470
|
+
| `CalcitaSecretField` | `field` |
|
|
407
471
|
| `CalcitaSectionCard` | `root`, `header`, `title` |
|
|
408
472
|
| `CalcitaSegmentedControl` | `root` |
|
|
409
473
|
| `CalcitaSelectInput` | `option`, `optionText` |
|
|
410
474
|
| `CalcitaSelectionActions` | `bar`, `count`, `clear`, `phoneCount`, `phoneActions` |
|
|
411
|
-
| `CalcitaServerTable` | `toolbar`, `summary`, `customHeader`, `count`, `countRow`, `selectionNotice`, `error`, `card`, `grid`, `pagination` |
|
|
475
|
+
| `CalcitaServerTable` | `cards`, `cardsPagination`, `viewToggle`, `toolbar`, `summary`, `customHeader`, `count`, `countRow`, `selectionNotice`, `error`, `card`, `grid`, `pagination` |
|
|
476
|
+
| `CalcitaServerTableFilterDrawer` | `button` |
|
|
412
477
|
| `CalcitaSessionsCard` | `item`, `itemIcon`, `itemHeader`, `actions` |
|
|
413
478
|
| `CalcitaSettingsMenu` | `divider` |
|
|
414
479
|
| `CalcitaSideMenu` | `scrollArrow`, `root`, `brand`, `brandLogo`, `brandText`, `brandName`, `brandCaption`, `railTop`, `listArea`, `list`, `section`, `sectionTitle`, `sectionTitleText`, `footer`, `railBottom` |
|
|
@@ -416,19 +481,22 @@ dejar Calcita con su propia cara, parte por parte.
|
|
|
416
481
|
| `CalcitaSignInForm` | `footer` |
|
|
417
482
|
| `CalcitaSignInScreen` | `alert` |
|
|
418
483
|
| `CalcitaSortableFilterHeader` | `title`, `root`, `sortButton`, `sortIndicator`, `clearButton` |
|
|
484
|
+
| `CalcitaStackedCell` | `root`, `line`, `label`, `valueRow`, `primary`, `secondary`, `note` |
|
|
419
485
|
| `CalcitaStatusChip` | `root`, `dot`, `label`, `chip` |
|
|
420
486
|
| `CalcitaSubmenuActionsMenu` | `badge` |
|
|
487
|
+
| `CalcitaSubmitButton` | `root` |
|
|
421
488
|
| `CalcitaTabBarEditor` | `root`, `header`, `title`, `list`, `itemIcon`, `footer` |
|
|
422
489
|
| `CalcitaTableToggle` | `root`, `button` |
|
|
423
490
|
| `CalcitaTag` | `root` |
|
|
424
491
|
| `CalcitaTemplateDownloadButton` | `root` |
|
|
425
492
|
| `CalcitaTermsAcceptanceDialog` | `content`, `frame`, `footer` |
|
|
426
493
|
| `CalcitaTextInput` | `field` |
|
|
494
|
+
| `CalcitaTimeInput` | `picker` |
|
|
427
495
|
| `CalcitaTopMenu` | `link`, `root` |
|
|
428
496
|
| `CalcitaTransferListInput` | `root`, `list`, `actions` |
|
|
429
497
|
| `CalcitaTwoStepVerification` | `qr`, `secret`, `secretCode`, `header`, `headerTitle`, `headerText`, `menuIcon` |
|
|
430
498
|
| `CalcitaWizardLayout` | `stepper`, `phoneSteps`, `actions` |
|
|
431
|
-
| `CalcitaWordmark` | `root` |
|
|
499
|
+
| `CalcitaWordmark` | `root`, `mode` |
|
|
432
500
|
|
|
433
501
|
</details>
|
|
434
502
|
|
|
@@ -532,11 +600,16 @@ estatus, insignias, botón para abrirlo y menú «⋯»; en el cuerpo sus datos
|
|
|
532
600
|
principal grande; abajo lo que va a todo lo ancho y «Ver detalle», que despliega más. El orden es el que se da y nada se
|
|
533
601
|
reacomoda solo: para un orden obligatorio, `trailing` pone el estatus al final y `columns` fija el ancho de cada columna.
|
|
534
602
|
`RecordCardSkeleton` es su esqueleto. El menú «⋯» (`ActionsMenu`) acepta secciones, separadores, ligas, renglones propios y
|
|
535
|
-
submenús (`children`), que se abren dentro del mismo menú con «Regresar».
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
603
|
+
submenús (`children`), que se abren dentro del mismo menú con «Regresar». Un renglón con `to` es una pantalla de la app: va
|
|
604
|
+
con la navegación del proyecto, así el clic normal no recarga la app y Cmd/Ctrl+clic o el botón de en medio la abren en otra
|
|
605
|
+
pestaña (`href` es para lo de afuera). `onClick` recibe el evento del clic.
|
|
606
|
+
|
|
607
|
+
Para las secciones de un registro, `RouteTabs`: pestañas donde cada una es una dirección (se comparte la liga a una
|
|
608
|
+
pestaña); por ruta (cada pestaña su página, y «atrás» regresa a la anterior) o en la consulta (`mode="query"`, `?tab=pagos`,
|
|
609
|
+
con `useActiveRouteTab` para pintar el panel), con ícono, conteo y permiso por pestaña. Las de la consulta son vistas de la
|
|
610
|
+
misma ficha: por omisión reemplazan la entrada del historial, así «atrás» y el `BackButton` salen de la ficha en vez de pasear
|
|
611
|
+
por sus pestañas (`replace={false}` lo apaga; en las de ruta, `replace` lo prende). Las tres navegaciones de Calcita (la del
|
|
612
|
+
navegador, la de `#` y la de Next) y react-router saben reemplazar. Junto al título de un registro van su
|
|
540
613
|
estatus (`StatusChip`) y sus insignias (`IconBadge`): un ícono y un texto corto con el
|
|
541
614
|
color de lo que significan («Envío asegurado», «Pago con tarjeta»), con borde, tenue o sin caja. Mejor desde un diccionario:
|
|
542
615
|
la entrada trae su ícono (`dictionaryEntryOf(value, keyPrefix, color, {icon})`) y la insignia saca de ahí texto,
|
|
@@ -554,6 +627,42 @@ El marco avisa cuando la persona encoge el menú (`onMenuCollapse`), abre el men
|
|
|
554
627
|
sus pestañas (`onMobileTabsChange`), para guardar esas preferencias en el servidor del proyecto si se quiere; sus textos se
|
|
555
628
|
cambian con `texts`.
|
|
556
629
|
|
|
630
|
+
## La tabla de servidor (`/table`)
|
|
631
|
+
|
|
632
|
+
`ServerTable` pagina, filtra y ordena en el servidor (`fetchPage` recibe `{filters, sortOrders, page, pageSize}` y regresa
|
|
633
|
+
`{rows, total, facets}`); con `prefix` su estado va en la dirección. Lo que piden casi todas las listas de administración
|
|
634
|
+
viene listo:
|
|
635
|
+
|
|
636
|
+
```jsx
|
|
637
|
+
import {ACTIONS_COLUMN_FIELD, ServerTable, actionsColumn, stackedColumn, statusColumn} from '@stackcraftmx/calcita/table';
|
|
638
|
+
|
|
639
|
+
const columns = [
|
|
640
|
+
actionsColumn({label: 'Acciones', actionsOf: (row) => [{key: 'open', label: 'Abrir', to: `/clientes/${row.id}`}]}),
|
|
641
|
+
stackedColumn({field: 'business_name', label: 'Empresa', entries: (row) => [row.business_name, row.rfc, row.regime]}),
|
|
642
|
+
stackedColumn({field: 'created_at', label: 'Fechas', entries: (row) => [ // con etiqueta
|
|
643
|
+
{label: 'Alta', value: formatDate(row.created_at)}, {label: 'Último cambio', value: formatDate(row.updated_at)},
|
|
644
|
+
]}),
|
|
645
|
+
statusColumn({field: 'status', label: 'Estado', dictionary: REGISTRATION_STATUS, t}), // el ancho, medido
|
|
646
|
+
];
|
|
647
|
+
|
|
648
|
+
<ServerTable prefix="clientes" fetchPage={fetchClients} columns={columns} ariaLabel="Clientes"
|
|
649
|
+
fixedColumn={[ACTIONS_COLUMN_FIELD, 'business_name']} // fijas al deslizar
|
|
650
|
+
renderCard={(row) => <RecordCard title={row.business_name} … />} /> // también en tarjetas
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
- `stackedColumn`: los datos que se leen de corrido en una celda, uno debajo del otro, los que hagan falta (el primero en
|
|
654
|
+
negritas, los demás en gris). Cada dato va solo, si el encabezado ya dice qué son, o con su etiqueta arriba
|
|
655
|
+
(`{label, value, note}`) si el encabezado nombra al grupo. Un dato vacío no se pinta, salvo el principal o uno con
|
|
656
|
+
etiqueta, que enseñan «—» (`texts={{cells: {empty: '- - -'}}}` lo cambia). Los renglones toman su alto solos;
|
|
657
|
+
`getRowHeight` también acepta `'auto'`. `StackedCell` es la misma celda para un `renderCell` propio.
|
|
658
|
+
- `statusColumn`: el estatus desde su diccionario, con filtro de facetas y, con `t`, el ancho de su etiqueta más larga.
|
|
659
|
+
- `actionsColumn`: el «⋯» de cada renglón en su columna, de ancho fijo.
|
|
660
|
+
- `fixedColumn` acepta una lista: cada una se pega donde termina la anterior (las que no son la última llevan `width`).
|
|
661
|
+
- `renderCard`: la misma lista en tarjetas, con la misma búsqueda, filtros, orden y páginas; un botón cambia de vista y con
|
|
662
|
+
`prefix` la vista va en la dirección (`?clientes_view=cards`). `view`, `defaultView`, `onViewChange` y `showViewToggle`
|
|
663
|
+
para decidirlo desde la pantalla.
|
|
664
|
+
- `gridSx` se combina con los estilos de la tabla regla por regla; `gridProps` pasa lo demás al DataGrid.
|
|
665
|
+
|
|
557
666
|
## Eventos y textos de cada componente
|
|
558
667
|
|
|
559
668
|
Cada componente de la casa avisa hacia afuera todo lo que pasa adentro (`onSelect`, `onChange`, `onViewChange`,
|
|
@@ -644,6 +753,29 @@ location /ventas/ {
|
|
|
644
753
|
}
|
|
645
754
|
```
|
|
646
755
|
|
|
756
|
+
Si el prefijo lo decide el servidor al servir la página (Laravel con `config('app.url')`, un proxy con su ruta), se construye
|
|
757
|
+
UNA vez con archivos relativos (`ASSET_URL=./`) y la página trae `<base href="/ventas/">`: `documentBasePath()` lo lee al
|
|
758
|
+
abrirla (`null` si no hay `<base>`) y el proyecto de `calcita new` ya lo usa para el router y `publicUrlOf`.
|
|
759
|
+
|
|
760
|
+
## Direcciones con nombre y textos que se juntan solos
|
|
761
|
+
|
|
762
|
+
Cuando una pantalla lleva a la de otro módulo (el detalle de un cliente regresa a la lista de donde salió), cada módulo
|
|
763
|
+
publica sus direcciones en su archivo y un índice las junta; se piden por nombre, sin importar el otro módulo:
|
|
764
|
+
|
|
765
|
+
```js
|
|
766
|
+
// src/app/appPaths.js — el glob tiene que ser un texto literal: Vite lo resuelve al compilar
|
|
767
|
+
export const {pathOf} = createPathIndex(import.meta.glob('@/modules/**/paths.js', {eager: true}));
|
|
768
|
+
|
|
769
|
+
pathOf('admin:profile:detail', {groupId: 2, userId: 7675, tab: 'documentos'}) // '/admin/user/detail/2/7675?tab=documentos'
|
|
770
|
+
```
|
|
771
|
+
|
|
772
|
+
Lo que la ruta pide (`:userId`) se sustituye y lo que sobra va como consulta; un nombre que no existe o una parte que falta
|
|
773
|
+
truena y dice qué sí hay. Las direcciones del API de cada módulo, igual: `createPathIndex(glob, {exportName: 'uris'})`.
|
|
774
|
+
`fillPath(ruta, partes)` llena una suelta.
|
|
775
|
+
|
|
776
|
+
Los textos de cada módulo se juntan con `combineTexts`: por idioma y namespace, los objetos de adentro se suman
|
|
777
|
+
(`validation.fields` de dos módulos) y un texto repetido entre módulos truena en vez de pisarse.
|
|
778
|
+
|
|
647
779
|
## La petición de la casa
|
|
648
780
|
|
|
649
781
|
Todo lo que habla con el servidor pasa por un cliente, armado una vez por proyecto con `fetch` del navegador (sin axios):
|
|
@@ -872,6 +1004,35 @@ import {FormRow, FormSection} from '@stackcraftmx/calcita';
|
|
|
872
1004
|
Los campos van directo dentro de la `FormSection` (sin Grid ni Stack en medio): la línea entre renglones se dibuja entre
|
|
873
1005
|
hermanos.
|
|
874
1006
|
|
|
1007
|
+
### Mensajes de error propios (sin tocar el esquema)
|
|
1008
|
+
|
|
1009
|
+
Los mensajes de `buildYupLocale()` son llaves que se traducen al pintarse, y cada uno lleva **su regla y su campo**. Cuando
|
|
1010
|
+
el negocio pide un mensaje muy específico no hace falta tocar el esquema ni los mensajes de Calcita. El primero que exista
|
|
1011
|
+
gana:
|
|
1012
|
+
|
|
1013
|
+
1. el de la pantalla, en el campo: `<FormTextField name="rfc" messages={{matches: 'El RFC lleva 12 o 13 caracteres'}} />`;
|
|
1014
|
+
2. el de los textos del proyecto para ese campo y esa regla, en todas las pantallas:
|
|
1015
|
+
`{es: {validation: {fields: {rfc: {matches: 'El RFC lleva 12 o 13 caracteres'}}}}}` (dentro de una lista,
|
|
1016
|
+
`contacts[2].email` busca `contacts.email`);
|
|
1017
|
+
3. el general de Calcita (o el del esquema: `.required({key: 'clientes:rfcObligatorio'})`).
|
|
1018
|
+
|
|
1019
|
+
Los textos pueden decir el nombre del campo: `{{label}}` (el de `.label('RFC')` del esquema o, si no, la etiqueta del campo).
|
|
1020
|
+
|
|
1021
|
+
**Un sistema que ya existe** con su propio `setLocale` de yup: Calcita entiende sola `{key, values}`, `{msg, values}` (la de
|
|
1022
|
+
muchos sistemas con i18next), `{message: 'texto'}`, el error entero de react-hook-form y un texto ya escrito. Para otra
|
|
1023
|
+
forma, una función en el proveedor; los esquemas viejos entran sin reescribirse:
|
|
1024
|
+
|
|
1025
|
+
```jsx
|
|
1026
|
+
<ToolkitProvider readValidationMessage={(error) => (error?.codigo ? {key: `legado:${error.codigo}`, values: error.datos} : undefined)}>
|
|
1027
|
+
```
|
|
1028
|
+
|
|
1029
|
+
Una forma que no entiende no se pinta como «[object Object]»: sale el mensaje general y se avisa en la consola. Un campo
|
|
1030
|
+
propio del proyecto usa lo mismo con `useValidationText({label, messages})`.
|
|
1031
|
+
|
|
1032
|
+
Reglas listas, con su mensaje en el idioma activo: `sameAsFieldRule('password', {otherLabel: 'la contraseña'})` (confirmar
|
|
1033
|
+
una contraseña, una CLABE o un correo), `percentRule({max: 30})` y, en `/mexico`, `postalCodeRule()` junto a `rfcRule()`,
|
|
1034
|
+
`curpRule()` y `clabeRule()`.
|
|
1035
|
+
|
|
875
1036
|
### Campos con máscara
|
|
876
1037
|
|
|
877
1038
|
`MaskedInput` (y `FormMaskedField` en `/forms` y `/react-hook-form`) es el campo de texto de la casa con una máscara: mientras
|
package/cli/calcita.js
CHANGED
|
@@ -14,6 +14,7 @@ import {argv, cwd, exit, stdin, stdout} from 'node:process';
|
|
|
14
14
|
import {parseArgs} from 'node:util';
|
|
15
15
|
|
|
16
16
|
import {askQuestions} from './askQuestions.js';
|
|
17
|
+
import {checkTexts} from './checkTexts.js';
|
|
17
18
|
import {makeComponent} from './makeComponent.js';
|
|
18
19
|
import {makeForm} from './makeForm.js';
|
|
19
20
|
import {makeModule} from './makeModule.js';
|
|
@@ -33,6 +34,8 @@ const HELP = `Calcita: proyectos y esqueletos con la forma de los ejemplos.
|
|
|
33
34
|
calcita make:form <módulo> <Formulario> un formulario con sus reglas y sus textos
|
|
34
35
|
calcita make:component <Componente> un componente en src/core (lo usan varios módulos)
|
|
35
36
|
--module <módulo> … o solo de un módulo
|
|
37
|
+
calcita check:texts revisa que cada t('llave') exista en todos los idiomas, y que los idiomas tengan
|
|
38
|
+
las mismas llaves (lo que si no se descubre en la pantalla)
|
|
36
39
|
|
|
37
40
|
Opciones:
|
|
38
41
|
-y, --yes sin preguntas: lo recomendado, más lo que diga cada opción
|
|
@@ -125,6 +128,17 @@ async function runMakeComponent(componentName) {
|
|
|
125
128
|
stdout.write('\nListo.\n');
|
|
126
129
|
}
|
|
127
130
|
|
|
131
|
+
async function runCheckTexts() {
|
|
132
|
+
const {problems, checkedKeys, textFiles, languages} = await checkTexts({projectRoot: cwd()});
|
|
133
|
+
stdout.write(`Revisé ${checkedKeys} llaves escritas en el código contra ${textFiles.length} archivos de textos (${languages.join(', ') || 'sin idiomas'}).\n`);
|
|
134
|
+
if (problems.length === 0) {
|
|
135
|
+
stdout.write('Todo en su lugar.\n');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
stdout.write(`\n${problems.map((problem) => ` ✗ ${problem}`).join('\n')}\n`);
|
|
139
|
+
throw new CliError(`${problems.length} problema(s) con los textos.`);
|
|
140
|
+
}
|
|
141
|
+
|
|
128
142
|
const COMMANDS = Object.freeze({
|
|
129
143
|
'new': runNew,
|
|
130
144
|
'init': () => runNew('.'),
|
|
@@ -132,6 +146,7 @@ const COMMANDS = Object.freeze({
|
|
|
132
146
|
'make:page': runMakePage,
|
|
133
147
|
'make:form': runMakeForm,
|
|
134
148
|
'make:component': runMakeComponent,
|
|
149
|
+
'check:texts': runCheckTexts,
|
|
135
150
|
});
|
|
136
151
|
|
|
137
152
|
try {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// calcita check:texts — revisa los textos del proyecto sin abrir la app:
|
|
2
|
+
// 1. que cada t('llave') escrita tal cual en el código exista en todos los idiomas (con su namespace: el de
|
|
3
|
+
// useToolkitTranslation('orders') del archivo, o el que trae la llave, 'common:actions.save');
|
|
4
|
+
// 2. que cada idioma tenga las mismas llaves que los demás (lo que falta en inglés se pintaría en español… o como llave).
|
|
5
|
+
// Sin esto, una llave que no existe se descubre cuando alguien la ve en la pantalla («profile:tabs.documents»).
|
|
6
|
+
//
|
|
7
|
+
// Lo que NO revisa: las llaves armadas con una variable (t(`status.${value}`)) ni las que llevan defaultValue (son opcionales).
|
|
8
|
+
// Para esas está el aviso de Calcita en la consola (onMissingText de ToolkitProvider).
|
|
9
|
+
//
|
|
10
|
+
// Lee los archivos de textos del proyecto (src/**/*Texts.js y src/**/texts.js: cada uno exporta {es: {...}, en: {...}}) con
|
|
11
|
+
// node, así que no deben importar otros archivos; el que los junta (con import.meta.glob) se salta. Los namespaces de Calcita
|
|
12
|
+
// (common, table, validation…) salen de su dist/.
|
|
13
|
+
//
|
|
14
|
+
// Solo node, sin dependencias (el CLI corre antes de que el proyecto tenga nada instalado): function (regla de la casa).
|
|
15
|
+
// Pruebas en checkTexts.test.js.
|
|
16
|
+
import {readdir, readFile} from 'node:fs/promises';
|
|
17
|
+
import {join, relative} from 'node:path';
|
|
18
|
+
import {pathToFileURL} from 'node:url';
|
|
19
|
+
|
|
20
|
+
const SOURCE_EXTENSIONS = /\.(jsx?|tsx?)$/;
|
|
21
|
+
const TEST_FILE = /\.test\.(jsx?|tsx?)$/;
|
|
22
|
+
const TEXT_FILE = /(?:^|\/)(?:texts|[A-Za-z0-9]+Texts)\.js$/;
|
|
23
|
+
const IGNORED_FOLDERS = new Set(['node_modules', 'dist', 'coverage', '.git']);
|
|
24
|
+
// el namespace de un archivo: useToolkitTranslation('orders') o useToolkitTranslation(['orders', …])
|
|
25
|
+
const NAMESPACE_CALL = /useToolkitTranslation\(\s*\[?\s*(['"])([\w-]+)\1/g;
|
|
26
|
+
// una llave escrita tal cual: t('list.title') o t("common:actions.save"); con lo que sigue, para ver si trae defaultValue
|
|
27
|
+
const KEY_CALL = /\bt\(\s*(['"])([^'"`$\n]+?)\1([^)]{0,200})/g;
|
|
28
|
+
const PLURAL_SUFFIXES = ['', '_zero', '_one', '_two', '_few', '_many', '_other'];
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Los archivos de una carpeta y sus subcarpetas, sin node_modules ni dist.
|
|
32
|
+
*
|
|
33
|
+
* @param {string} folder
|
|
34
|
+
* @returns {Promise<string[]>}
|
|
35
|
+
*/
|
|
36
|
+
async function filesOf(folder) {
|
|
37
|
+
const entries = await readdir(folder, {withFileTypes: true}).catch(() => []);
|
|
38
|
+
const nested = await Promise.all(entries.map(async (entry) => {
|
|
39
|
+
const path = join(folder, entry.name);
|
|
40
|
+
if (entry.isDirectory()) {
|
|
41
|
+
return IGNORED_FOLDERS.has(entry.name) ? [] : filesOf(path);
|
|
42
|
+
}
|
|
43
|
+
return [path];
|
|
44
|
+
}));
|
|
45
|
+
return nested.flat();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Suma los textos de un archivo a los de todos (por idioma y namespace, objetos por dentro). Aquí no truena con un repetido:
|
|
50
|
+
* lo anota, para decirlo junto con lo demás.
|
|
51
|
+
*/
|
|
52
|
+
function addTexts(combined, fileTexts, duplicates, fileName) {
|
|
53
|
+
const mergeInto = (target, added, place) => {
|
|
54
|
+
for (const [key, value] of Object.entries(added)) {
|
|
55
|
+
const isObject = value && typeof value === 'object';
|
|
56
|
+
const keyPath = [...place.path, key];
|
|
57
|
+
if (isObject && target[key] && typeof target[key] === 'object') {
|
|
58
|
+
mergeInto(target[key], value, {...place, path: keyPath});
|
|
59
|
+
} else if (Object.hasOwn(target, key)) {
|
|
60
|
+
duplicates.push(`«${place.namespace}:${keyPath.join('.')}» en ${place.language} (otra vez en ${fileName})`);
|
|
61
|
+
} else {
|
|
62
|
+
target[key] = isObject ? structuredClone(value) : value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
for (const [language, namespaces] of Object.entries(fileTexts)) {
|
|
67
|
+
combined[language] ??= {};
|
|
68
|
+
for (const [namespace, keys] of Object.entries(namespaces ?? {})) {
|
|
69
|
+
combined[language][namespace] ??= {};
|
|
70
|
+
mergeInto(combined[language][namespace], keys, {language, namespace, path: []});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* ¿Parece un objeto de textos? ({es: {namespace: {...}}, en: {...}}: llaves de idioma con objetos adentro)
|
|
77
|
+
*
|
|
78
|
+
* @param {unknown} value
|
|
79
|
+
* @returns {boolean}
|
|
80
|
+
*/
|
|
81
|
+
function looksLikeTexts(value) {
|
|
82
|
+
return Boolean(value) && typeof value === 'object' && Object.keys(value).length > 0
|
|
83
|
+
&& Object.entries(value).every(([language, namespaces]) => /^[a-z]{2}(-[A-Z]{2})?$/.test(language) && namespaces && typeof namespaces === 'object');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Lo que hay en un camino con puntos ('list.title') dentro de un objeto. */
|
|
87
|
+
function valueAt(object, dottedKey) {
|
|
88
|
+
return dottedKey.split('.').reduce((current, step) => (current && typeof current === 'object' ? current[step] : undefined), object);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** ¿Existe la llave (o su plural) en ese namespace? */
|
|
92
|
+
function hasKey(namespaceTexts, key) {
|
|
93
|
+
return PLURAL_SUFFIXES.some((suffix) => typeof valueAt(namespaceTexts, `${key}${suffix}`) === 'string');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Todas las llaves con texto de un objeto, con puntos (para comparar idiomas); los plurales cuentan como su llave base. */
|
|
97
|
+
function leafKeysOf(object, prefix = '') {
|
|
98
|
+
return Object.entries(object ?? {}).flatMap(([key, value]) => {
|
|
99
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
100
|
+
if (value && typeof value === 'object') {
|
|
101
|
+
return leafKeysOf(value, path);
|
|
102
|
+
}
|
|
103
|
+
return [path.replace(/_(zero|one|two|few|many|other)$/, '')];
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Los textos de Calcita (common, table, validation…), del dist/ del paquete que corre este CLI. Sin dist/ (el repo del paquete
|
|
109
|
+
* sin construir), ninguno: las llaves de esos namespaces no se revisan.
|
|
110
|
+
*
|
|
111
|
+
* @returns {Promise<Record<string, object>>}
|
|
112
|
+
*/
|
|
113
|
+
async function packageTextsOf() {
|
|
114
|
+
const texts = {};
|
|
115
|
+
for (const language of ['es', 'en']) {
|
|
116
|
+
try {
|
|
117
|
+
const module = await import(new URL(`../dist/locales/${language}.js`, import.meta.url).href);
|
|
118
|
+
texts[language] = module.default ?? {};
|
|
119
|
+
} catch {
|
|
120
|
+
return {};
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return texts;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {object} options
|
|
128
|
+
* @param {string} options.projectRoot la carpeta del proyecto
|
|
129
|
+
* @param {string} [options.sourceFolder] dónde está el código (por omisión src)
|
|
130
|
+
* @param {Record<string, object>} [options.packageTexts] los textos de Calcita (por omisión, los de su dist/)
|
|
131
|
+
* @returns {Promise<{problems: string[], checkedKeys: number, textFiles: string[], languages: string[]}>}
|
|
132
|
+
*/
|
|
133
|
+
export async function checkTexts({projectRoot, sourceFolder = 'src', packageTexts}) {
|
|
134
|
+
const sourceRoot = join(projectRoot, sourceFolder);
|
|
135
|
+
const files = await filesOf(sourceRoot);
|
|
136
|
+
const problems = [];
|
|
137
|
+
|
|
138
|
+
// 1. los textos del proyecto
|
|
139
|
+
const projectTexts = {};
|
|
140
|
+
const duplicates = [];
|
|
141
|
+
const textFiles = files.filter((file) => TEXT_FILE.test(file.replaceAll('\\', '/')));
|
|
142
|
+
const readTextFiles = [];
|
|
143
|
+
for (const file of textFiles) {
|
|
144
|
+
const fileName = relative(projectRoot, file);
|
|
145
|
+
// el que JUNTA los textos (appTexts.js, con el glob de Vite) no es un archivo de textos: se salta
|
|
146
|
+
if ((await readFile(file, 'utf8')).includes('import.meta.glob')) {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
readTextFiles.push(fileName);
|
|
150
|
+
try {
|
|
151
|
+
const module = await import(pathToFileURL(file).href);
|
|
152
|
+
const exported = Object.values(module).filter(looksLikeTexts);
|
|
153
|
+
if (exported.length === 0) {
|
|
154
|
+
problems.push(`${fileName}: no exporta textos con la forma {es: {...}, en: {...}}`);
|
|
155
|
+
}
|
|
156
|
+
exported.forEach((fileTexts) => addTexts(projectTexts, fileTexts, duplicates, fileName));
|
|
157
|
+
} catch (error) {
|
|
158
|
+
problems.push(`${fileName}: no se pudo leer (${error.message.split('\n')[0]}); un archivo de textos no debe importar otros archivos`);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
problems.push(...duplicates.map((duplicate) => `texto repetido: ${duplicate}`));
|
|
162
|
+
|
|
163
|
+
const languages = Object.keys(projectTexts);
|
|
164
|
+
// 2. que cada idioma tenga las mismas llaves
|
|
165
|
+
for (const language of languages) {
|
|
166
|
+
for (const otherLanguage of languages.filter((other) => other !== language)) {
|
|
167
|
+
for (const namespace of Object.keys(projectTexts[language])) {
|
|
168
|
+
const otherKeys = new Set(leafKeysOf(projectTexts[otherLanguage][namespace]));
|
|
169
|
+
for (const key of new Set(leafKeysOf(projectTexts[language][namespace]))) {
|
|
170
|
+
if (!otherKeys.has(key)) {
|
|
171
|
+
problems.push(`«${namespace}:${key}» está en ${language} y falta en ${otherLanguage}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// 3. cada t('llave') escrita tal cual
|
|
179
|
+
const calcitaTexts = packageTexts ?? await packageTextsOf();
|
|
180
|
+
const textsOf = (language, namespace) => projectTexts[language]?.[namespace] ?? calcitaTexts[language]?.[namespace];
|
|
181
|
+
let checkedKeys = 0;
|
|
182
|
+
for (const file of files.filter((path) => SOURCE_EXTENSIONS.test(path) && !TEST_FILE.test(path))) {
|
|
183
|
+
const code = await readFile(file, 'utf8');
|
|
184
|
+
const fileNamespaces = [...code.matchAll(NAMESPACE_CALL)].map((match) => match[2]);
|
|
185
|
+
for (const match of code.matchAll(KEY_CALL)) {
|
|
186
|
+
const [, , key, rest] = match;
|
|
187
|
+
// con defaultValue la llave es opcional; una llave con espacios es un texto, no una llave
|
|
188
|
+
if (/defaultValue/.test(rest) || /\s/.test(key)) {
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const line = code.slice(0, match.index).split('\n').length;
|
|
192
|
+
const place = `${relative(projectRoot, file)}:${line}`;
|
|
193
|
+
const [explicitNamespace, plainKey] = key.includes(':') ? key.split(':', 2) : [null, key];
|
|
194
|
+
const candidates = explicitNamespace ? [explicitNamespace] : fileNamespaces;
|
|
195
|
+
if (candidates.length === 0) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
checkedKeys += 1;
|
|
199
|
+
for (const language of languages.length > 0 ? languages : ['es']) {
|
|
200
|
+
const found = candidates.some((namespace) => hasKey(textsOf(language, namespace), plainKey));
|
|
201
|
+
if (!found) {
|
|
202
|
+
problems.push(`${place} «${explicitNamespace ? key : `${candidates[0]}:${key}`}» no existe en ${language}`);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {problems, checkedKeys, textFiles: readTextFiles, languages};
|
|
209
|
+
}
|
package/cli/packageVersions.js
CHANGED
|
@@ -7,7 +7,7 @@ import {readFile} from 'node:fs/promises';
|
|
|
7
7
|
// Lo que el paquete no usa, así que no está en sus devDependencies: i18next (su adaptador recibe la instancia del proyecto;
|
|
8
8
|
// versión con más de dos semanas publicada al escogerla, pnpm espera un día antes de instalar una recién salida), qrcode
|
|
9
9
|
// (el QR de la cuenta de demostración; la misma versión que usan los ejemplos) y las pruebas de pantalla del proyecto
|
|
10
|
-
// (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19
|
|
10
|
+
// (Vitest con jsdom y Testing Library), en las versiones que ya se probaron juntas con el mismo Vite 7 y React 19, y ESLint
|
|
11
11
|
const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
|
|
12
12
|
i18next: '26.4.2',
|
|
13
13
|
qrcode: '1.5.4',
|
|
@@ -16,6 +16,11 @@ const VERSIONS_NOT_IN_PACKAGE = Object.freeze({
|
|
|
16
16
|
'@testing-library/dom': '10.4.2',
|
|
17
17
|
'@testing-library/react': '16.3.3',
|
|
18
18
|
'@testing-library/user-event': '14.6.7',
|
|
19
|
+
// ESLint del proyecto nuevo (pnpm lint): versiones con más de dos semanas publicadas al escogerlas (03/11/2026)
|
|
20
|
+
eslint: '10.13.0',
|
|
21
|
+
'@eslint/js': '10.0.1',
|
|
22
|
+
'eslint-plugin-react-hooks': '7.1.1',
|
|
23
|
+
globals: '17.13.0',
|
|
19
24
|
});
|
|
20
25
|
|
|
21
26
|
/**
|
package/cli/projectFiles.js
CHANGED
|
@@ -15,8 +15,10 @@ const BASE_DEPENDENCIES = Object.freeze([
|
|
|
15
15
|
const HOOK_FORM_DEPENDENCIES = Object.freeze(['@hookform/resolvers', 'react-hook-form']);
|
|
16
16
|
// la cuenta: las llaves de acceso (las pide @stackcraftmx/calcita/account) y el QR de la demostración
|
|
17
17
|
const ACCOUNT_DEPENDENCIES = Object.freeze(['@simplewebauthn/browser', 'qrcode']);
|
|
18
|
-
// las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona
|
|
18
|
+
// las pruebas de pantalla (pnpm test): Vitest con jsdom como navegador y Testing Library para picar como una persona; y ESLint
|
|
19
|
+
// (pnpm lint), que caza al escribir lo que si no se ve en la pantalla del cliente (una variable que no existe: «t is not defined»)
|
|
19
20
|
const DEV_DEPENDENCIES = Object.freeze([
|
|
21
|
+
'@eslint/js', 'eslint', 'eslint-plugin-react-hooks', 'globals',
|
|
20
22
|
'@testing-library/dom', '@testing-library/react', '@testing-library/user-event', '@vitejs/plugin-react', 'jsdom', 'vite', 'vitest',
|
|
21
23
|
]);
|
|
22
24
|
|
|
@@ -43,7 +45,7 @@ export function packageJsonOf({packageName, answers, calcitaPackage}) {
|
|
|
43
45
|
version: '0.1.0',
|
|
44
46
|
private: true,
|
|
45
47
|
type: 'module',
|
|
46
|
-
scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run'},
|
|
48
|
+
scripts: {dev: 'vite', build: 'vite build', preview: 'vite preview', test: 'vitest run', lint: 'eslint .', 'check:texts': 'calcita check:texts'},
|
|
47
49
|
dependencies: sortedByKey({'@stackcraftmx/calcita': `^${calcitaPackage.version}`, ...versionsOf(dependencyNames, calcitaPackage)}),
|
|
48
50
|
devDependencies: sortedByKey(versionsOf([...DEV_DEPENDENCIES], calcitaPackage)),
|
|
49
51
|
engines: {node: '>=22'},
|