@stackcraftmx/calcita 0.27.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -0
- package/dist/DataTable/AuditTrailDialog.js +18 -14
- package/dist/DataTable/AuditTrailDialog.js.map +1 -1
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js +12 -6
- package/dist/DataTable/ColumnFilters/HeaderFilterPopover.js.map +1 -1
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js +8 -3
- package/dist/DataTable/ColumnFilters/MultiSelectColumnFilter.js.map +1 -1
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js +26 -18
- package/dist/DataTable/ColumnFilters/SortableFilterHeader.js.map +1 -1
- package/dist/DataTable/EmptyState.js +8 -3
- package/dist/DataTable/EmptyState.js.map +1 -1
- package/dist/DataTable/FilterChips.js +11 -5
- package/dist/DataTable/FilterChips.js.map +1 -1
- package/dist/DataTable/GoToPage.js +9 -4
- package/dist/DataTable/GoToPage.js.map +1 -1
- package/dist/DataTable/ServerTable.js +74 -61
- package/dist/DataTable/ServerTable.js.map +1 -1
- package/dist/DataTable/columns.js +1 -8
- package/dist/DataTable/columns.js.map +1 -1
- package/dist/access/PermissionMatrix.js +18 -9
- package/dist/access/PermissionMatrix.js.map +1 -1
- package/dist/account/AccountSecuritySection.js +8 -3
- package/dist/account/AccountSecuritySection.js.map +1 -1
- package/dist/account/DemoTotpCodeButton.js +8 -3
- package/dist/account/DemoTotpCodeButton.js.map +1 -1
- package/dist/account/ProfileScreen.js +19 -13
- package/dist/account/ProfileScreen.js.map +1 -1
- package/dist/account/SignInScreen.js +6 -2
- package/dist/account/SignInScreen.js.map +1 -1
- package/dist/appShell/AccountMenu.js +13 -5
- package/dist/appShell/AccountMenu.js.map +1 -1
- package/dist/appShell/AppLayout.js +100 -71
- package/dist/appShell/AppLayout.js.map +1 -1
- package/dist/appShell/AppearanceSettings.js +21 -13
- package/dist/appShell/AppearanceSettings.js.map +1 -1
- package/dist/appShell/AuthLayout.js +26 -13
- package/dist/appShell/AuthLayout.js.map +1 -1
- package/dist/appShell/BarActions.js +10 -5
- package/dist/appShell/BarActions.js.map +1 -1
- package/dist/appShell/BarCapsule.js +9 -4
- package/dist/appShell/BarCapsule.js.map +1 -1
- package/dist/appShell/BarSearch.js +7 -3
- package/dist/appShell/BarSearch.js.map +1 -1
- package/dist/appShell/HeaderTools.js +20 -15
- package/dist/appShell/HeaderTools.js.map +1 -1
- package/dist/appShell/MobileTabBar.js +25 -14
- package/dist/appShell/MobileTabBar.js.map +1 -1
- package/dist/appShell/SettingsMenu.js +7 -3
- package/dist/appShell/SettingsMenu.js.map +1 -1
- package/dist/appShell/SideMenu.js +53 -35
- package/dist/appShell/SideMenu.js.map +1 -1
- package/dist/appShell/SideMenuEntry.js +18 -9
- package/dist/appShell/SideMenuEntry.js.map +1 -1
- package/dist/appShell/SubmenuActionsMenu.js +6 -2
- package/dist/appShell/SubmenuActionsMenu.js.map +1 -1
- package/dist/appShell/TabBarEditor.js +16 -7
- package/dist/appShell/TabBarEditor.js.map +1 -1
- package/dist/appShell/TopMenu.js +12 -7
- package/dist/appShell/TopMenu.js.map +1 -1
- package/dist/appShell/Wordmark.js +6 -2
- package/dist/appShell/Wordmark.js.map +1 -1
- package/dist/auth/PasswordChecklist.js +14 -7
- package/dist/auth/PasswordChecklist.js.map +1 -1
- package/dist/calendar/EventCalendar.js +26 -19
- package/dist/calendar/EventCalendar.js.map +1 -1
- package/dist/calendar/EventDetails.js +28 -13
- package/dist/calendar/EventDetails.js.map +1 -1
- package/dist/downloads/DownloadManager.js +34 -18
- package/dist/downloads/DownloadManager.js.map +1 -1
- package/dist/feedback/ConfirmDialog.js +22 -17
- package/dist/feedback/ConfirmDialog.js.map +1 -1
- package/dist/feedback/ModalWaiting.js +12 -5
- package/dist/feedback/ModalWaiting.js.map +1 -1
- package/dist/feedback/NewBuildNotice.js +16 -8
- package/dist/feedback/NewBuildNotice.js.map +1 -1
- package/dist/filepond/FileDropzone.js +39 -31
- package/dist/filepond/FileDropzone.js.map +1 -1
- package/dist/forms/FormProblemAlert.js +4 -1
- package/dist/forms/FormProblemAlert.js.map +1 -1
- package/dist/forms/auth/SignInForm.js +6 -2
- package/dist/forms/auth/SignInForm.js.map +1 -1
- package/dist/forms/security/SecondStepForm.js +6 -2
- package/dist/forms/security/SecondStepForm.js.map +1 -1
- package/dist/forms/security/TwoStepVerificationCard.js +19 -9
- package/dist/forms/security/TwoStepVerificationCard.js.map +1 -1
- package/dist/hookForm/FormProblemAlert.js +4 -1
- package/dist/hookForm/FormProblemAlert.js.map +1 -1
- package/dist/inbox/InboxLayout.js +47 -29
- package/dist/inbox/InboxLayout.js.map +1 -1
- package/dist/inbox/MessageList.js +53 -32
- package/dist/inbox/MessageList.js.map +1 -1
- package/dist/inbox/MessageView.js +31 -15
- package/dist/inbox/MessageView.js.map +1 -1
- package/dist/index.js +7 -1
- package/dist/index.js.map +1 -1
- package/dist/inputs/ColorInput.js +22 -17
- package/dist/inputs/ColorInput.js.map +1 -1
- package/dist/inputs/FormSection.js +66 -63
- package/dist/inputs/FormSection.js.map +1 -1
- package/dist/inputs/SelectInput.js +21 -16
- package/dist/inputs/SelectInput.js.map +1 -1
- package/dist/inputs/TextInput.js +25 -20
- package/dist/inputs/TextInput.js.map +1 -1
- package/dist/inputs/WizardLayout.js +21 -15
- package/dist/inputs/WizardLayout.js.map +1 -1
- package/dist/kanban/KanbanBoard.js +82 -50
- package/dist/kanban/KanbanBoard.js.map +1 -1
- package/dist/maps/MapView.js +19 -14
- package/dist/maps/MapView.js.map +1 -1
- package/dist/pwa/InstallAppSetting.js +4 -1
- package/dist/pwa/InstallAppSetting.js.map +1 -1
- package/dist/pwa/InstallInstructionsDialog.js +6 -2
- package/dist/pwa/InstallInstructionsDialog.js.map +1 -1
- package/dist/pwa/InstallPromptBanner.js +10 -5
- package/dist/pwa/InstallPromptBanner.js.map +1 -1
- package/dist/security/ActivityLog.js +6 -2
- package/dist/security/ActivityLog.js.map +1 -1
- package/dist/security/RecoveryCodesDialog.js +8 -3
- package/dist/security/RecoveryCodesDialog.js.map +1 -1
- package/dist/security/SessionsCard.js +12 -5
- package/dist/security/SessionsCard.js.map +1 -1
- package/dist/theme/components/glassComponents.js +19 -16
- package/dist/theme/components/glassComponents.js.map +1 -1
- package/dist/theme/components/minimalComponents.js +10 -6
- package/dist/theme/components/minimalComponents.js.map +1 -1
- package/dist/theme/createToolkitTheme.js +23 -6
- package/dist/theme/createToolkitTheme.js.map +1 -1
- package/dist/theme/cssTokens.js +23 -0
- package/dist/theme/cssTokens.js.map +1 -0
- package/dist/theme/glassSurface.js +64 -10
- package/dist/theme/glassSurface.js.map +1 -1
- package/dist/theme/glassToasterStyles.js +2 -3
- package/dist/theme/glassToasterStyles.js.map +1 -1
- package/dist/theme/themePieces.js +16 -0
- package/dist/theme/themePieces.js.map +1 -0
- package/dist/types/DataTable/AuditTrailDialog.d.ts +1 -1
- package/dist/types/DataTable/ColumnFilters/HeaderFilterPopover.d.ts +1 -1
- package/dist/types/DataTable/ColumnFilters/MultiSelectColumnFilter.d.ts +1 -1
- package/dist/types/DataTable/ColumnFilters/SortableFilterHeader.d.ts +1 -1
- package/dist/types/DataTable/EmptyState.d.ts +1 -1
- package/dist/types/DataTable/FilterChips.d.ts +1 -1
- package/dist/types/DataTable/GoToPage.d.ts +1 -1
- package/dist/types/DataTable/ServerTable.d.ts +1 -1
- package/dist/types/access/PermissionMatrix.d.ts +1 -1
- package/dist/types/account/AccountSecuritySection.d.ts +1 -1
- package/dist/types/account/DemoTotpCodeButton.d.ts +1 -1
- package/dist/types/account/ProfileScreen.d.ts +1 -1
- package/dist/types/account/SignInScreen.d.ts +1 -1
- package/dist/types/appShell/AccountMenu.d.ts +1 -1
- package/dist/types/appShell/AppLayout.d.ts +1 -1
- package/dist/types/appShell/AppearanceSettings.d.ts +1 -1
- package/dist/types/appShell/AuthLayout.d.ts +1 -1
- package/dist/types/appShell/BarActions.d.ts +1 -1
- package/dist/types/appShell/BarCapsule.d.ts +1 -1
- package/dist/types/appShell/BarSearch.d.ts +1 -1
- package/dist/types/appShell/HeaderTools.d.ts +1 -1
- package/dist/types/appShell/MobileTabBar.d.ts +1 -1
- package/dist/types/appShell/SettingsMenu.d.ts +1 -1
- package/dist/types/appShell/SideMenu.d.ts +1 -1
- package/dist/types/appShell/SideMenuEntry.d.ts +1 -1
- package/dist/types/appShell/SubmenuActionsMenu.d.ts +1 -1
- package/dist/types/appShell/TabBarEditor.d.ts +1 -1
- package/dist/types/appShell/TopMenu.d.ts +1 -1
- package/dist/types/appShell/Wordmark.d.ts +1 -1
- package/dist/types/auth/PasswordChecklist.d.ts +1 -1
- package/dist/types/calendar/EventCalendar.d.ts +1 -1
- package/dist/types/calendar/EventDetails.d.ts +1 -1
- package/dist/types/feedback/ConfirmDialog.d.ts +1 -1
- package/dist/types/feedback/ModalWaiting.d.ts +1 -1
- package/dist/types/feedback/NewBuildNotice.d.ts +1 -1
- package/dist/types/filepond/FileDropzone.d.ts +1 -1
- package/dist/types/forms/auth/SignInForm.d.ts +1 -1
- package/dist/types/forms/security/SecondStepForm.d.ts +1 -1
- package/dist/types/forms/security/TwoStepVerificationCard.d.ts +1 -1
- package/dist/types/inbox/InboxLayout.d.ts +1 -1
- package/dist/types/inbox/MessageList.d.ts +1 -1
- package/dist/types/inbox/MessageView.d.ts +1 -1
- package/dist/types/index.d.ts +2 -1
- package/dist/types/inputs/ColorInput.d.ts +1 -1
- package/dist/types/inputs/FormSection.d.ts +2 -2
- package/dist/types/inputs/SelectInput.d.ts +1 -1
- package/dist/types/inputs/TextInput.d.ts +1 -1
- package/dist/types/inputs/WizardLayout.d.ts +1 -1
- package/dist/types/kanban/KanbanBoard.d.ts +1 -1
- package/dist/types/maps/MapView.d.ts +1 -1
- package/dist/types/pwa/InstallInstructionsDialog.d.ts +1 -1
- package/dist/types/pwa/InstallPromptBanner.d.ts +1 -1
- package/dist/types/security/ActivityLog.d.ts +1 -1
- package/dist/types/security/RecoveryCodesDialog.d.ts +1 -1
- package/dist/types/security/SessionsCard.d.ts +1 -1
- package/dist/types/theme/createToolkitTheme.d.ts +33 -1
- package/dist/types/theme/cssTokens.d.ts +46 -0
- package/dist/types/theme/glassSurface.d.ts +57 -1
- package/dist/types/theme/themePieces.d.ts +20 -0
- package/dist/types/ui/actions/ActionsMenu.d.ts +1 -1
- package/dist/types/ui/actions/DownloadMenu.d.ts +1 -1
- package/dist/types/ui/actions/SelectionActions.d.ts +1 -1
- package/dist/types/ui/filters/DateRangeFilter.d.ts +1 -1
- package/dist/types/ui/filters/PeriodRangeSelector.d.ts +1 -1
- package/dist/types/ui/filters/QuickFilters.d.ts +1 -1
- package/dist/types/ui/filters/SegmentedControl.d.ts +7 -2
- package/dist/types/ui/labels/StatusChip.d.ts +1 -1
- package/dist/types/ui/labels/Tag.d.ts +1 -1
- package/dist/types/ui/layout/CollapsibleSection.d.ts +1 -1
- package/dist/types/ui/layout/ContentDialog.d.ts +1 -1
- package/dist/types/ui/layout/DefinitionList.d.ts +1 -1
- package/dist/types/ui/layout/DetailHeader.d.ts +1 -1
- package/dist/types/ui/layout/ErrorPage.d.ts +1 -1
- package/dist/types/ui/layout/GlassPanel.d.ts +1 -1
- package/dist/types/ui/layout/PageHeader.d.ts +1 -1
- package/dist/types/ui/layout/PageHeading.d.ts +1 -1
- package/dist/types/ui/layout/ProfileHeader.d.ts +1 -1
- package/dist/types/ui/layout/ScrollableRow.d.ts +1 -1
- package/dist/types/ui/layout/SectionCard.d.ts +1 -1
- package/dist/types/ui/metrics/BarList.d.ts +1 -1
- package/dist/types/ui/metrics/CalendarHeatmap.d.ts +1 -1
- package/dist/types/ui/metrics/ChartTooltip.d.ts +1 -1
- package/dist/types/ui/metrics/ColumnChart.d.ts +1 -1
- package/dist/types/ui/metrics/LineChart.d.ts +1 -1
- package/dist/types/ui/metrics/MetricCard.d.ts +1 -1
- package/dist/types/ui/metrics/TableToggle.d.ts +1 -1
- package/dist/types/ui/navigation/ColorSchemeToggle.d.ts +1 -1
- package/dist/types/ui/navigation/FontSizeToggle.d.ts +1 -1
- package/dist/types/ui/navigation/LanguageToggle.d.ts +1 -1
- package/dist/types/ui/navigation/PageBreadcrumbs.d.ts +1 -1
- package/dist/types/ui/timeline/GroupedTimeline.d.ts +1 -1
- package/dist/types/upload/FileDropzone.d.ts +1 -1
- package/dist/ui/actions/ActionsMenu.js +9 -4
- package/dist/ui/actions/ActionsMenu.js.map +1 -1
- package/dist/ui/actions/DownloadMenu.js +21 -17
- package/dist/ui/actions/DownloadMenu.js.map +1 -1
- package/dist/ui/actions/SelectionActions.js +15 -7
- package/dist/ui/actions/SelectionActions.js.map +1 -1
- package/dist/ui/filters/DateRangeFilter.js +11 -5
- package/dist/ui/filters/DateRangeFilter.js.map +1 -1
- package/dist/ui/filters/PeriodRangeSelector.js +11 -5
- package/dist/ui/filters/PeriodRangeSelector.js.map +1 -1
- package/dist/ui/filters/QuickFilters.js +16 -8
- package/dist/ui/filters/QuickFilters.js.map +1 -1
- package/dist/ui/filters/SegmentedControl.js +12 -5
- package/dist/ui/filters/SegmentedControl.js.map +1 -1
- package/dist/ui/labels/StatusChip.js +35 -25
- package/dist/ui/labels/StatusChip.js.map +1 -1
- package/dist/ui/labels/Tag.js +8 -3
- package/dist/ui/labels/Tag.js.map +1 -1
- package/dist/ui/layout/CollapsibleSection.js +18 -8
- package/dist/ui/layout/CollapsibleSection.js.map +1 -1
- package/dist/ui/layout/ContentDialog.js +13 -6
- package/dist/ui/layout/ContentDialog.js.map +1 -1
- package/dist/ui/layout/DefinitionList.js +13 -6
- package/dist/ui/layout/DefinitionList.js.map +1 -1
- package/dist/ui/layout/DetailHeader.js +14 -6
- package/dist/ui/layout/DetailHeader.js.map +1 -1
- package/dist/ui/layout/ErrorPage.js +20 -15
- package/dist/ui/layout/ErrorPage.js.map +1 -1
- package/dist/ui/layout/FullPageLoader.js +4 -1
- package/dist/ui/layout/FullPageLoader.js.map +1 -1
- package/dist/ui/layout/GlassPanel.js +8 -4
- package/dist/ui/layout/GlassPanel.js.map +1 -1
- package/dist/ui/layout/PageHeader.js +10 -4
- package/dist/ui/layout/PageHeader.js.map +1 -1
- package/dist/ui/layout/PageHeading.js +17 -8
- package/dist/ui/layout/PageHeading.js.map +1 -1
- package/dist/ui/layout/ProfileHeader.js +21 -10
- package/dist/ui/layout/ProfileHeader.js.map +1 -1
- package/dist/ui/layout/ScrollableRow.js +19 -15
- package/dist/ui/layout/ScrollableRow.js.map +1 -1
- package/dist/ui/layout/SectionCard.js +10 -4
- package/dist/ui/layout/SectionCard.js.map +1 -1
- package/dist/ui/metrics/BarList.js +9 -4
- package/dist/ui/metrics/BarList.js.map +1 -1
- package/dist/ui/metrics/CalendarHeatmap.js +40 -21
- package/dist/ui/metrics/CalendarHeatmap.js.map +1 -1
- package/dist/ui/metrics/ChartTooltip.js +6 -2
- package/dist/ui/metrics/ChartTooltip.js.map +1 -1
- package/dist/ui/metrics/ColumnChart.js +22 -18
- package/dist/ui/metrics/ColumnChart.js.map +1 -1
- package/dist/ui/metrics/LineChart.js +20 -16
- package/dist/ui/metrics/LineChart.js.map +1 -1
- package/dist/ui/metrics/MetricCard.js +42 -24
- package/dist/ui/metrics/MetricCard.js.map +1 -1
- package/dist/ui/metrics/TableToggle.js +7 -2
- package/dist/ui/metrics/TableToggle.js.map +1 -1
- package/dist/ui/metrics/chartParts.js +14 -5
- package/dist/ui/metrics/chartParts.js.map +1 -1
- package/dist/ui/navigation/ColorSchemeToggle.js +8 -4
- package/dist/ui/navigation/ColorSchemeToggle.js.map +1 -1
- package/dist/ui/navigation/FontSizeToggle.js +6 -2
- package/dist/ui/navigation/FontSizeToggle.js.map +1 -1
- package/dist/ui/navigation/LanguageToggle.js +8 -4
- package/dist/ui/navigation/LanguageToggle.js.map +1 -1
- package/dist/ui/navigation/PageBreadcrumbs.js +12 -6
- package/dist/ui/navigation/PageBreadcrumbs.js.map +1 -1
- package/dist/ui/timeline/GroupedTimeline.js +26 -13
- package/dist/ui/timeline/GroupedTimeline.js.map +1 -1
- package/dist/upload/FileDropzone.js +35 -25
- package/dist/upload/FileDropzone.js.map +1 -1
- package/package.json +1 -1
|
@@ -37,6 +37,15 @@ import CopyButton from "../../ui/actions/CopyButton.js";
|
|
|
37
37
|
import Tag from "../../ui/labels/Tag.js";
|
|
38
38
|
import SectionCard from "../../ui/layout/SectionCard.js";
|
|
39
39
|
import ComponentTexts from "../../toolkit/ComponentTexts.js";
|
|
40
|
+
import { piecePart, usePieceProps } from "../../theme/themePieces.js";
|
|
41
|
+
const THEME_NAME = "CalcitaTwoStepVerification";
|
|
42
|
+
const TwoStepQr = piecePart(THEME_NAME, "Qr", Box);
|
|
43
|
+
const TwoStepSecret = piecePart(THEME_NAME, "Secret", Stack);
|
|
44
|
+
const TwoStepSecretCode = piecePart(THEME_NAME, "SecretCode", Box);
|
|
45
|
+
const TwoStepHeader = piecePart(THEME_NAME, "Header", Stack);
|
|
46
|
+
const TwoStepHeaderTitle = piecePart(THEME_NAME, "HeaderTitle", Stack);
|
|
47
|
+
const TwoStepHeaderText = piecePart(THEME_NAME, "HeaderText", Box);
|
|
48
|
+
const TwoStepMenuIcon = piecePart(THEME_NAME, "MenuIcon", ListItemIcon);
|
|
40
49
|
const FEW_CODES_LEFT = 2;
|
|
41
50
|
const QR_SIZE_PIXELS = 200;
|
|
42
51
|
const CODE_FONT = "ui-monospace, SFMono-Regular, Menlo, monospace";
|
|
@@ -103,17 +112,17 @@ function TotpDialog({ setup, onConfirm, onClose }) {
|
|
|
103
112
|
/* @__PURE__ */ jsx(DialogContent, { children: /* @__PURE__ */ jsxs(Stack, { spacing: 2, children: [
|
|
104
113
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", children: t("twoStep.totpStep1") }),
|
|
105
114
|
setup?.qrImageUrl && /* @__PURE__ */ jsx(
|
|
106
|
-
|
|
115
|
+
TwoStepQr,
|
|
107
116
|
{
|
|
108
117
|
component: "img",
|
|
109
118
|
src: setup.qrImageUrl,
|
|
110
119
|
alt: t("twoStep.totpQrAlt"),
|
|
111
|
-
|
|
120
|
+
ownSx: { width: QR_SIZE_PIXELS, height: QR_SIZE_PIXELS, alignSelf: "center", borderRadius: 1, bgcolor: "common.white" }
|
|
112
121
|
}
|
|
113
122
|
),
|
|
114
123
|
/* @__PURE__ */ jsx(Typography, { variant: "caption", color: "text.secondary", children: t("twoStep.totpCantScan") }),
|
|
115
|
-
/* @__PURE__ */ jsxs(
|
|
116
|
-
/* @__PURE__ */ jsx(
|
|
124
|
+
/* @__PURE__ */ jsxs(TwoStepSecret, { direction: "row", spacing: 1, ownSx: { alignItems: "center" }, children: [
|
|
125
|
+
/* @__PURE__ */ jsx(TwoStepSecretCode, { component: "code", ownSx: { fontFamily: CODE_FONT, fontSize: 13, wordBreak: "break-all" }, children: setup?.secret }),
|
|
117
126
|
/* @__PURE__ */ jsx(CopyButton, { value: setup?.secret ?? "" })
|
|
118
127
|
] }),
|
|
119
128
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", children: t("twoStep.totpStep2") }),
|
|
@@ -179,10 +188,10 @@ function PasskeyDialog({ open, onAdd, onClose, notice }) {
|
|
|
179
188
|
) });
|
|
180
189
|
}
|
|
181
190
|
function SettingRow({ Icon, title, caption, captionColor = "text.secondary", action }) {
|
|
182
|
-
return /* @__PURE__ */ jsxs(
|
|
183
|
-
/* @__PURE__ */ jsxs(
|
|
191
|
+
return /* @__PURE__ */ jsxs(TwoStepHeader, { direction: "row", spacing: 1, ownSx: { alignItems: "center", justifyContent: "space-between" }, children: [
|
|
192
|
+
/* @__PURE__ */ jsxs(TwoStepHeaderTitle, { direction: "row", spacing: 1.5, ownSx: { alignItems: "center", minWidth: 0 }, children: [
|
|
184
193
|
/* @__PURE__ */ jsx(Icon, { color: "action" }),
|
|
185
|
-
/* @__PURE__ */ jsxs(
|
|
194
|
+
/* @__PURE__ */ jsxs(TwoStepHeaderText, { ownSx: { minWidth: 0 }, children: [
|
|
186
195
|
/* @__PURE__ */ jsx(Typography, { variant: "subtitle2", children: title }),
|
|
187
196
|
caption && /* @__PURE__ */ jsx(Typography, { variant: "caption", color: captionColor, component: "p", children: caption })
|
|
188
197
|
] })
|
|
@@ -190,7 +199,8 @@ function SettingRow({ Icon, title, caption, captionColor = "text.secondary", act
|
|
|
190
199
|
action
|
|
191
200
|
] });
|
|
192
201
|
}
|
|
193
|
-
function TwoStepVerificationCard(
|
|
202
|
+
function TwoStepVerificationCard(props) {
|
|
203
|
+
const { security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts } = usePieceProps(props, THEME_NAME);
|
|
194
204
|
const { t } = useToolkitTranslation("security", texts);
|
|
195
205
|
const { formatDateTime } = useFormatters();
|
|
196
206
|
const [dialog, setDialog] = useState(null);
|
|
@@ -242,7 +252,7 @@ function TwoStepVerificationCard({ security, onSecurityChange, actions, canUsePa
|
|
|
242
252
|
children: /* @__PURE__ */ jsx(DeleteOutlineOutlinedIcon, { fontSize: "small" })
|
|
243
253
|
}
|
|
244
254
|
) }), children: [
|
|
245
|
-
/* @__PURE__ */ jsx(
|
|
255
|
+
/* @__PURE__ */ jsx(TwoStepMenuIcon, { ownSx: { minWidth: 32 }, children: /* @__PURE__ */ jsx(VpnKeyOutlinedIcon, { fontSize: "small" }) }),
|
|
246
256
|
/* @__PURE__ */ jsx(
|
|
247
257
|
ListItemText,
|
|
248
258
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TwoStepVerificationCard.js","sources":["../../../src/forms/security/TwoStepVerificationCard.jsx"],"sourcesContent":["// «Verificación en dos pasos» de «Mi perfil»: las llaves de acceso, la app autenticadora y los códigos de recuperación.\n//\n// Lo que QUITA protección (desactivar la app, quitar una llave, generar códigos nuevos) pide la contraseña: una sesión que\n// alguien dejó abierta no debe bastar para desarmar la cuenta. Activar no la pide.\n//\n// La tarjeta no sabe del servidor: el proyecto le pasa el estado (security) y qué hacer en cada caso (actions); cada\n// acción regresa {security, recoveryCodes?} y la tarjeta avisa el estado nuevo con onSecurityChange. Las llaves de acceso\n// las crea el proyecto con createPasskey (@stackcraftmx/calcita/passkeys) dentro de actions.addPasskey: así esta puerta no\n// carga la librería de llaves.\n//\n// <TwoStepVerificationCard security={security} onSecurityChange={setSecurity} account={session.email}\n// canUsePasskeys={passkeysSupported()} actions={securityActions} />\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport VpnKeyOutlinedIcon from '@mui/icons-material/VpnKeyOutlined';\n\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n CURRENT_PASSWORD_MAX_LENGTH, PASSKEY_NAME_MAX_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport RecoveryCodesDialog from '@/security/RecoveryCodesDialog.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\n\n// los códigos que quedan desde donde se avisa en ámbar que ya son pocos\nconst FEW_CODES_LEFT = 2;\nconst QR_SIZE_PIXELS = 200;\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * Lo que hace un formulario de la tarjeta con un error: un ApiError va a su campo o arriba (applyServerProblem); un\n * error del navegador (la llave de acceso) va arriba; si la persona solo canceló, nada.\n *\n * @param {Error & {cancelled?: boolean, status?: number}} error\n * @param {object} formikHelpers\n * @param {object} values\n */\nfunction showFormError(error, formikHelpers, values) {\n if (error?.cancelled) {\n return;\n }\n if (error?.status === undefined) {\n formikHelpers.setStatus(error?.message);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n}\n\n/** Confirmar con la contraseña antes de quitar protección. */\nfunction PasswordDialog({open, title, description, confirmLabel, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const passwordSchema = Yup.object({password: Yup.string().max(CURRENT_PASSWORD_MAX_LENGTH).required(t('twoStep.passwordRequired'))});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{password: ''}} validationSchema={passwordSchema} enableReinitialize\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(values.password);\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{description}</DialogContentText>\n <FormProblemAlert />\n <FormTextField name=\"password\" label={t('twoStep.passwordLabel')} type=\"password\" autoComplete=\"current-password\" autoFocus />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" color=\"error\">{confirmLabel}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Activar la app: escanear el QR (o teclear la clave) y confirmar con un código. */\nfunction TotpDialog({setup, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const codeSchema = Yup.object({\n code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat')),\n });\n\n return (\n <Dialog open={Boolean(setup)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{code: ''}} validationSchema={codeSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(normalizeTotpCode(values.code));\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.totpDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{t('twoStep.totpStep1')}</Typography>\n {setup?.qrImageUrl && (\n <Box component=\"img\" src={setup.qrImageUrl} alt={t('twoStep.totpQrAlt')}\n sx={{width: QR_SIZE_PIXELS, height: QR_SIZE_PIXELS, alignSelf: 'center', borderRadius: 1, bgcolor: 'common.white'}} />\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">{t('twoStep.totpCantScan')}</Typography>\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center'}}>\n <Box component=\"code\" sx={{fontFamily: CODE_FONT, fontSize: 13, wordBreak: 'break-all'}}>{setup?.secret}</Box>\n <CopyButton value={setup?.secret ?? ''} />\n </Stack>\n <Typography variant=\"body2\">{t('twoStep.totpStep2')}</Typography>\n <FormProblemAlert />\n <FormTextField name=\"code\" label={t('twoStep.codeLabel')} autoComplete=\"one-time-code\" clearable={false}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n {setup?.extra}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\">{t('twoStep.activate')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Nombrar y crear una llave nueva (el navegador pide la huella, el rostro o el PIN); notice va arriba del nombre. */\nfunction PasskeyDialog({open, onAdd, onClose, notice}) {\n const {t} = useToolkitTranslation('security');\n const nameSchema = Yup.object({name: Yup.string().trim().max(PASSKEY_NAME_MAX_LENGTH)});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{name: ''}} validationSchema={nameSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onAdd(values.name.trim());\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.passkeyDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{t('twoStep.passkeyDialogText')}</DialogContentText>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('twoStep.passkeyName')} placeholder={t('twoStep.passkeyNamePlaceholder')} autoFocus\n slotProps={{htmlInput: {maxLength: PASSKEY_NAME_MAX_LENGTH}}} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" startIcon={<FingerprintOutlinedIcon />}>{t('twoStep.createPasskey')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/**\n * Un renglón de la tarjeta: ícono, nombre, explicación chica y su botón a la derecha.\n *\n * @param {{Icon: import('react').ElementType, title: string, caption?: import('react').ReactNode, captionColor?: string, action?: import('react').ReactNode}} props\n */\nfunction SettingRow({Icon, title, caption, captionColor = 'text.secondary', action}) {\n return (\n <Stack direction=\"row\" spacing={1} sx={{alignItems: 'center', justifyContent: 'space-between'}}>\n <Stack direction=\"row\" spacing={1.5} sx={{alignItems: 'center', minWidth: 0}}>\n <Icon color=\"action\" />\n <Box sx={{minWidth: 0}}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n {caption && <Typography variant=\"caption\" color={captionColor} component=\"p\">{caption}</Typography>}\n </Box>\n </Stack>\n {action}\n </Stack>\n );\n}\n\n/**\n * @param {object} props\n * @param {{isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}} props.security\n * @param {(security: {isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}) => void} props.onSecurityChange\n * @param {object} props.actions lo que hace el proyecto en cada caso; cada una regresa {security, recoveryCodes?}\n * @param {(name: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.addPasskey crea la llave\n * (createPasskey) y la guarda en el servidor\n * @param {(passkey: {id: string, name: string}, password: string) => Promise<{security: object}>} props.actions.removePasskey\n * @param {() => Promise<{secret: string, qrImageUrl: string, extra?: import('react').ReactNode}>} props.actions.startTotp\n * la clave y su QR; extra va abajo del campo (p. ej. un atajo de los ejemplos)\n * @param {(code: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.confirmTotp\n * @param {(password: string) => Promise<{security: object}>} props.actions.disableTotp\n * @param {(password: string) => Promise<{security: object, recoveryCodes: string[]}>} props.actions.regenerateRecoveryCodes\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys; sin él no se ofrece agregar llaves\n * @param {string} [props.account] la cuenta (el correo), para el archivo de los códigos\n * @param {import('react').ReactNode} [props.passkeyNotice] un aviso en el diálogo de crear la llave, antes de que el\n * navegador la guarde (p. ej. el de la demostración: la llave queda de verdad en el llavero de la persona)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function TwoStepVerificationCard({security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts}) {\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n // qué diálogo está abierto: uno a la vez\n const [dialog, setDialog] = useState(null);\n const [totpSetup, setTotpSetup] = useState(null);\n const [recoveryCodes, setRecoveryCodes] = useState(null);\n const [error, setError] = useState(null);\n\n const close = () => setDialog(null);\n // lo que regresa cada acción: el estado nuevo, los códigos si hubo, el aviso y cerrar el diálogo\n const apply = (result, message) => {\n onSecurityChange(result.security);\n if (result.recoveryCodes?.length) {\n setRecoveryCodes(result.recoveryCodes);\n }\n if (message) {\n toastSuccess(message);\n }\n close();\n };\n\n const startTotp = async () => {\n setError(null);\n try {\n setTotpSetup(await actions.startTotp());\n } catch (caught) {\n setError(caught.message);\n }\n };\n\n const passkeyBeingRemoved = dialog?.passkey;\n const codesLeft = security.recoveryCodesRemaining;\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('twoStep.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('twoStep.description')}</Typography>\n <Box>\n {security.isEnabled\n ? <Tag label={t('twoStep.active')} color=\"success\" tooltip={t('twoStep.activeSince', {date: formatDateTime(security.enabledAt)})} />\n : <Tag label={t('twoStep.inactive')} color=\"warning\" tooltip={t('twoStep.inactiveTooltip')} />}\n </Box>\n {error && <Alert severity=\"error\" onClose={() => setError(null)}>{error}</Alert>}\n\n <Divider />\n <SettingRow Icon={FingerprintOutlinedIcon} title={t('twoStep.passkeysTitle')}\n caption={security.passkeys.length === 0 ? (canUsePasskeys ? t('twoStep.passkeysEmpty') : t('passkeys.unsupported')) : null}\n action={canUsePasskeys && <Button size=\"small\" onClick={() => setDialog({kind: 'passkey'})}>{t('twoStep.add')}</Button>} />\n {security.passkeys.length > 0 && (\n <List dense disablePadding>\n {security.passkeys.map((passkey) => (\n <ListItem key={passkey.id} disableGutters secondaryAction={(\n <Tooltip title={t('twoStep.removePasskeyTooltip')}>\n <IconButton edge=\"end\" aria-label={t('twoStep.removePasskey', {name: passkey.name})}\n onClick={() => setDialog({kind: 'removePasskey', passkey})}>\n <DeleteOutlineOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}>\n <ListItemIcon sx={{minWidth: 32}}><VpnKeyOutlinedIcon fontSize=\"small\" /></ListItemIcon>\n <ListItemText primary={passkey.name}\n secondary={passkey.lastUsedAt\n ? t('twoStep.passkeyUsed', {date: formatDateTime(passkey.lastUsedAt)})\n : t('twoStep.passkeyCreated', {date: formatDateTime(passkey.createdAt)})} />\n </ListItem>\n ))}\n </List>\n )}\n\n <Divider />\n <SettingRow Icon={PhonelinkLockOutlinedIcon} title={t('twoStep.appTitle')}\n caption={security.totp.isEnabled ? t('twoStep.appActiveSince', {date: formatDateTime(security.totp.enabledAt)}) : t('twoStep.appDescription')}\n action={security.totp.isEnabled\n ? <Button size=\"small\" color=\"error\" onClick={() => setDialog({kind: 'disableTotp'})}>{t('twoStep.deactivate')}</Button>\n : <Button size=\"small\" onClick={startTotp}>{t('twoStep.activate')}</Button>} />\n\n {/* los códigos de recuperación solo existen con la verificación activa */}\n {security.isEnabled && (\n <>\n <Divider />\n <SettingRow Icon={VpnKeyOutlinedIcon} title={t('twoStep.codesTitle')}\n caption={codesLeft ? t('twoStep.codesRemaining', {count: codesLeft}) : t('twoStep.codesNone')}\n captionColor={codesLeft <= FEW_CODES_LEFT ? 'warning.main' : 'text.secondary'}\n action={<Button size=\"small\" onClick={() => setDialog({kind: 'regenerateCodes'})}>{t('twoStep.regenerate')}</Button>} />\n </>\n )}\n </Stack>\n\n <PasskeyDialog open={dialog?.kind === 'passkey'} onClose={close} notice={passkeyNotice}\n onAdd={async (name) => apply(await actions.addPasskey(name), t('twoStep.passkeyAdded'))} />\n <TotpDialog setup={totpSetup} onClose={() => setTotpSetup(null)}\n onConfirm={async (code) => {\n const result = await actions.confirmTotp(code);\n setTotpSetup(null);\n apply(result, t('twoStep.appActivated'));\n }} />\n <PasswordDialog open={dialog?.kind === 'disableTotp'} onClose={close} title={t('twoStep.disableTitle')}\n description={t('twoStep.disableText')} confirmLabel={t('twoStep.deactivate')}\n onConfirm={async (password) => apply(await actions.disableTotp(password), t('twoStep.appDeactivated'))} />\n <PasswordDialog open={dialog?.kind === 'removePasskey'} onClose={close}\n title={t('twoStep.removeTitle', {name: passkeyBeingRemoved?.name ?? ''})} description={t('twoStep.removeText')}\n confirmLabel={t('twoStep.remove')}\n onConfirm={async (password) => apply(await actions.removePasskey(passkeyBeingRemoved, password), t('twoStep.passkeyRemoved'))} />\n <PasswordDialog open={dialog?.kind === 'regenerateCodes'} onClose={close} title={t('twoStep.regenerateTitle')}\n description={t('twoStep.regenerateText')} confirmLabel={t('twoStep.generate')}\n onConfirm={async (password) => apply(await actions.regenerateRecoveryCodes(password))} />\n <RecoveryCodesDialog codes={recoveryCodes} account={account} onClose={() => setRecoveryCodes(null)} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TwoStepVerificationCard.js","sources":["../../../src/forms/security/TwoStepVerificationCard.jsx"],"sourcesContent":["// «Verificación en dos pasos» de «Mi perfil»: las llaves de acceso, la app autenticadora y los códigos de recuperación.\n//\n// Lo que QUITA protección (desactivar la app, quitar una llave, generar códigos nuevos) pide la contraseña: una sesión que\n// alguien dejó abierta no debe bastar para desarmar la cuenta. Activar no la pide.\n//\n// La tarjeta no sabe del servidor: el proyecto le pasa el estado (security) y qué hacer en cada caso (actions); cada\n// acción regresa {security, recoveryCodes?} y la tarjeta avisa el estado nuevo con onSecurityChange. Las llaves de acceso\n// las crea el proyecto con createPasskey (@stackcraftmx/calcita/passkeys) dentro de actions.addPasskey: así esta puerta no\n// carga la librería de llaves.\n//\n// <TwoStepVerificationCard security={security} onSecurityChange={setSecurity} account={session.email}\n// canUsePasskeys={passkeysSupported()} actions={securityActions} />\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaTwoStepVerification con defaultProps y\n// styleOverrides de sus partes: qr, secret, secretCode, header, headerTitle, headerText, menuIcon.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useState} from 'react';\nimport {Form, Formik} from 'formik';\nimport Alert from '@mui/material/Alert';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport Dialog from '@mui/material/Dialog';\nimport DialogActions from '@mui/material/DialogActions';\nimport DialogContent from '@mui/material/DialogContent';\nimport DialogContentText from '@mui/material/DialogContentText';\nimport DialogTitle from '@mui/material/DialogTitle';\nimport Divider from '@mui/material/Divider';\nimport IconButton from '@mui/material/IconButton';\nimport List from '@mui/material/List';\nimport ListItem from '@mui/material/ListItem';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport Stack from '@mui/material/Stack';\nimport Tooltip from '@mui/material/Tooltip';\nimport Typography from '@mui/material/Typography';\nimport DeleteOutlineOutlinedIcon from '@mui/icons-material/DeleteOutlineOutlined';\nimport FingerprintOutlinedIcon from '@mui/icons-material/FingerprintOutlined';\nimport PhonelinkLockOutlinedIcon from '@mui/icons-material/PhonelinkLockOutlined';\nimport VpnKeyOutlinedIcon from '@mui/icons-material/VpnKeyOutlined';\n\nimport {toastSuccess} from '@/feedback/toast.jsx';\nimport FormProblemAlert from '@/forms/FormProblemAlert.jsx';\nimport FormTextField from '@/forms/FormTextField.jsx';\nimport SubmitButton from '@/forms/SubmitButton.jsx';\nimport {applyServerProblem} from '@/forms/applyServerProblem.js';\nimport {Yup} from '@/forms/validation.js';\nimport {\n CURRENT_PASSWORD_MAX_LENGTH, PASSKEY_NAME_MAX_LENGTH, TOTP_CODE_MAX_LENGTH, TOTP_CODE_MIN_LENGTH, TOTP_CODE_PATTERN, normalizeTotpCode,\n} from '@/forms/security/securityRules.js';\nimport {useFormatters} from '@/toolkit/useFormatters.js';\nimport RecoveryCodesDialog from '@/security/RecoveryCodesDialog.jsx';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport CopyButton from '@/ui/actions/CopyButton.jsx';\nimport Tag from '@/ui/labels/Tag.jsx';\nimport SectionCard from '@/ui/layout/SectionCard.jsx';\nimport ComponentTexts from '@/toolkit/ComponentTexts.jsx';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaTwoStepVerification)\nconst THEME_NAME = 'CalcitaTwoStepVerification';\nconst TwoStepQr = piecePart(THEME_NAME, 'Qr', Box);\nconst TwoStepSecret = piecePart(THEME_NAME, 'Secret', Stack);\nconst TwoStepSecretCode = piecePart(THEME_NAME, 'SecretCode', Box);\nconst TwoStepHeader = piecePart(THEME_NAME, 'Header', Stack);\nconst TwoStepHeaderTitle = piecePart(THEME_NAME, 'HeaderTitle', Stack);\nconst TwoStepHeaderText = piecePart(THEME_NAME, 'HeaderText', Box);\nconst TwoStepMenuIcon = piecePart(THEME_NAME, 'MenuIcon', ListItemIcon);\n\n// los códigos que quedan desde donde se avisa en ámbar que ya son pocos\nconst FEW_CODES_LEFT = 2;\nconst QR_SIZE_PIXELS = 200;\nconst CODE_FONT = 'ui-monospace, SFMono-Regular, Menlo, monospace';\n\n/**\n * Lo que hace un formulario de la tarjeta con un error: un ApiError va a su campo o arriba (applyServerProblem); un\n * error del navegador (la llave de acceso) va arriba; si la persona solo canceló, nada.\n *\n * @param {Error & {cancelled?: boolean, status?: number}} error\n * @param {object} formikHelpers\n * @param {object} values\n */\nfunction showFormError(error, formikHelpers, values) {\n if (error?.cancelled) {\n return;\n }\n if (error?.status === undefined) {\n formikHelpers.setStatus(error?.message);\n return;\n }\n applyServerProblem(error, {...formikHelpers, values});\n}\n\n/** Confirmar con la contraseña antes de quitar protección. */\nfunction PasswordDialog({open, title, description, confirmLabel, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const passwordSchema = Yup.object({password: Yup.string().max(CURRENT_PASSWORD_MAX_LENGTH).required(t('twoStep.passwordRequired'))});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{password: ''}} validationSchema={passwordSchema} enableReinitialize\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(values.password);\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{description}</DialogContentText>\n <FormProblemAlert />\n <FormTextField name=\"password\" label={t('twoStep.passwordLabel')} type=\"password\" autoComplete=\"current-password\" autoFocus />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" color=\"error\">{confirmLabel}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Activar la app: escanear el QR (o teclear la clave) y confirmar con un código. */\nfunction TotpDialog({setup, onConfirm, onClose}) {\n const {t} = useToolkitTranslation('security');\n const codeSchema = Yup.object({\n code: Yup.string().trim().required(t('twoStep.codeRequired')).min(TOTP_CODE_MIN_LENGTH).max(TOTP_CODE_MAX_LENGTH)\n .matches(TOTP_CODE_PATTERN, t('twoStep.codeFormat')),\n });\n\n return (\n <Dialog open={Boolean(setup)} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{code: ''}} validationSchema={codeSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onConfirm(normalizeTotpCode(values.code));\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.totpDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <Typography variant=\"body2\">{t('twoStep.totpStep1')}</Typography>\n {setup?.qrImageUrl && (\n <TwoStepQr component=\"img\" src={setup.qrImageUrl} alt={t('twoStep.totpQrAlt')}\n ownSx={{width: QR_SIZE_PIXELS, height: QR_SIZE_PIXELS, alignSelf: 'center', borderRadius: 1, bgcolor: 'common.white'}} />\n )}\n <Typography variant=\"caption\" color=\"text.secondary\">{t('twoStep.totpCantScan')}</Typography>\n <TwoStepSecret direction=\"row\" spacing={1} ownSx={{alignItems: 'center'}}>\n <TwoStepSecretCode component=\"code\" ownSx={{fontFamily: CODE_FONT, fontSize: 13, wordBreak: 'break-all'}}>{setup?.secret}</TwoStepSecretCode>\n <CopyButton value={setup?.secret ?? ''} />\n </TwoStepSecret>\n <Typography variant=\"body2\">{t('twoStep.totpStep2')}</Typography>\n <FormProblemAlert />\n <FormTextField name=\"code\" label={t('twoStep.codeLabel')} autoComplete=\"one-time-code\" clearable={false}\n slotProps={{htmlInput: {inputMode: 'numeric', maxLength: TOTP_CODE_MAX_LENGTH}}} />\n {setup?.extra}\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\">{t('twoStep.activate')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/** Nombrar y crear una llave nueva (el navegador pide la huella, el rostro o el PIN); notice va arriba del nombre. */\nfunction PasskeyDialog({open, onAdd, onClose, notice}) {\n const {t} = useToolkitTranslation('security');\n const nameSchema = Yup.object({name: Yup.string().trim().max(PASSKEY_NAME_MAX_LENGTH)});\n\n return (\n <Dialog open={open} onClose={onClose} maxWidth=\"xs\" fullWidth>\n <Formik initialValues={{name: ''}} validationSchema={nameSchema}\n onSubmit={async (values, helpers) => {\n try {\n await onAdd(values.name.trim());\n } catch (error) {\n showFormError(error, helpers, values);\n }\n }}>\n {() => (\n <Form noValidate>\n <DialogTitle>{t('twoStep.passkeyDialogTitle')}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>{t('twoStep.passkeyDialogText')}</DialogContentText>\n {notice && <Alert severity=\"info\">{notice}</Alert>}\n <FormProblemAlert />\n <FormTextField name=\"name\" label={t('twoStep.passkeyName')} placeholder={t('twoStep.passkeyNamePlaceholder')} autoFocus\n slotProps={{htmlInput: {maxLength: PASSKEY_NAME_MAX_LENGTH}}} />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={onClose}>{t('common:actions.cancel')}</Button>\n <SubmitButton size=\"medium\" startIcon={<FingerprintOutlinedIcon />}>{t('twoStep.createPasskey')}</SubmitButton>\n </DialogActions>\n </Form>\n )}\n </Formik>\n </Dialog>\n );\n}\n\n/**\n * Un renglón de la tarjeta: ícono, nombre, explicación chica y su botón a la derecha.\n *\n * @param {{Icon: import('react').ElementType, title: string, caption?: import('react').ReactNode, captionColor?: string, action?: import('react').ReactNode}} props\n */\nfunction SettingRow({Icon, title, caption, captionColor = 'text.secondary', action}) {\n return (\n <TwoStepHeader direction=\"row\" spacing={1} ownSx={{alignItems: 'center', justifyContent: 'space-between'}}>\n <TwoStepHeaderTitle direction=\"row\" spacing={1.5} ownSx={{alignItems: 'center', minWidth: 0}}>\n <Icon color=\"action\" />\n <TwoStepHeaderText ownSx={{minWidth: 0}}>\n <Typography variant=\"subtitle2\">{title}</Typography>\n {caption && <Typography variant=\"caption\" color={captionColor} component=\"p\">{caption}</Typography>}\n </TwoStepHeaderText>\n </TwoStepHeaderTitle>\n {action}\n </TwoStepHeader>\n );\n}\n\n/**\n * @param {object} props\n * @param {{isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}} props.security\n * @param {(security: {isEnabled: boolean, enabledAt?: string|null, passkeys: Array<{id: string, name: string, createdAt: string, lastUsedAt?: string|null}>, totp: {isEnabled: boolean, enabledAt?: string|null}, recoveryCodesRemaining: number}) => void} props.onSecurityChange\n * @param {object} props.actions lo que hace el proyecto en cada caso; cada una regresa {security, recoveryCodes?}\n * @param {(name: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.addPasskey crea la llave\n * (createPasskey) y la guarda en el servidor\n * @param {(passkey: {id: string, name: string}, password: string) => Promise<{security: object}>} props.actions.removePasskey\n * @param {() => Promise<{secret: string, qrImageUrl: string, extra?: import('react').ReactNode}>} props.actions.startTotp\n * la clave y su QR; extra va abajo del campo (p. ej. un atajo de los ejemplos)\n * @param {(code: string) => Promise<{security: object, recoveryCodes?: string[]}>} props.actions.confirmTotp\n * @param {(password: string) => Promise<{security: object}>} props.actions.disableTotp\n * @param {(password: string) => Promise<{security: object, recoveryCodes: string[]}>} props.actions.regenerateRecoveryCodes\n * @param {boolean} [props.canUsePasskeys] passkeysSupported() de /passkeys; sin él no se ofrece agregar llaves\n * @param {string} [props.account] la cuenta (el correo), para el archivo de los códigos\n * @param {import('react').ReactNode} [props.passkeyNotice] un aviso en el diálogo de crear la llave, antes de que el\n * navegador la guarde (p. ej. el de la demostración: la llave queda de verdad en el llavero de la persona)\n * @param {object} [props.texts] los textos que cambian, con la forma de `security` en los textos del paquete\n */\nexport default function TwoStepVerificationCard(props) {\n // lo que el proyecto puso en su tema (components.CalcitaTwoStepVerification.defaultProps) y encima lo que llega aquí\n const {security, onSecurityChange, actions, canUsePasskeys = false, account, passkeyNotice, texts} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('security', texts);\n const {formatDateTime} = useFormatters();\n // qué diálogo está abierto: uno a la vez\n const [dialog, setDialog] = useState(null);\n const [totpSetup, setTotpSetup] = useState(null);\n const [recoveryCodes, setRecoveryCodes] = useState(null);\n const [error, setError] = useState(null);\n\n const close = () => setDialog(null);\n // lo que regresa cada acción: el estado nuevo, los códigos si hubo, el aviso y cerrar el diálogo\n const apply = (result, message) => {\n onSecurityChange(result.security);\n if (result.recoveryCodes?.length) {\n setRecoveryCodes(result.recoveryCodes);\n }\n if (message) {\n toastSuccess(message);\n }\n close();\n };\n\n const startTotp = async () => {\n setError(null);\n try {\n setTotpSetup(await actions.startTotp());\n } catch (caught) {\n setError(caught.message);\n }\n };\n\n const passkeyBeingRemoved = dialog?.passkey;\n const codesLeft = security.recoveryCodesRemaining;\n\n return (\n <ComponentTexts namespace=\"security\" texts={texts}>\n <SectionCard title={t('twoStep.title')}>\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">{t('twoStep.description')}</Typography>\n <Box>\n {security.isEnabled\n ? <Tag label={t('twoStep.active')} color=\"success\" tooltip={t('twoStep.activeSince', {date: formatDateTime(security.enabledAt)})} />\n : <Tag label={t('twoStep.inactive')} color=\"warning\" tooltip={t('twoStep.inactiveTooltip')} />}\n </Box>\n {error && <Alert severity=\"error\" onClose={() => setError(null)}>{error}</Alert>}\n\n <Divider />\n <SettingRow Icon={FingerprintOutlinedIcon} title={t('twoStep.passkeysTitle')}\n caption={security.passkeys.length === 0 ? (canUsePasskeys ? t('twoStep.passkeysEmpty') : t('passkeys.unsupported')) : null}\n action={canUsePasskeys && <Button size=\"small\" onClick={() => setDialog({kind: 'passkey'})}>{t('twoStep.add')}</Button>} />\n {security.passkeys.length > 0 && (\n <List dense disablePadding>\n {security.passkeys.map((passkey) => (\n <ListItem key={passkey.id} disableGutters secondaryAction={(\n <Tooltip title={t('twoStep.removePasskeyTooltip')}>\n <IconButton edge=\"end\" aria-label={t('twoStep.removePasskey', {name: passkey.name})}\n onClick={() => setDialog({kind: 'removePasskey', passkey})}>\n <DeleteOutlineOutlinedIcon fontSize=\"small\" />\n </IconButton>\n </Tooltip>\n )}>\n <TwoStepMenuIcon ownSx={{minWidth: 32}}><VpnKeyOutlinedIcon fontSize=\"small\" /></TwoStepMenuIcon>\n <ListItemText primary={passkey.name}\n secondary={passkey.lastUsedAt\n ? t('twoStep.passkeyUsed', {date: formatDateTime(passkey.lastUsedAt)})\n : t('twoStep.passkeyCreated', {date: formatDateTime(passkey.createdAt)})} />\n </ListItem>\n ))}\n </List>\n )}\n\n <Divider />\n <SettingRow Icon={PhonelinkLockOutlinedIcon} title={t('twoStep.appTitle')}\n caption={security.totp.isEnabled ? t('twoStep.appActiveSince', {date: formatDateTime(security.totp.enabledAt)}) : t('twoStep.appDescription')}\n action={security.totp.isEnabled\n ? <Button size=\"small\" color=\"error\" onClick={() => setDialog({kind: 'disableTotp'})}>{t('twoStep.deactivate')}</Button>\n : <Button size=\"small\" onClick={startTotp}>{t('twoStep.activate')}</Button>} />\n\n {/* los códigos de recuperación solo existen con la verificación activa */}\n {security.isEnabled && (\n <>\n <Divider />\n <SettingRow Icon={VpnKeyOutlinedIcon} title={t('twoStep.codesTitle')}\n caption={codesLeft ? t('twoStep.codesRemaining', {count: codesLeft}) : t('twoStep.codesNone')}\n captionColor={codesLeft <= FEW_CODES_LEFT ? 'warning.main' : 'text.secondary'}\n action={<Button size=\"small\" onClick={() => setDialog({kind: 'regenerateCodes'})}>{t('twoStep.regenerate')}</Button>} />\n </>\n )}\n </Stack>\n\n <PasskeyDialog open={dialog?.kind === 'passkey'} onClose={close} notice={passkeyNotice}\n onAdd={async (name) => apply(await actions.addPasskey(name), t('twoStep.passkeyAdded'))} />\n <TotpDialog setup={totpSetup} onClose={() => setTotpSetup(null)}\n onConfirm={async (code) => {\n const result = await actions.confirmTotp(code);\n setTotpSetup(null);\n apply(result, t('twoStep.appActivated'));\n }} />\n <PasswordDialog open={dialog?.kind === 'disableTotp'} onClose={close} title={t('twoStep.disableTitle')}\n description={t('twoStep.disableText')} confirmLabel={t('twoStep.deactivate')}\n onConfirm={async (password) => apply(await actions.disableTotp(password), t('twoStep.appDeactivated'))} />\n <PasswordDialog open={dialog?.kind === 'removePasskey'} onClose={close}\n title={t('twoStep.removeTitle', {name: passkeyBeingRemoved?.name ?? ''})} description={t('twoStep.removeText')}\n confirmLabel={t('twoStep.remove')}\n onConfirm={async (password) => apply(await actions.removePasskey(passkeyBeingRemoved, password), t('twoStep.passkeyRemoved'))} />\n <PasswordDialog open={dialog?.kind === 'regenerateCodes'} onClose={close} title={t('twoStep.regenerateTitle')}\n description={t('twoStep.regenerateText')} confirmLabel={t('twoStep.generate')}\n onConfirm={async (password) => apply(await actions.regenerateRecoveryCodes(password))} />\n <RecoveryCodesDialog codes={recoveryCodes} account={account} onClose={() => setRecoveryCodes(null)} />\n </SectionCard>\n </ComponentTexts>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { jsx } from "react/jsx-runtime";
|
|
3
3
|
import { useFormContext, useFormState } from "react-hook-form";
|
|
4
4
|
import Alert from "@mui/material/Alert";
|
|
5
|
+
import { piecePart } from "../theme/themePieces.js";
|
|
6
|
+
const THEME_NAME = "CalcitaFormProblemAlert";
|
|
7
|
+
const ProblemAlert = piecePart(THEME_NAME, "Root", Alert);
|
|
5
8
|
function FormProblemAlert({ control, sx }) {
|
|
6
9
|
const formContext = useFormContext();
|
|
7
10
|
const { errors } = useFormState({ control: control ?? formContext?.control });
|
|
@@ -9,7 +12,7 @@ function FormProblemAlert({ control, sx }) {
|
|
|
9
12
|
if (!message) {
|
|
10
13
|
return null;
|
|
11
14
|
}
|
|
12
|
-
return /* @__PURE__ */ jsx(
|
|
15
|
+
return /* @__PURE__ */ jsx(ProblemAlert, { severity: "error", role: "alert", sx, children: message });
|
|
13
16
|
}
|
|
14
17
|
export {
|
|
15
18
|
FormProblemAlert as default
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FormProblemAlert.js","sources":["../../src/hookForm/FormProblemAlert.jsx"],"sourcesContent":["// El aviso de arriba del formulario de react-hook-form: lo que el servidor dijo que no es de un campo. applyServerProblem\n// lo deja en errors.root.server; aquí se pinta. Sin mensaje no pinta nada ni ocupa lugar.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormContext, useFormState} from 'react-hook-form';\nimport Alert from '@mui/material/Alert';\n\n/**\n * @param {{control?: object, sx?: object}} props control: el de useForm, si la pantalla no usa FormProvider\n */\nexport default function FormProblemAlert({control, sx}) {\n const formContext = useFormContext();\n const {errors} = useFormState({control: control ?? formContext?.control});\n const message = errors.root?.server?.message;\n\n // guard clause: sin aviso del servidor\n if (!message) {\n return null;\n }\n return <
|
|
1
|
+
{"version":3,"file":"FormProblemAlert.js","sources":["../../src/hookForm/FormProblemAlert.jsx"],"sourcesContent":["// El aviso de arriba del formulario de react-hook-form: lo que el servidor dijo que no es de un campo. applyServerProblem\n// lo deja en errors.root.server; aquí se pinta. Sin mensaje no pinta nada ni ocupa lugar.\n//\n// Se cambia desde el tema del proyecto: components.CalcitaFormProblemAlert con styleOverrides.root.\n//\n// Genérico del paquete: function (regla de la casa).\nimport {useFormContext, useFormState} from 'react-hook-form';\nimport Alert from '@mui/material/Alert';\nimport {piecePart} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y su parte (theme.components.CalcitaFormProblemAlert): el estilo del proyecto va\n// después del de MUI y el sx de la pantalla al final\nconst THEME_NAME = 'CalcitaFormProblemAlert';\nconst ProblemAlert = piecePart(THEME_NAME, 'Root', Alert);\n\n/**\n * @param {{control?: object, sx?: object}} props control: el de useForm, si la pantalla no usa FormProvider\n */\nexport default function FormProblemAlert({control, sx}) {\n const formContext = useFormContext();\n const {errors} = useFormState({control: control ?? formContext?.control});\n const message = errors.root?.server?.message;\n\n // guard clause: sin aviso del servidor\n if (!message) {\n return null;\n }\n return <ProblemAlert severity=\"error\" role=\"alert\" sx={sx}>{message}</ProblemAlert>;\n}\n"],"names":[],"mappings":";;;;;AAYA,MAAM,aAAa;AACnB,MAAM,eAAe,UAAU,YAAY,QAAQ,KAAK;AAKxD,SAAwB,iBAAiB,EAAC,SAAS,MAAK;AACpD,QAAM,cAAc,eAAA;AACpB,QAAM,EAAC,WAAU,aAAa,EAAC,SAAS,WAAW,aAAa,SAAQ;AACxE,QAAM,UAAU,OAAO,MAAM,QAAQ;AAGrC,MAAI,CAAC,SAAS;AACV,WAAO;AAAA,EACX;AACA,6BAAQ,cAAA,EAAa,UAAS,SAAQ,MAAK,SAAQ,IAAS,UAAA,SAAQ;AACxE;"}
|
|
@@ -15,6 +15,23 @@ import Typography from "@mui/material/Typography";
|
|
|
15
15
|
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
|
16
16
|
import { PHONE_MEDIA_QUERY, DESKTOP_MEDIA_QUERY } from "../appShell/shellSizes.js";
|
|
17
17
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
18
|
+
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
19
|
+
const THEME_NAME = "CalcitaInboxLayout";
|
|
20
|
+
const InboxLabelDot = piecePart(THEME_NAME, "LabelDot", Box);
|
|
21
|
+
const InboxFolder = piecePart(THEME_NAME, "Folder", ListItemButton);
|
|
22
|
+
const InboxFolderIcon = piecePart(THEME_NAME, "FolderIcon", ListItemIcon);
|
|
23
|
+
const InboxFolderCount = piecePart(THEME_NAME, "FolderCount", Typography);
|
|
24
|
+
const InboxRoot = piecePart(THEME_NAME, "Root", Box);
|
|
25
|
+
const InboxFolders = piecePart(THEME_NAME, "Folders", Paper);
|
|
26
|
+
const InboxFoldersHeader = piecePart(THEME_NAME, "FoldersHeader", Box);
|
|
27
|
+
const InboxLabelsTitle = piecePart(THEME_NAME, "LabelsTitle", ListSubheader);
|
|
28
|
+
const InboxListPane = piecePart(THEME_NAME, "ListPane", Paper);
|
|
29
|
+
const InboxPhoneTabs = piecePart(THEME_NAME, "PhoneTabs", Tabs);
|
|
30
|
+
const InboxPhoneTabBadge = piecePart(THEME_NAME, "PhoneTabBadge", Badge);
|
|
31
|
+
const InboxListHeader = piecePart(THEME_NAME, "ListHeader", Box);
|
|
32
|
+
const InboxList = piecePart(THEME_NAME, "List", Box);
|
|
33
|
+
const InboxDetail = piecePart(THEME_NAME, "Detail", Paper);
|
|
34
|
+
const InboxBackButton = piecePart(THEME_NAME, "BackButton", Button);
|
|
18
35
|
const FOLDERS_WIDTH_PIXELS = 220;
|
|
19
36
|
const LIST_WIDTH_PIXELS = 380;
|
|
20
37
|
const DESKTOP_HEIGHT = "calc(100dvh - 220px)";
|
|
@@ -26,29 +43,30 @@ function FolderRow({ folder, isSelected, onSelect, t }) {
|
|
|
26
43
|
if (Icon) {
|
|
27
44
|
icon = /* @__PURE__ */ jsx(Icon, { fontSize: "small" });
|
|
28
45
|
} else if (color) {
|
|
29
|
-
icon = /* @__PURE__ */ jsx(
|
|
46
|
+
icon = /* @__PURE__ */ jsx(InboxLabelDot, { "aria-hidden": "true", ownSx: { width: LABEL_DOT_PIXELS, height: LABEL_DOT_PIXELS, borderRadius: "50%", bgcolor: `${color}.main`, ml: 0.5 } });
|
|
30
47
|
}
|
|
31
|
-
return /* @__PURE__ */ jsxs(
|
|
32
|
-
icon && /* @__PURE__ */ jsx(
|
|
48
|
+
return /* @__PURE__ */ jsxs(InboxFolder, { selected: isSelected, onClick: () => onSelect(id), ownSx: { borderRadius: 2 }, children: [
|
|
49
|
+
icon && /* @__PURE__ */ jsx(InboxFolderIcon, { ownSx: { minWidth: 36 }, children: icon }),
|
|
33
50
|
/* @__PURE__ */ jsx(ListItemText, { primary: label }),
|
|
34
|
-
unreadCount > 0 && /* @__PURE__ */ jsx(
|
|
51
|
+
unreadCount > 0 && /* @__PURE__ */ jsx(InboxFolderCount, { variant: "caption", ownSx: { fontWeight: 700 }, "aria-label": t("unreadCount", { count: unreadCount }), children: unreadCount })
|
|
35
52
|
] });
|
|
36
53
|
}
|
|
37
|
-
function InboxLayout({
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
54
|
+
function InboxLayout(props) {
|
|
55
|
+
const {
|
|
56
|
+
folders,
|
|
57
|
+
labels = [],
|
|
58
|
+
selectedFolderId,
|
|
59
|
+
onSelectFolder,
|
|
60
|
+
toolbar,
|
|
61
|
+
list,
|
|
62
|
+
detail,
|
|
63
|
+
isDetailOpen = false,
|
|
64
|
+
onCloseDetail,
|
|
65
|
+
foldersHeader,
|
|
66
|
+
texts
|
|
67
|
+
} = usePieceProps(props, THEME_NAME);
|
|
50
68
|
const { t } = useToolkitTranslation("inbox", texts);
|
|
51
|
-
return /* @__PURE__ */ jsxs(
|
|
69
|
+
return /* @__PURE__ */ jsxs(InboxRoot, { ownSx: {
|
|
52
70
|
display: "grid",
|
|
53
71
|
gap: 2,
|
|
54
72
|
[DESKTOP_MEDIA_QUERY]: {
|
|
@@ -57,20 +75,20 @@ function InboxLayout({
|
|
|
57
75
|
minHeight: DESKTOP_MIN_HEIGHT_PIXELS
|
|
58
76
|
}
|
|
59
77
|
}, children: [
|
|
60
|
-
/* @__PURE__ */ jsxs(
|
|
61
|
-
foldersHeader && /* @__PURE__ */ jsx(
|
|
78
|
+
/* @__PURE__ */ jsxs(InboxFolders, { variant: "outlined", component: "nav", "aria-label": t("folders"), ownSx: { p: 1, overflowY: "auto", borderRadius: 3, [PHONE_MEDIA_QUERY]: { display: "none" } }, children: [
|
|
79
|
+
foldersHeader && /* @__PURE__ */ jsx(InboxFoldersHeader, { ownSx: { p: 0.5, pb: 1 }, children: foldersHeader }),
|
|
62
80
|
/* @__PURE__ */ jsx(List, { disablePadding: true, children: folders.map((folder) => /* @__PURE__ */ jsx(FolderRow, { folder, isSelected: folder.id === selectedFolderId, onSelect: onSelectFolder, t }, folder.id)) }),
|
|
63
81
|
labels.length > 0 && /* @__PURE__ */ jsx(
|
|
64
82
|
List,
|
|
65
83
|
{
|
|
66
84
|
disablePadding: true,
|
|
67
85
|
"aria-label": t("labels"),
|
|
68
|
-
subheader: /* @__PURE__ */ jsx(
|
|
86
|
+
subheader: /* @__PURE__ */ jsx(InboxLabelsTitle, { disableSticky: true, ownSx: { bgcolor: "transparent", lineHeight: 2.5, mt: 1 }, children: t("labels") }),
|
|
69
87
|
children: labels.map((label) => /* @__PURE__ */ jsx(FolderRow, { folder: label, isSelected: label.id === selectedFolderId, onSelect: onSelectFolder, t }, label.id))
|
|
70
88
|
}
|
|
71
89
|
)
|
|
72
90
|
] }),
|
|
73
|
-
/* @__PURE__ */ jsxs(
|
|
91
|
+
/* @__PURE__ */ jsxs(InboxListPane, { variant: "outlined", ownSx: {
|
|
74
92
|
display: "flex",
|
|
75
93
|
flexDirection: "column",
|
|
76
94
|
minHeight: 0,
|
|
@@ -79,21 +97,21 @@ function InboxLayout({
|
|
|
79
97
|
[PHONE_MEDIA_QUERY]: isDetailOpen ? { display: "none" } : {}
|
|
80
98
|
}, children: [
|
|
81
99
|
/* @__PURE__ */ jsx(
|
|
82
|
-
|
|
100
|
+
InboxPhoneTabs,
|
|
83
101
|
{
|
|
84
102
|
value: selectedFolderId,
|
|
85
103
|
onChange: (event, folderId) => onSelectFolder(folderId),
|
|
86
104
|
variant: "scrollable",
|
|
87
105
|
"aria-label": t("folders"),
|
|
88
|
-
|
|
89
|
-
children: [...folders, ...labels].map(({ id, label, unreadCount }) => /* @__PURE__ */ jsx(Tab, { value: id, label: unreadCount > 0 ? /* @__PURE__ */ jsx(
|
|
106
|
+
ownSx: { px: 1, [DESKTOP_MEDIA_QUERY]: { display: "none" } },
|
|
107
|
+
children: [...folders, ...labels].map(({ id, label, unreadCount }) => /* @__PURE__ */ jsx(Tab, { value: id, label: unreadCount > 0 ? /* @__PURE__ */ jsx(InboxPhoneTabBadge, { color: "primary", badgeContent: unreadCount, ownSx: { pr: 1.5 }, children: label }) : label }, id))
|
|
90
108
|
}
|
|
91
109
|
),
|
|
92
|
-
toolbar && /* @__PURE__ */ jsx(
|
|
93
|
-
/* @__PURE__ */ jsx(
|
|
110
|
+
toolbar && /* @__PURE__ */ jsx(InboxListHeader, { ownSx: { p: 1.5, pb: 1 }, children: toolbar }),
|
|
111
|
+
/* @__PURE__ */ jsx(InboxList, { ownSx: { flex: 1, minHeight: 0, overflowY: "auto", px: 1, pb: 1 }, children: list })
|
|
94
112
|
] }),
|
|
95
|
-
/* @__PURE__ */ jsxs(
|
|
96
|
-
onCloseDetail && /* @__PURE__ */ jsx(
|
|
113
|
+
/* @__PURE__ */ jsxs(InboxDetail, { variant: "outlined", ownSx: { p: { xs: 2, md: 3 }, overflowY: "auto", borderRadius: 3, [PHONE_MEDIA_QUERY]: isDetailOpen ? {} : { display: "none" } }, children: [
|
|
114
|
+
onCloseDetail && /* @__PURE__ */ jsx(InboxBackButton, { startIcon: /* @__PURE__ */ jsx(ArrowBackRoundedIcon, {}), onClick: onCloseDetail, ownSx: { mb: 1, [DESKTOP_MEDIA_QUERY]: { display: "none" } }, children: t("back") }),
|
|
97
115
|
detail
|
|
98
116
|
] })
|
|
99
117
|
] });
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InboxLayout.js","sources":["../../src/inbox/InboxLayout.jsx"],"sourcesContent":["// El acomodo de una bandeja tipo correo: las carpetas, la lista y la lectura. En escritorio, tres columnas; en el teléfono,\n// una a la vez: las carpetas como pestañas arriba de la lista y, al abrir un mensaje, la lectura con «Regresar».\n//\n// <InboxLayout folders={folders} selectedFolderId={folderId} onSelectFolder={setFolderId} toolbar={<Buscar y Redactar />}\n// list={<MessageList … />} detail={<MessageView … />} isDetailOpen={Boolean(selectedId)} onCloseDetail={() => setSelectedId(null)} />\n//\n// Debajo de las carpetas, las etiquetas del proyecto (labels) con su punto de color, como Gmail; se eligen igual que una\n// carpeta. foldersHeader pone algo arriba de las carpetas (un botón «Redactar») y texts cambia cualquier texto.\n//\n// Escritorio o teléfono se decide con CSS (no con useMediaQuery): así el servidor pinta lo mismo que el navegador.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Paper from '@mui/material/Paper';\nimport Tab from '@mui/material/Tab';\nimport Tabs from '@mui/material/Tabs';\nimport Typography from '@mui/material/Typography';\nimport ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';\n\nimport {DESKTOP_MEDIA_QUERY, PHONE_MEDIA_QUERY} from '@/appShell/shellSizes.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\n\nconst FOLDERS_WIDTH_PIXELS = 220;\nconst LIST_WIDTH_PIXELS = 380;\n// la bandeja ocupa el alto de la ventana (menos la barra y el encabezado de la página): cada columna desliza por dentro\nconst DESKTOP_HEIGHT = 'calc(100dvh - 220px)';\nconst DESKTOP_MIN_HEIGHT_PIXELS = 480;\nconst LABEL_DOT_PIXELS = 10;\n\n/** Un renglón de carpeta o etiqueta: su ícono (o su punto de color), su nombre y cuántos sin leer. */\nfunction FolderRow({folder, isSelected, onSelect, t}) {\n const {id, label, Icon, color, unreadCount} = folder;\n let icon = null;\n if (Icon) {\n icon = <Icon fontSize=\"small\" />;\n } else if (color) {\n icon = <Box aria-hidden=\"true\" sx={{width: LABEL_DOT_PIXELS, height: LABEL_DOT_PIXELS, borderRadius: '50%', bgcolor: `${color}.main`, ml: 0.5}} />;\n }\n return (\n <ListItemButton selected={isSelected} onClick={() => onSelect(id)} sx={{borderRadius: 2}}>\n {icon && <ListItemIcon sx={{minWidth: 36}}>{icon}</ListItemIcon>}\n <ListItemText primary={label} />\n {unreadCount > 0 && <Typography variant=\"caption\" sx={{fontWeight: 700}} aria-label={t('unreadCount', {count: unreadCount})}>{unreadCount}</Typography>}\n </ListItemButton>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, label: string, Icon?: import('react').ElementType, unreadCount?: number}>} props.folders\n * @param {Array<{id: string, label: string, color?: string, unreadCount?: number}>} [props.labels] las etiquetas, debajo de las\n * carpetas con su punto de color (un color del tema); su id no debe repetir el de una carpeta\n * @param {string} props.selectedFolderId la carpeta o etiqueta elegida\n * @param {(folderId: string) => void} props.onSelectFolder\n * @param {import('react').ReactNode} [props.toolbar] arriba de la lista (buscar, redactar)\n * @param {import('react').ReactNode} props.list normalmente MessageList\n * @param {import('react').ReactNode} props.detail normalmente MessageView\n * @param {boolean} [props.isDetailOpen] en el teléfono, se ve la lectura en lugar de la lista\n * @param {() => void} [props.onCloseDetail] «Regresar» del teléfono\n * @param {import('react').ReactNode} [props.foldersHeader] arriba de las carpetas en escritorio (un botón «Redactar»)\n * @param {object} [props.texts] los textos que cambian, con la forma de `inbox` en los textos del paquete\n */\nexport default function InboxLayout({\n folders, labels = [], selectedFolderId, onSelectFolder, toolbar, list, detail, isDetailOpen = false, onCloseDetail, foldersHeader, texts,\n}) {\n const {t} = useToolkitTranslation('inbox', texts);\n\n return (\n <Box sx={{\n display: 'grid', gap: 2,\n [DESKTOP_MEDIA_QUERY]: {\n gridTemplateColumns: `${FOLDERS_WIDTH_PIXELS}px ${LIST_WIDTH_PIXELS}px minmax(0, 1fr)`, height: DESKTOP_HEIGHT, minHeight: DESKTOP_MIN_HEIGHT_PIXELS,\n },\n }}>\n {/* las carpetas: columna en escritorio */}\n <Paper variant=\"outlined\" component=\"nav\" aria-label={t('folders')} sx={{p: 1, overflowY: 'auto', borderRadius: 3, [PHONE_MEDIA_QUERY]: {display: 'none'}}}>\n {foldersHeader && <Box sx={{p: 0.5, pb: 1}}>{foldersHeader}</Box>}\n <List disablePadding>\n {folders.map((folder) => (\n <FolderRow key={folder.id} folder={folder} isSelected={folder.id === selectedFolderId} onSelect={onSelectFolder} t={t} />\n ))}\n </List>\n {labels.length > 0 && (\n <List disablePadding aria-label={t('labels')}\n subheader={<ListSubheader disableSticky sx={{bgcolor: 'transparent', lineHeight: 2.5, mt: 1}}>{t('labels')}</ListSubheader>}>\n {labels.map((label) => (\n <FolderRow key={label.id} folder={label} isSelected={label.id === selectedFolderId} onSelect={onSelectFolder} t={t} />\n ))}\n </List>\n )}\n </Paper>\n\n {/* la lista, con las carpetas como pestañas en el teléfono */}\n <Paper variant=\"outlined\" sx={{display: 'flex', flexDirection: 'column', minHeight: 0, borderRadius: 3, overflow: 'hidden',\n [PHONE_MEDIA_QUERY]: isDetailOpen ? {display: 'none'} : {}}}>\n <Tabs value={selectedFolderId} onChange={(event, folderId) => onSelectFolder(folderId)} variant=\"scrollable\" aria-label={t('folders')}\n sx={{px: 1, [DESKTOP_MEDIA_QUERY]: {display: 'none'}}}>\n {[...folders, ...labels].map(({id, label, unreadCount}) => (\n <Tab key={id} value={id} label={unreadCount > 0 ? <Badge color=\"primary\" badgeContent={unreadCount} sx={{pr: 1.5}}>{label}</Badge> : label} />\n ))}\n </Tabs>\n {toolbar && <Box sx={{p: 1.5, pb: 1}}>{toolbar}</Box>}\n <Box sx={{flex: 1, minHeight: 0, overflowY: 'auto', px: 1, pb: 1}}>{list}</Box>\n </Paper>\n\n {/* la lectura: en el teléfono solo con un mensaje abierto, con su «Regresar» */}\n <Paper variant=\"outlined\" sx={{p: {xs: 2, md: 3}, overflowY: 'auto', borderRadius: 3, [PHONE_MEDIA_QUERY]: isDetailOpen ? {} : {display: 'none'}}}>\n {onCloseDetail && (\n <Button startIcon={<ArrowBackRoundedIcon />} onClick={onCloseDetail} sx={{mb: 1, [DESKTOP_MEDIA_QUERY]: {display: 'none'}}}>{t('back')}</Button>\n )}\n {detail}\n </Paper>\n </Box>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"InboxLayout.js","sources":["../../src/inbox/InboxLayout.jsx"],"sourcesContent":["// El acomodo de una bandeja tipo correo: las carpetas, la lista y la lectura. En escritorio, tres columnas; en el teléfono,\n// una a la vez: las carpetas como pestañas arriba de la lista y, al abrir un mensaje, la lectura con «Regresar».\n//\n// <InboxLayout folders={folders} selectedFolderId={folderId} onSelectFolder={setFolderId} toolbar={<Buscar y Redactar />}\n// list={<MessageList … />} detail={<MessageView … />} isDetailOpen={Boolean(selectedId)} onCloseDetail={() => setSelectedId(null)} />\n//\n// Debajo de las carpetas, las etiquetas del proyecto (labels) con su punto de color, como Gmail; se eligen igual que una\n// carpeta. foldersHeader pone algo arriba de las carpetas (un botón «Redactar») y texts cambia cualquier texto.\n//\n// Escritorio o teléfono se decide con CSS (no con useMediaQuery): así el servidor pinta lo mismo que el navegador.\n//\n// Se cambia desde el tema del proyecto, como las piezas de MUI: components.CalcitaInboxLayout con defaultProps y\n// styleOverrides de sus partes: labelDot, folder, folderIcon, folderCount, root, folders, foldersHeader, labelsTitle,\n// listPane, phoneTabs, phoneTabBadge, listHeader, list, detail, backButton.\n//\n// Genérico del paquete: function (regla de la casa).\nimport Badge from '@mui/material/Badge';\nimport Box from '@mui/material/Box';\nimport Button from '@mui/material/Button';\nimport List from '@mui/material/List';\nimport ListItemButton from '@mui/material/ListItemButton';\nimport ListItemIcon from '@mui/material/ListItemIcon';\nimport ListItemText from '@mui/material/ListItemText';\nimport ListSubheader from '@mui/material/ListSubheader';\nimport Paper from '@mui/material/Paper';\nimport Tab from '@mui/material/Tab';\nimport Tabs from '@mui/material/Tabs';\nimport Typography from '@mui/material/Typography';\nimport ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';\n\nimport {DESKTOP_MEDIA_QUERY, PHONE_MEDIA_QUERY} from '@/appShell/shellSizes.js';\nimport {useToolkitTranslation} from '@/toolkit/useToolkitTranslation.js';\nimport {piecePart, usePieceProps} from '@/theme/themePieces.js';\n\n// el nombre de la pieza en el tema y sus partes (theme.components.CalcitaInboxLayout)\nconst THEME_NAME = 'CalcitaInboxLayout';\nconst InboxLabelDot = piecePart(THEME_NAME, 'LabelDot', Box);\nconst InboxFolder = piecePart(THEME_NAME, 'Folder', ListItemButton);\nconst InboxFolderIcon = piecePart(THEME_NAME, 'FolderIcon', ListItemIcon);\nconst InboxFolderCount = piecePart(THEME_NAME, 'FolderCount', Typography);\nconst InboxRoot = piecePart(THEME_NAME, 'Root', Box);\nconst InboxFolders = piecePart(THEME_NAME, 'Folders', Paper);\nconst InboxFoldersHeader = piecePart(THEME_NAME, 'FoldersHeader', Box);\nconst InboxLabelsTitle = piecePart(THEME_NAME, 'LabelsTitle', ListSubheader);\nconst InboxListPane = piecePart(THEME_NAME, 'ListPane', Paper);\nconst InboxPhoneTabs = piecePart(THEME_NAME, 'PhoneTabs', Tabs);\nconst InboxPhoneTabBadge = piecePart(THEME_NAME, 'PhoneTabBadge', Badge);\nconst InboxListHeader = piecePart(THEME_NAME, 'ListHeader', Box);\nconst InboxList = piecePart(THEME_NAME, 'List', Box);\nconst InboxDetail = piecePart(THEME_NAME, 'Detail', Paper);\nconst InboxBackButton = piecePart(THEME_NAME, 'BackButton', Button);\n\nconst FOLDERS_WIDTH_PIXELS = 220;\nconst LIST_WIDTH_PIXELS = 380;\n// la bandeja ocupa el alto de la ventana (menos la barra y el encabezado de la página): cada columna desliza por dentro\nconst DESKTOP_HEIGHT = 'calc(100dvh - 220px)';\nconst DESKTOP_MIN_HEIGHT_PIXELS = 480;\nconst LABEL_DOT_PIXELS = 10;\n\n/** Un renglón de carpeta o etiqueta: su ícono (o su punto de color), su nombre y cuántos sin leer. */\nfunction FolderRow({folder, isSelected, onSelect, t}) {\n const {id, label, Icon, color, unreadCount} = folder;\n let icon = null;\n if (Icon) {\n icon = <Icon fontSize=\"small\" />;\n } else if (color) {\n icon = <InboxLabelDot aria-hidden=\"true\" ownSx={{width: LABEL_DOT_PIXELS, height: LABEL_DOT_PIXELS, borderRadius: '50%', bgcolor: `${color}.main`, ml: 0.5}} />;\n }\n return (\n <InboxFolder selected={isSelected} onClick={() => onSelect(id)} ownSx={{borderRadius: 2}}>\n {icon && <InboxFolderIcon ownSx={{minWidth: 36}}>{icon}</InboxFolderIcon>}\n <ListItemText primary={label} />\n {unreadCount > 0 && <InboxFolderCount variant=\"caption\" ownSx={{fontWeight: 700}} aria-label={t('unreadCount', {count: unreadCount})}>{unreadCount}</InboxFolderCount>}\n </InboxFolder>\n );\n}\n\n/**\n * @param {object} props\n * @param {Array<{id: string, label: string, Icon?: import('react').ElementType, unreadCount?: number}>} props.folders\n * @param {Array<{id: string, label: string, color?: string, unreadCount?: number}>} [props.labels] las etiquetas, debajo de las\n * carpetas con su punto de color (un color del tema); su id no debe repetir el de una carpeta\n * @param {string} props.selectedFolderId la carpeta o etiqueta elegida\n * @param {(folderId: string) => void} props.onSelectFolder\n * @param {import('react').ReactNode} [props.toolbar] arriba de la lista (buscar, redactar)\n * @param {import('react').ReactNode} props.list normalmente MessageList\n * @param {import('react').ReactNode} props.detail normalmente MessageView\n * @param {boolean} [props.isDetailOpen] en el teléfono, se ve la lectura en lugar de la lista\n * @param {() => void} [props.onCloseDetail] «Regresar» del teléfono\n * @param {import('react').ReactNode} [props.foldersHeader] arriba de las carpetas en escritorio (un botón «Redactar»)\n * @param {object} [props.texts] los textos que cambian, con la forma de `inbox` en los textos del paquete\n */\nexport default function InboxLayout(props) {\n // lo que el proyecto puso en su tema (components.CalcitaInboxLayout.defaultProps) y encima lo que llega aquí\n const {\n folders, labels = [], selectedFolderId, onSelectFolder, toolbar, list, detail, isDetailOpen = false, onCloseDetail, foldersHeader, texts,\n} = usePieceProps(props, THEME_NAME);\n const {t} = useToolkitTranslation('inbox', texts);\n\n return (\n <InboxRoot ownSx={{\n display: 'grid', gap: 2,\n [DESKTOP_MEDIA_QUERY]: {\n gridTemplateColumns: `${FOLDERS_WIDTH_PIXELS}px ${LIST_WIDTH_PIXELS}px minmax(0, 1fr)`, height: DESKTOP_HEIGHT, minHeight: DESKTOP_MIN_HEIGHT_PIXELS,\n },\n }}>\n {/* las carpetas: columna en escritorio */}\n <InboxFolders variant=\"outlined\" component=\"nav\" aria-label={t('folders')} ownSx={{p: 1, overflowY: 'auto', borderRadius: 3, [PHONE_MEDIA_QUERY]: {display: 'none'}}}>\n {foldersHeader && <InboxFoldersHeader ownSx={{p: 0.5, pb: 1}}>{foldersHeader}</InboxFoldersHeader>}\n <List disablePadding>\n {folders.map((folder) => (\n <FolderRow key={folder.id} folder={folder} isSelected={folder.id === selectedFolderId} onSelect={onSelectFolder} t={t} />\n ))}\n </List>\n {labels.length > 0 && (\n <List disablePadding aria-label={t('labels')}\n subheader={<InboxLabelsTitle disableSticky ownSx={{bgcolor: 'transparent', lineHeight: 2.5, mt: 1}}>{t('labels')}</InboxLabelsTitle>}>\n {labels.map((label) => (\n <FolderRow key={label.id} folder={label} isSelected={label.id === selectedFolderId} onSelect={onSelectFolder} t={t} />\n ))}\n </List>\n )}\n </InboxFolders>\n\n {/* la lista, con las carpetas como pestañas en el teléfono */}\n <InboxListPane variant=\"outlined\" ownSx={{display: 'flex', flexDirection: 'column', minHeight: 0, borderRadius: 3, overflow: 'hidden',\n [PHONE_MEDIA_QUERY]: isDetailOpen ? {display: 'none'} : {}}}>\n <InboxPhoneTabs value={selectedFolderId} onChange={(event, folderId) => onSelectFolder(folderId)} variant=\"scrollable\" aria-label={t('folders')}\n ownSx={{px: 1, [DESKTOP_MEDIA_QUERY]: {display: 'none'}}}>\n {[...folders, ...labels].map(({id, label, unreadCount}) => (\n <Tab key={id} value={id} label={unreadCount > 0 ? <InboxPhoneTabBadge color=\"primary\" badgeContent={unreadCount} ownSx={{pr: 1.5}}>{label}</InboxPhoneTabBadge> : label} />\n ))}\n </InboxPhoneTabs>\n {toolbar && <InboxListHeader ownSx={{p: 1.5, pb: 1}}>{toolbar}</InboxListHeader>}\n <InboxList ownSx={{flex: 1, minHeight: 0, overflowY: 'auto', px: 1, pb: 1}}>{list}</InboxList>\n </InboxListPane>\n\n {/* la lectura: en el teléfono solo con un mensaje abierto, con su «Regresar» */}\n <InboxDetail variant=\"outlined\" ownSx={{p: {xs: 2, md: 3}, overflowY: 'auto', borderRadius: 3, [PHONE_MEDIA_QUERY]: isDetailOpen ? {} : {display: 'none'}}}>\n {onCloseDetail && (\n <InboxBackButton startIcon={<ArrowBackRoundedIcon />} onClick={onCloseDetail} ownSx={{mb: 1, [DESKTOP_MEDIA_QUERY]: {display: 'none'}}}>{t('back')}</InboxBackButton>\n )}\n {detail}\n </InboxDetail>\n </InboxRoot>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -21,24 +21,45 @@ import { useFormatters } from "../toolkit/useFormatters.js";
|
|
|
21
21
|
import { tagOf } from "../ui/labels/tagOf.js";
|
|
22
22
|
import { useToolkitTranslation } from "../toolkit/useToolkitTranslation.js";
|
|
23
23
|
import ActionsMenu from "../ui/actions/ActionsMenu.js";
|
|
24
|
+
import { piecePart, usePieceProps } from "../theme/themePieces.js";
|
|
25
|
+
const THEME_NAME = "CalcitaMessageList";
|
|
26
|
+
const MessagesEmpty = piecePart(THEME_NAME, "Empty", Typography);
|
|
27
|
+
const MessagesSelectedAvatar = piecePart(THEME_NAME, "SelectedAvatar", Avatar);
|
|
28
|
+
const MessagesItem = piecePart(THEME_NAME, "Item", ListItem);
|
|
29
|
+
const MessagesActions = piecePart(THEME_NAME, "Actions", Stack);
|
|
30
|
+
const MessagesStarButton = piecePart(THEME_NAME, "StarButton", IconButton);
|
|
31
|
+
const MessagesItemButton = piecePart(THEME_NAME, "ItemButton", ListItemButton);
|
|
32
|
+
const MessagesAvatarSlot = piecePart(THEME_NAME, "AvatarSlot", ListItemAvatar);
|
|
33
|
+
const MessagesAvatarButton = piecePart(THEME_NAME, "AvatarButton", ButtonBase);
|
|
34
|
+
const MessagesAvatar = piecePart(THEME_NAME, "Avatar", Avatar);
|
|
35
|
+
const MessagesBody = piecePart(THEME_NAME, "Body", Box);
|
|
36
|
+
const MessagesText = piecePart(THEME_NAME, "Text", Box);
|
|
37
|
+
const MessagesTopRow = piecePart(THEME_NAME, "TopRow", Stack);
|
|
38
|
+
const MessagesUnreadDot = piecePart(THEME_NAME, "UnreadDot", Box);
|
|
39
|
+
const MessagesSender = piecePart(THEME_NAME, "Sender", Typography);
|
|
40
|
+
const MessagesThreadCount = piecePart(THEME_NAME, "ThreadCount", Typography);
|
|
41
|
+
const MessagesDate = piecePart(THEME_NAME, "Date", Typography);
|
|
42
|
+
const MessagesSubject = piecePart(THEME_NAME, "Subject", Typography);
|
|
43
|
+
const MessagesLabels = piecePart(THEME_NAME, "Labels", Stack);
|
|
24
44
|
const UNREAD_DOT_PIXELS = 8;
|
|
25
45
|
const AVATAR_PIXELS = 40;
|
|
26
|
-
function MessageList({
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
function MessageList(props) {
|
|
47
|
+
const {
|
|
48
|
+
messages,
|
|
49
|
+
selectedId = null,
|
|
50
|
+
onSelect,
|
|
51
|
+
onToggleStar,
|
|
52
|
+
selectedIds = [],
|
|
53
|
+
onSelectionChange,
|
|
54
|
+
messageActions,
|
|
55
|
+
renderMessage,
|
|
56
|
+
emptyText,
|
|
57
|
+
texts
|
|
58
|
+
} = usePieceProps(props, THEME_NAME);
|
|
38
59
|
const { t } = useToolkitTranslation("inbox", texts);
|
|
39
60
|
const formatters = useFormatters();
|
|
40
61
|
if (messages.length === 0) {
|
|
41
|
-
return /* @__PURE__ */ jsx(
|
|
62
|
+
return /* @__PURE__ */ jsx(MessagesEmpty, { variant: "body2", color: "text.secondary", ownSx: { p: 3, textAlign: "center" }, children: emptyText ?? t("empty") });
|
|
42
63
|
}
|
|
43
64
|
const toggleSelected = (messageId, isChecked) => {
|
|
44
65
|
onSelectionChange(isChecked ? [...selectedIds, messageId] : selectedIds.filter((id) => id !== messageId));
|
|
@@ -46,59 +67,59 @@ function MessageList({
|
|
|
46
67
|
return /* @__PURE__ */ jsx(List, { disablePadding: true, "aria-label": t("listLabel"), children: messages.map((message) => {
|
|
47
68
|
const weight = message.isUnread ? 700 : 400;
|
|
48
69
|
const isChecked = selectedIds.includes(message.id);
|
|
49
|
-
const avatar = /* @__PURE__ */ jsx(
|
|
50
|
-
return /* @__PURE__ */ jsx(
|
|
70
|
+
const avatar = /* @__PURE__ */ jsx(MessagesSelectedAvatar, { ownSx: { width: AVATAR_PIXELS, height: AVATAR_PIXELS }, children: initialsOf(message.from.name) });
|
|
71
|
+
return /* @__PURE__ */ jsx(MessagesItem, { disablePadding: true, secondaryAction: (onToggleStar || messageActions) && /* @__PURE__ */ jsxs(MessagesActions, { direction: "row", ownSx: { alignItems: "center" }, children: [
|
|
51
72
|
onToggleStar && /* @__PURE__ */ jsx(
|
|
52
|
-
|
|
73
|
+
MessagesStarButton,
|
|
53
74
|
{
|
|
54
75
|
size: "small",
|
|
55
76
|
onClick: () => onToggleStar(message.id),
|
|
56
77
|
"aria-pressed": Boolean(message.isStarred),
|
|
57
78
|
"aria-label": message.isStarred ? t("unstar") : t("star"),
|
|
58
|
-
|
|
79
|
+
ownSx: { color: message.isStarred ? "warning.main" : "text.secondary" },
|
|
59
80
|
children: message.isStarred ? /* @__PURE__ */ jsx(StarRoundedIcon, { fontSize: "small" }) : /* @__PURE__ */ jsx(StarBorderRoundedIcon, { fontSize: "small" })
|
|
60
81
|
}
|
|
61
82
|
),
|
|
62
83
|
messageActions && /* @__PURE__ */ jsx(ActionsMenu, { label: t("messageActions", { subject: message.subject }), actions: messageActions(message) })
|
|
63
|
-
] }),
|
|
64
|
-
|
|
84
|
+
] }), ownSx: { "& .MuiListItemSecondaryAction-root": { right: 4 } }, children: /* @__PURE__ */ jsxs(
|
|
85
|
+
MessagesItemButton,
|
|
65
86
|
{
|
|
66
87
|
selected: message.id === selectedId || isChecked,
|
|
67
88
|
onClick: () => onSelect(message.id),
|
|
68
89
|
alignItems: "flex-start",
|
|
69
|
-
|
|
90
|
+
ownSx: { borderRadius: 2, pr: onToggleStar && messageActions ? 10 : 6 },
|
|
70
91
|
children: [
|
|
71
|
-
/* @__PURE__ */ jsx(
|
|
72
|
-
|
|
92
|
+
/* @__PURE__ */ jsx(MessagesAvatarSlot, { ownSx: { mt: 0.5 }, children: onSelectionChange ? /* @__PURE__ */ jsx(
|
|
93
|
+
MessagesAvatarButton,
|
|
73
94
|
{
|
|
74
95
|
role: "checkbox",
|
|
75
96
|
"aria-checked": isChecked,
|
|
76
97
|
"aria-label": t("select", { subject: message.subject }),
|
|
77
|
-
|
|
98
|
+
ownSx: { borderRadius: "50%" },
|
|
78
99
|
onClick: (event) => {
|
|
79
100
|
event.stopPropagation();
|
|
80
101
|
toggleSelected(message.id, !isChecked);
|
|
81
102
|
},
|
|
82
|
-
children: isChecked ? /* @__PURE__ */ jsx(
|
|
103
|
+
children: isChecked ? /* @__PURE__ */ jsx(MessagesAvatar, { ownSx: { width: AVATAR_PIXELS, height: AVATAR_PIXELS, bgcolor: "primary.main", color: "primary.contrastText" }, children: /* @__PURE__ */ jsx(CheckIcon, {}) }) : avatar
|
|
83
104
|
}
|
|
84
105
|
) : avatar }),
|
|
85
|
-
renderMessage ? /* @__PURE__ */ jsx(
|
|
86
|
-
/* @__PURE__ */ jsxs(
|
|
87
|
-
message.isUnread && /* @__PURE__ */ jsx(
|
|
88
|
-
/* @__PURE__ */ jsxs(
|
|
106
|
+
renderMessage ? /* @__PURE__ */ jsx(MessagesBody, { ownSx: { minWidth: 0, flex: 1 }, children: renderMessage(message) }) : /* @__PURE__ */ jsxs(MessagesText, { ownSx: { minWidth: 0, flex: 1 }, children: [
|
|
107
|
+
/* @__PURE__ */ jsxs(MessagesTopRow, { direction: "row", spacing: 1, ownSx: { alignItems: "center" }, children: [
|
|
108
|
+
message.isUnread && /* @__PURE__ */ jsx(MessagesUnreadDot, { component: "span", "aria-label": t("unread"), ownSx: { width: UNREAD_DOT_PIXELS, height: UNREAD_DOT_PIXELS, borderRadius: "50%", bgcolor: "primary.main", flexShrink: 0 } }),
|
|
109
|
+
/* @__PURE__ */ jsxs(MessagesSender, { variant: "body2", noWrap: true, ownSx: { fontWeight: weight, flex: 1 }, children: [
|
|
89
110
|
message.from.name,
|
|
90
|
-
message.threadCount > 1 && /* @__PURE__ */ jsxs(
|
|
111
|
+
message.threadCount > 1 && /* @__PURE__ */ jsxs(MessagesThreadCount, { component: "span", variant: "caption", color: "text.secondary", ownSx: { ml: 0.5 }, "aria-label": t("threadCount", { count: message.threadCount }), children: [
|
|
91
112
|
"(",
|
|
92
113
|
message.threadCount,
|
|
93
114
|
")"
|
|
94
115
|
] })
|
|
95
116
|
] }),
|
|
96
117
|
message.hasAttachments && /* @__PURE__ */ jsx(AttachFileRoundedIcon, { fontSize: "inherit", color: "action", "aria-label": t("attachments") }),
|
|
97
|
-
/* @__PURE__ */ jsx(
|
|
118
|
+
/* @__PURE__ */ jsx(MessagesDate, { variant: "caption", color: "text.secondary", ownSx: { flexShrink: 0, fontVariantNumeric: "tabular-nums" }, children: inboxDateOf(message.receivedAt, /* @__PURE__ */ new Date(), formatters) })
|
|
98
119
|
] }),
|
|
99
|
-
/* @__PURE__ */ jsx(
|
|
120
|
+
/* @__PURE__ */ jsx(MessagesSubject, { variant: "body2", noWrap: true, ownSx: { fontWeight: weight }, children: message.subject }),
|
|
100
121
|
/* @__PURE__ */ jsx(Typography, { variant: "body2", color: "text.secondary", noWrap: true, children: message.preview }),
|
|
101
|
-
message.labels?.length > 0 && /* @__PURE__ */ jsx(
|
|
122
|
+
message.labels?.length > 0 && /* @__PURE__ */ jsx(MessagesLabels, { direction: "row", spacing: 0.5, useFlexGap: true, ownSx: { flexWrap: "wrap", mt: 0.5 }, children: message.labels.map((rawLabel) => {
|
|
102
123
|
const label = tagOf(rawLabel);
|
|
103
124
|
return /* @__PURE__ */ jsx(Chip, { size: "small", variant: "outlined", color: label.color, label: label.label }, label.label);
|
|
104
125
|
}) })
|