@micha.bigler/ui-core-micha 3.14.4 → 4.0.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/CHANGELOG.md +25 -0
- package/README.md +7 -0
- package/dist/chat/kitSxRegistry.js +9 -0
- package/dist/components/charts/kitSxRegistry.js +6 -0
- package/dist/entries/charts.js +6 -0
- package/dist/entries/chat.js +8 -0
- package/dist/entries/markdown.js +1 -0
- package/dist/index.js +0 -14
- package/dist/pages/AccountPage.js +1 -2
- package/dist/pages/LoginPage.js +1 -2
- package/dist/pages/PasswordChangePage.js +1 -2
- package/dist/pages/PasswordInvitePage.js +1 -2
- package/dist/pages/PasswordResetRequestPage.js +1 -2
- package/dist/pages/SignUpPage.js +1 -2
- package/dist/pages/SignupConfirmPage.js +1 -2
- package/dist/theme/kitSxEntry.js +11 -0
- package/dist/theme/kitSxRegistry.js +2 -15
- package/package.json +32 -7
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -126
- package/DESIGN.md +0 -60
- package/WORK_ORDERS.md +0 -150
- package/dev/entries.jsx +0 -721
- package/dev/fixtures.js +0 -26
- package/dev/main.jsx +0 -75
- package/dev/mockTransport.jsx +0 -128
- package/docs/CHART-LAYOUT.md +0 -157
- package/docs/DARK-TOKENS.md +0 -140
- package/docs/TYPE-SCALE.md +0 -111
- package/docs/messaging-deviations.md +0 -118
- package/index.html +0 -12
- package/renovate.json +0 -10
- package/scripts/chart-api-census.mjs +0 -415
- package/src/auth/AuthContext.jsx +0 -158
- package/src/auth/ReauthModal.jsx +0 -82
- package/src/auth/UserMenu.jsx +0 -121
- package/src/auth/apiClient.jsx +0 -232
- package/src/auth/authApi.jsx +0 -539
- package/src/auth/authConfig.jsx +0 -34
- package/src/auth/reauth.js +0 -47
- package/src/chat/ChatComposer.jsx +0 -36
- package/src/chat/ChatList.jsx +0 -87
- package/src/chat/ChatThread.jsx +0 -128
- package/src/chat/ChatWorkspace.jsx +0 -244
- package/src/chat/MarkdownReply.jsx +0 -22
- package/src/chat/ModelPicker.jsx +0 -85
- package/src/chat/api.js +0 -18
- package/src/chat/useChatSocket.js +0 -32
- package/src/chat/wire.js +0 -7
- package/src/components/AccessCodeManager.jsx +0 -288
- package/src/components/AccessCodeSingleUseToggle.jsx +0 -92
- package/src/components/AllowedEmailDomainsManager.jsx +0 -92
- package/src/components/AuthFactorRequirementCard.jsx +0 -95
- package/src/components/BulkInviteCsvTab.jsx +0 -198
- package/src/components/EmptyState.jsx +0 -38
- package/src/components/LoginForm.jsx +0 -159
- package/src/components/MFAComponent.jsx +0 -350
- package/src/components/MfaLoginComponent.jsx +0 -252
- package/src/components/MobileBottomNav.jsx +0 -82
- package/src/components/PasskeysComponent.jsx +0 -252
- package/src/components/PasswordChangeForm.jsx +0 -87
- package/src/components/PasswordResetRequestForm.jsx +0 -43
- package/src/components/PasswordSetForm.jsx +0 -97
- package/src/components/ProfileComponent.jsx +0 -348
- package/src/components/QrSignupManager.jsx +0 -404
- package/src/components/QrSignupValidityManager.jsx +0 -106
- package/src/components/RegistrationMethodsManager.jsx +0 -150
- package/src/components/SecurityComponent.jsx +0 -141
- package/src/components/SocialLoginButtons.jsx +0 -83
- package/src/components/SoftChip.jsx +0 -93
- package/src/components/StatTile.jsx +0 -39
- package/src/components/SupportRecoveryRequestsTab.jsx +0 -482
- package/src/components/TurnstileWidget.jsx +0 -99
- package/src/components/UserInviteComponent.jsx +0 -76
- package/src/components/UserListComponent.jsx +0 -618
- package/src/components/charts/BarChart.jsx +0 -102
- package/src/components/charts/ChartFrame.jsx +0 -214
- package/src/components/charts/LineChart.jsx +0 -126
- package/src/components/charts/ScatterChart.jsx +0 -299
- package/src/components/charts/TimeSeriesChart.jsx +0 -300
- package/src/components/charts/chartDefaults.js +0 -644
- package/src/components/charts/chartLabels.js +0 -36
- package/src/components/charts/exportChart.js +0 -258
- package/src/components/charts/formatters.js +0 -39
- package/src/components/charts/palette.js +0 -33
- package/src/components/charts/yearTickInterval.js +0 -7
- package/src/i18n/authTranslations.ts +0 -2651
- package/src/i18n/chartsTranslations.ts +0 -91
- package/src/i18n/chatTranslations.ts +0 -55
- package/src/i18n/messagingTranslations.ts +0 -120
- package/src/i18n/notificationsTranslations.ts +0 -32
- package/src/i18n/onboardingTranslations.ts +0 -29
- package/src/i18n/sectionNavTranslations.ts +0 -20
- package/src/i18n/uiCoreTranslations.js +0 -29
- package/src/i18n/userMenuTranslations.ts +0 -5
- package/src/index.js +0 -158
- package/src/layout/PageLayout.jsx +0 -36
- package/src/layout/SectionNav.jsx +0 -320
- package/src/messaging/AttachmentList.jsx +0 -145
- package/src/messaging/Composer.jsx +0 -197
- package/src/messaging/ConversationLaunchers.jsx +0 -51
- package/src/messaging/ConversationList.jsx +0 -109
- package/src/messaging/DirectMessageLauncher.jsx +0 -112
- package/src/messaging/MessageBubble.jsx +0 -183
- package/src/messaging/MessagingProvider.jsx +0 -0
- package/src/messaging/MessagingScopeConfig.jsx +0 -24
- package/src/messaging/PollCard.jsx +0 -163
- package/src/messaging/ReactionBar.jsx +0 -43
- package/src/messaging/ReadTicks.jsx +0 -82
- package/src/messaging/Thread.jsx +0 -195
- package/src/messaging/api.js +0 -48
- package/src/messaging/composerImageCompression.js +0 -35
- package/src/messaging/linkifyText.jsx +0 -37
- package/src/notifications/NotificationBell.jsx +0 -84
- package/src/notifications/NotificationSettings.jsx +0 -330
- package/src/notifications/NotificationsProvider.jsx +0 -224
- package/src/notifications/PopupSurface.jsx +0 -105
- package/src/notifications/api.js +0 -55
- package/src/notifications/feedApi.js +0 -18
- package/src/notifications/realtime.jsx +0 -138
- package/src/notifications/serviceWorker/sw.js +0 -50
- package/src/onboarding/OnboardingProvider.jsx +0 -326
- package/src/onboarding/OnboardingWizard.jsx +0 -61
- package/src/onboarding/WizardDialogShell.jsx +0 -51
- package/src/onboarding/api.js +0 -13
- package/src/onboarding/stepSelection.js +0 -16
- package/src/onboarding/steps/BrowserPushStep.jsx +0 -121
- package/src/onboarding/steps/CompleteNameStep.jsx +0 -46
- package/src/onboarding/steps/CookieConsentStep.jsx +0 -133
- package/src/onboarding/steps/PwaInstallStep.jsx +0 -47
- package/src/pages/AccountPage.jsx +0 -394
- package/src/pages/LoginPage.jsx +0 -343
- package/src/pages/PasswordChangePage.jsx +0 -62
- package/src/pages/PasswordInvitePage.jsx +0 -177
- package/src/pages/PasswordResetRequestPage.jsx +0 -68
- package/src/pages/SignUpPage.jsx +0 -268
- package/src/pages/SignupConfirmPage.jsx +0 -155
- package/src/testing/checkKitIntegration.js +0 -233
- package/src/theme/createAppTheme.js +0 -423
- package/src/theme/fonts.js +0 -3
- package/src/theme/index.js +0 -21
- package/src/theme/kitSxRegistry.js +0 -285
- package/src/theme/themeCompleteness.js +0 -1045
- package/src/theme/tokens.js +0 -478
- package/src/utils/auth-errors.js +0 -76
- package/src/utils/authService.js +0 -223
- package/src/utils/resolveErrorText.js +0 -28
- package/src/utils/webauthn.js +0 -51
- package/tests/AccountPage.profileStatements.test.jsx +0 -162
- package/tests/AccountPage.test.jsx +0 -200
- package/tests/AuthContext.test.jsx +0 -129
- package/tests/BarChart.test.jsx +0 -112
- package/tests/BrowserPushStep.test.jsx +0 -110
- package/tests/ChartFrame.test.jsx +0 -336
- package/tests/ConversationList.test.jsx +0 -144
- package/tests/CookieConsentStep.test.jsx +0 -69
- package/tests/EmptyState.test.jsx +0 -62
- package/tests/LineChart.test.jsx +0 -80
- package/tests/MessagingProvider.test.jsx +0 -50
- package/tests/MobileBottomNav.test.jsx +0 -319
- package/tests/NotificationBell.test.jsx +0 -60
- package/tests/NotificationSettings.test.jsx +0 -276
- package/tests/NotificationsProvider.test.jsx +0 -240
- package/tests/OnboardingProvider.test.jsx +0 -294
- package/tests/OnboardingWizard.test.jsx +0 -143
- package/tests/PasswordChangeForm.test.jsx +0 -70
- package/tests/PasswordInvitePage.test.jsx +0 -142
- package/tests/PasswordSetForm.test.jsx +0 -70
- package/tests/PopupSurface.test.jsx +0 -142
- package/tests/ProfileComponent.refresh.test.jsx +0 -194
- package/tests/QrSignupManager.registrationContext.test.jsx +0 -106
- package/tests/QrSignupManagerInfoBox.test.jsx +0 -49
- package/tests/ScatterChart.test.jsx +0 -270
- package/tests/SectionNav.test.jsx +0 -363
- package/tests/SignUpPage.test.jsx +0 -208
- package/tests/SoftChip.test.jsx +0 -105
- package/tests/StatTile.test.jsx +0 -57
- package/tests/Thread.test.jsx +0 -151
- package/tests/TimeSeriesChart.test.jsx +0 -367
- package/tests/UserListComponent.test.jsx +0 -192
- package/tests/UserMenu.test.jsx +0 -171
- package/tests/apiClientLanguage.test.js +0 -33
- package/tests/apiClientPublicPaths.test.js +0 -178
- package/tests/authApiLogin.test.js +0 -42
- package/tests/authApiRegistration.test.js +0 -31
- package/tests/authErrorCodeRendering.test.jsx +0 -77
- package/tests/authErrors.test.js +0 -74
- package/tests/authTranslations.test.js +0 -74
- package/tests/chart-api-census.test.js +0 -366
- package/tests/chartAxisTickBand.test.jsx +0 -95
- package/tests/chartDefaults.test.js +0 -902
- package/tests/chartLabels.test.js +0 -50
- package/tests/chartsPalette.test.js +0 -99
- package/tests/chatApi.test.js +0 -48
- package/tests/chatDriverNames.test.jsx +0 -71
- package/tests/chatExports.test.js +0 -3
- package/tests/chatList.test.jsx +0 -84
- package/tests/chatPicker.test.jsx +0 -82
- package/tests/chatSocket.test.jsx +0 -26
- package/tests/chatThread.test.jsx +0 -116
- package/tests/chatTranslations.test.js +0 -47
- package/tests/chatWorkspace.test.jsx +0 -234
- package/tests/checkKitIntegration.test.jsx +0 -219
- package/tests/createAppTheme.test.js +0 -132
- package/tests/createAppThemeDark.test.js +0 -127
- package/tests/createAppThemeLightGuard.test.js +0 -40
- package/tests/createAppThemePageContrast.test.js +0 -108
- package/tests/createAppThemeTypography.test.js +0 -53
- package/tests/errorCallSiteGuard.test.js +0 -32
- package/tests/exportChart.test.js +0 -313
- package/tests/feedApi.test.js +0 -46
- package/tests/fixtures/light-theme-baseline.json +0 -2234
- package/tests/i18nAggregate.test.js +0 -120
- package/tests/i18nGermanVariant.test.js +0 -65
- package/tests/loginPageErrorResolution.test.jsx +0 -102
- package/tests/messagingApi.test.js +0 -32
- package/tests/messagingChunk6.test.jsx +0 -116
- package/tests/messagingCompactBubble.test.jsx +0 -64
- package/tests/messagingComposer.test.jsx +0 -136
- package/tests/messagingComposerChunk5.test.jsx +0 -113
- package/tests/messagingContractConformance.test.js +0 -69
- package/tests/messagingDirectMessageLauncher.test.jsx +0 -151
- package/tests/messagingExports.test.js +0 -21
- package/tests/messagingInteractions.test.jsx +0 -136
- package/tests/messagingLastMessagePreview.test.jsx +0 -93
- package/tests/messagingLinkify.test.jsx +0 -51
- package/tests/messagingMessageActions.test.jsx +0 -163
- package/tests/messagingMsg6.test.jsx +0 -105
- package/tests/messagingMsg6f.test.jsx +0 -155
- package/tests/messagingMsg6g.test.jsx +0 -166
- package/tests/messagingMsg6h.test.jsx +0 -136
- package/tests/messagingMsg6i.test.jsx +0 -129
- package/tests/messagingPollRendering.test.jsx +0 -142
- package/tests/messagingReactionMerge.test.jsx +0 -77
- package/tests/messagingReadTicksTooltip.test.jsx +0 -36
- package/tests/messagingThreadReceipts.test.jsx +0 -178
- package/tests/messagingThreadingQuoting.test.jsx +0 -87
- package/tests/messagingUnreadLifecycle.test.jsx +0 -78
- package/tests/notificationsApi.test.js +0 -41
- package/tests/notificationsExports.test.js +0 -34
- package/tests/onboardingDescriptors.test.js +0 -132
- package/tests/packageTreeShaking.test.js +0 -126
- package/tests/realtime.test.jsx +0 -219
- package/tests/resolveErrorText.test.js +0 -72
- package/tests/stepSelection.test.js +0 -53
- package/tests/themeCompleteness.test.js +0 -626
- package/tests/timeSeriesLegend.test.js +0 -99
- package/tests/userScreenTranslations.test.jsx +0 -218
- package/tsconfig.build.json +0 -18
- package/vite.config.mjs +0 -6
- package/work-orders/AUTH-1.md +0 -74
- package/work-orders/AUTH-2.md +0 -74
- package/work-orders/AUTH-4.md +0 -183
- package/work-orders/AUTH-5.md +0 -154
- package/work-orders/AUTH-7.md +0 -181
- package/work-orders/AUTH-8.md +0 -184
- package/work-orders/CHART-1.md +0 -285
- package/work-orders/CHART-2.md +0 -222
- package/work-orders/CHART-3.md +0 -163
- package/work-orders/CHART-4.md +0 -203
- package/work-orders/CHART-5.md +0 -226
- package/work-orders/CHART-6.md +0 -428
- package/work-orders/CHART-7.md +0 -175
- package/work-orders/DX-1.md +0 -169
- package/work-orders/DX-2.md +0 -188
- package/work-orders/DX-3.md +0 -195
- package/work-orders/I18N-1.md +0 -254
- package/work-orders/I18N-3.md +0 -207
- package/work-orders/MSG-3.md +0 -375
- package/work-orders/MSG-3b.md +0 -1002
- package/work-orders/MSG-3c.md +0 -378
- package/work-orders/MSG-3d.md +0 -268
- package/work-orders/MSG-3e.md +0 -164
- package/work-orders/MSG-3f.md +0 -391
- package/work-orders/MSG-6e.md +0 -308
- package/work-orders/MSG-6f.md +0 -214
- package/work-orders/MSG-6g.md +0 -244
- package/work-orders/MSG-6h.md +0 -238
- package/work-orders/MSG-6i.md +0 -271
- package/work-orders/MSG-6j.md +0 -117
- package/work-orders/NOTIF-12.md +0 -214
- package/work-orders/NOTIF-13.md +0 -142
- package/work-orders/NOTIF-14.md +0 -151
- package/work-orders/PRIM-1.md +0 -177
- package/work-orders/SHELL-1.md +0 -255
- package/work-orders/SHELL-2.md +0 -197
- package/work-orders/SHELL-3.md +0 -359
- package/work-orders/SHELL-4.md +0 -351
- package/work-orders/SHELL-5.md +0 -345
- package/work-orders/SHELL-6.md +0 -210
- package/work-orders/THEME-1.md +0 -587
- package/work-orders/THEME-10.md +0 -303
- package/work-orders/THEME-11.md +0 -208
- package/work-orders/THEME-2.md +0 -339
- package/work-orders/THEME-3.md +0 -210
- package/work-orders/THEME-4.md +0 -271
- package/work-orders/THEME-5.md +0 -341
- package/work-orders/THEME-6.md +0 -247
- package/work-orders/THEME-7.md +0 -82
- package/work-orders/THEME-8.md +0 -211
- package/work-orders/THEME-9.md +0 -194
- package/work-orders/UCM-AUTH-10.md +0 -490
- package/work-orders/UCM-AUTH-11.md +0 -185
- package/work-orders/UCM-AUTH-12.md +0 -98
- package/work-orders/UCM-AUTH-13.md +0 -47
- package/work-orders/UCM-AUTH-9.md +0 -207
- package/work-orders/UCM-CHART-10.md +0 -150
- package/work-orders/UCM-CHART-11.md +0 -132
- package/work-orders/UCM-CHART-12.md +0 -348
- package/work-orders/UCM-CHART-13.md +0 -122
- package/work-orders/UCM-CHART-14.md +0 -198
- package/work-orders/UCM-CHART-15.md +0 -187
- package/work-orders/UCM-CHART-16.md +0 -169
- package/work-orders/UCM-CHART-17.md +0 -195
- package/work-orders/UCM-CHART-18.md +0 -200
- package/work-orders/UCM-CHART-8.md +0 -147
- package/work-orders/UCM-CHART-9.md +0 -265
- package/work-orders/UCM-CHAT-1.md +0 -190
- package/work-orders/UCM-CHAT-2.md +0 -75
- package/work-orders/UCM-CI-1.md +0 -65
- package/work-orders/UCM-DEP-1.md +0 -63
- package/work-orders/UCM-I18N-4.md +0 -226
- package/work-orders/UCM-I18N-5.md +0 -211
- package/work-orders/UCM-MSG-20.md +0 -192
- package/work-orders/UCM-PRIM-2.md +0 -114
- package/work-orders/UCM-SHELL-7.md +0 -134
- package/work-orders/UCM-SHELL-8.md +0 -125
- package/work-orders/UCM-TEST-1.md +0 -304
- package/work-orders/UCM-THEME-13.md +0 -69
- package/work-orders/UCM-THEME-14.md +0 -312
- package/work-orders/UCM-THEME-15.md +0 -356
- package/work-orders/UCM-THEME-16.md +0 -80
- package/work-orders/UCM-THEME-17.md +0 -111
- package/work-orders/UCM-THEME-18.md +0 -184
- package/work-orders/assets/AUTH-4-user-list-card.html +0 -407
- package/work-orders/assets/PRIM-1-dashboard-primitives.html +0 -624
- package/work-orders/assets/SHELL-2-account-navigation.html +0 -1159
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +0 -1352
- package/work-orders/assets/UCM-CHAT-1-ai-chat.html +0 -447
- package/work-orders/assets/UCM-THEME-13-dark-prototype.html +0 -390
- package/work-orders/assets/UCM-THEME-17-type-scale.html +0 -391
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
// src/components/layout/PageLayout.jsx
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Container, Box, Typography } from '@mui/material';
|
|
4
|
-
|
|
5
|
-
export const PAGE_LAYOUT_CONTAINER_SX = { mt: 4 };
|
|
6
|
-
|
|
7
|
-
// Layout for content pages with larger width (e.g. Profile, Welcome, Input)
|
|
8
|
-
export const WidePage = ({ title, children, maxWidth = 'lg' }) => (
|
|
9
|
-
<Container maxWidth={maxWidth} sx={PAGE_LAYOUT_CONTAINER_SX}>
|
|
10
|
-
{title && (
|
|
11
|
-
<Typography variant="h4" gutterBottom>
|
|
12
|
-
{title}
|
|
13
|
-
</Typography>
|
|
14
|
-
)}
|
|
15
|
-
{children}
|
|
16
|
-
</Container>
|
|
17
|
-
);
|
|
18
|
-
|
|
19
|
-
// Layout for forms or narrow pages (e.g. Login, Reset, Invite)
|
|
20
|
-
export const NarrowPage = ({ title, subtitle, children }) => (
|
|
21
|
-
<Container maxWidth="md" sx={PAGE_LAYOUT_CONTAINER_SX}>
|
|
22
|
-
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
|
|
23
|
-
{title && (
|
|
24
|
-
<Typography variant="h4" gutterBottom>
|
|
25
|
-
{title}
|
|
26
|
-
</Typography>
|
|
27
|
-
)}
|
|
28
|
-
{subtitle && (
|
|
29
|
-
<Typography paragraph>
|
|
30
|
-
{subtitle}
|
|
31
|
-
</Typography>
|
|
32
|
-
)}
|
|
33
|
-
{children}
|
|
34
|
-
</Box>
|
|
35
|
-
</Container>
|
|
36
|
-
);
|
|
@@ -1,320 +0,0 @@
|
|
|
1
|
-
import { useId, useState } from 'react';
|
|
2
|
-
import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown';
|
|
3
|
-
import {
|
|
4
|
-
Box,
|
|
5
|
-
ButtonBase,
|
|
6
|
-
Drawer,
|
|
7
|
-
List,
|
|
8
|
-
ListItem,
|
|
9
|
-
ListItemButton,
|
|
10
|
-
ListItemText,
|
|
11
|
-
Paper,
|
|
12
|
-
Stack,
|
|
13
|
-
Typography,
|
|
14
|
-
} from '@mui/material';
|
|
15
|
-
import { useTheme } from '@mui/material/styles';
|
|
16
|
-
import { useTranslation } from 'react-i18next';
|
|
17
|
-
|
|
18
|
-
const defaultZIndex = (theme) => theme.zIndex.drawer + 3;
|
|
19
|
-
|
|
20
|
-
export const SECTION_NAV_DRAWER_PAPER_SX = {
|
|
21
|
-
borderTopLeftRadius: 18,
|
|
22
|
-
borderTopRightRadius: 18,
|
|
23
|
-
maxHeight: '78dvh',
|
|
24
|
-
p: 2,
|
|
25
|
-
pb: 'max(16px, env(safe-area-inset-bottom))',
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
export const SECTION_NAV_GROUP_PAPER_SX = {
|
|
29
|
-
overflow: 'hidden',
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const SECTION_NAV_GRID_SX = {
|
|
33
|
-
display: 'grid',
|
|
34
|
-
gap: 4,
|
|
35
|
-
alignItems: 'start',
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const SECTION_NAV_SIDEBAR_SX = {
|
|
39
|
-
position: 'sticky',
|
|
40
|
-
top: 'var(--SectionNav-headerOffset)',
|
|
41
|
-
alignSelf: 'start',
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
const SECTION_NAV_CONTENT_SX = {
|
|
45
|
-
minWidth: 0,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
const SECTION_NAV_GROUP_HEADER_SX = {
|
|
49
|
-
px: 2,
|
|
50
|
-
py: 1.5,
|
|
51
|
-
borderBottom: '1px solid',
|
|
52
|
-
borderColor: 'divider',
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const SECTION_NAV_TRIGGER_SX = {
|
|
56
|
-
width: '100%',
|
|
57
|
-
minHeight: 56,
|
|
58
|
-
mb: 2,
|
|
59
|
-
px: 1.75,
|
|
60
|
-
py: 1,
|
|
61
|
-
display: 'flex',
|
|
62
|
-
alignItems: 'center',
|
|
63
|
-
justifyContent: 'space-between',
|
|
64
|
-
gap: 2,
|
|
65
|
-
textAlign: 'left',
|
|
66
|
-
border: '1px solid',
|
|
67
|
-
borderColor: 'divider',
|
|
68
|
-
borderRadius: 1,
|
|
69
|
-
backgroundColor: 'background.paper',
|
|
70
|
-
boxShadow: 1,
|
|
71
|
-
'&:hover': {
|
|
72
|
-
backgroundColor: 'action.hover',
|
|
73
|
-
},
|
|
74
|
-
'&.Mui-focusVisible': {
|
|
75
|
-
outline: '2px solid',
|
|
76
|
-
outlineColor: 'primary.main',
|
|
77
|
-
outlineOffset: 2,
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
const SECTION_NAV_TRIGGER_TEXT_SX = {
|
|
82
|
-
minWidth: 0,
|
|
83
|
-
display: 'flex',
|
|
84
|
-
flexDirection: 'column',
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
const SECTION_NAV_TRIGGER_VALUE_SX = {
|
|
88
|
-
overflow: 'hidden',
|
|
89
|
-
textOverflow: 'ellipsis',
|
|
90
|
-
whiteSpace: 'nowrap',
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
const SECTION_NAV_CHEVRON_SX = {
|
|
94
|
-
color: 'text.secondary',
|
|
95
|
-
transition: 'transform 0.18s ease',
|
|
96
|
-
flex: 'none',
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const SECTION_NAV_DRAWER_TITLE_SX = {
|
|
100
|
-
mb: 2,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const SECTION_NAV_DRAWER_SCROLL_SX = {
|
|
104
|
-
overflowY: 'auto',
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
function SectionNavList({
|
|
108
|
-
groups,
|
|
109
|
-
activeKey,
|
|
110
|
-
rememberedKey,
|
|
111
|
-
overviewItem,
|
|
112
|
-
onSelect,
|
|
113
|
-
rememberedLabel,
|
|
114
|
-
}) {
|
|
115
|
-
const headingId = useId();
|
|
116
|
-
|
|
117
|
-
return (
|
|
118
|
-
<Stack spacing={2}>
|
|
119
|
-
{overviewItem && (
|
|
120
|
-
<Paper variant="outlined" sx={SECTION_NAV_GROUP_PAPER_SX}>
|
|
121
|
-
<List disablePadding>
|
|
122
|
-
<ListItem disablePadding>
|
|
123
|
-
<ListItemButton
|
|
124
|
-
selected={activeKey == null}
|
|
125
|
-
aria-current={activeKey == null ? 'page' : undefined}
|
|
126
|
-
onClick={() => onSelect?.(null)}
|
|
127
|
-
>
|
|
128
|
-
<ListItemText primary={overviewItem.label} />
|
|
129
|
-
</ListItemButton>
|
|
130
|
-
</ListItem>
|
|
131
|
-
</List>
|
|
132
|
-
</Paper>
|
|
133
|
-
)}
|
|
134
|
-
|
|
135
|
-
{groups.map((group, groupIndex) => {
|
|
136
|
-
const groupHeadingId = `${headingId}-${groupIndex}`;
|
|
137
|
-
|
|
138
|
-
return (
|
|
139
|
-
<Paper key={group.key} variant="outlined" sx={SECTION_NAV_GROUP_PAPER_SX}>
|
|
140
|
-
<Box id={groupHeadingId} sx={SECTION_NAV_GROUP_HEADER_SX}>
|
|
141
|
-
<Typography variant="subtitle2" fontWeight={700}>
|
|
142
|
-
{group.label}
|
|
143
|
-
</Typography>
|
|
144
|
-
</Box>
|
|
145
|
-
<List disablePadding aria-labelledby={groupHeadingId}>
|
|
146
|
-
{group.items.map((item) => {
|
|
147
|
-
const selected = item.key === activeKey;
|
|
148
|
-
return (
|
|
149
|
-
<ListItem key={item.key} disablePadding>
|
|
150
|
-
<ListItemButton
|
|
151
|
-
selected={selected}
|
|
152
|
-
aria-current={selected ? 'page' : undefined}
|
|
153
|
-
onClick={() => onSelect?.(item.key)}
|
|
154
|
-
>
|
|
155
|
-
<ListItemText
|
|
156
|
-
primary={item.label}
|
|
157
|
-
secondary={!selected && item.key === rememberedKey ? rememberedLabel : null}
|
|
158
|
-
/>
|
|
159
|
-
</ListItemButton>
|
|
160
|
-
</ListItem>
|
|
161
|
-
);
|
|
162
|
-
})}
|
|
163
|
-
</List>
|
|
164
|
-
</Paper>
|
|
165
|
-
);
|
|
166
|
-
})}
|
|
167
|
-
</Stack>
|
|
168
|
-
);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Render matrix:
|
|
173
|
-
*
|
|
174
|
-
* | `mode` | `open` | `onOpen` | trigger | sidebar | `children` | drawer |
|
|
175
|
-
* |-----------|------------------------|----------|---------|---------|------------------|---------------------|
|
|
176
|
-
* | `desktop` | ignored | ignored | — | yes | yes, in the grid | — |
|
|
177
|
-
* | `mobile` | absent (uncontrolled) | ignored | yes | — | yes | yes, internal state |
|
|
178
|
-
* | `mobile` | given | given | yes | — | yes | caller-driven |
|
|
179
|
-
* | `mobile` | given | absent | no | — | yes | caller-driven |
|
|
180
|
-
*
|
|
181
|
-
* Optional parts: without `overviewItem`, no overview entry or empty Paper is rendered;
|
|
182
|
-
* without `rememberedKey`, no secondary line is rendered; without `title` or
|
|
183
|
-
* `triggerEyebrow`, `t('SectionNav.TITLE')` or `t('SectionNav.TRIGGER_EYEBROW')` is used.
|
|
184
|
-
*/
|
|
185
|
-
export function SectionNav({
|
|
186
|
-
mode,
|
|
187
|
-
groups,
|
|
188
|
-
activeKey = null,
|
|
189
|
-
onSelect,
|
|
190
|
-
open,
|
|
191
|
-
onOpen,
|
|
192
|
-
onClose,
|
|
193
|
-
title,
|
|
194
|
-
triggerEyebrow,
|
|
195
|
-
overviewItem,
|
|
196
|
-
rememberedKey,
|
|
197
|
-
headerOffset = 24,
|
|
198
|
-
// SHELL-3's bottom bar uses drawer + 2, so the drawer must stay above it.
|
|
199
|
-
zIndex = defaultZIndex,
|
|
200
|
-
sidebarWidth = 280,
|
|
201
|
-
children,
|
|
202
|
-
}) {
|
|
203
|
-
const { t } = useTranslation();
|
|
204
|
-
const theme = useTheme();
|
|
205
|
-
const drawerId = useId();
|
|
206
|
-
const [internalOpen, setInternalOpen] = useState(false);
|
|
207
|
-
const isControlled = open !== undefined;
|
|
208
|
-
const drawerOpen = isControlled ? open : internalOpen;
|
|
209
|
-
const canOpenTrigger = !isControlled || Boolean(onOpen);
|
|
210
|
-
const resolvedTitle = title ?? t('SectionNav.TITLE');
|
|
211
|
-
const resolvedEyebrow = triggerEyebrow ?? t('SectionNav.TRIGGER_EYEBROW');
|
|
212
|
-
const rememberedLabel = t('SectionNav.LAST_OPENED');
|
|
213
|
-
const resolvedZIndex = typeof zIndex === 'function' ? zIndex(theme) : zIndex;
|
|
214
|
-
const resolvedHeaderOffset = typeof headerOffset === 'number' ? `${headerOffset}px` : headerOffset;
|
|
215
|
-
const activeItem = activeKey == null
|
|
216
|
-
? overviewItem
|
|
217
|
-
: groups.flatMap((group) => group.items).find((item) => item.key === activeKey);
|
|
218
|
-
|
|
219
|
-
const handleTriggerClick = () => {
|
|
220
|
-
if (isControlled) {
|
|
221
|
-
onOpen?.();
|
|
222
|
-
} else {
|
|
223
|
-
setInternalOpen(true);
|
|
224
|
-
}
|
|
225
|
-
};
|
|
226
|
-
|
|
227
|
-
const handleDrawerClose = () => {
|
|
228
|
-
if (!isControlled) setInternalOpen(false);
|
|
229
|
-
onClose?.();
|
|
230
|
-
};
|
|
231
|
-
|
|
232
|
-
const handleDrawerSelect = (key) => {
|
|
233
|
-
onSelect?.(key);
|
|
234
|
-
handleDrawerClose();
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
if (mode === 'desktop') {
|
|
238
|
-
return (
|
|
239
|
-
<Box
|
|
240
|
-
sx={{ ...SECTION_NAV_GRID_SX, gridTemplateColumns: `${sidebarWidth}px minmax(0, 1fr)` }}
|
|
241
|
-
data-section-nav-mode="desktop"
|
|
242
|
-
>
|
|
243
|
-
<Box
|
|
244
|
-
component="nav"
|
|
245
|
-
aria-label={resolvedTitle}
|
|
246
|
-
sx={SECTION_NAV_SIDEBAR_SX}
|
|
247
|
-
style={{ '--SectionNav-headerOffset': resolvedHeaderOffset }}
|
|
248
|
-
>
|
|
249
|
-
<SectionNavList
|
|
250
|
-
groups={groups}
|
|
251
|
-
activeKey={activeKey}
|
|
252
|
-
rememberedKey={rememberedKey}
|
|
253
|
-
overviewItem={overviewItem}
|
|
254
|
-
onSelect={onSelect}
|
|
255
|
-
rememberedLabel={rememberedLabel}
|
|
256
|
-
/>
|
|
257
|
-
</Box>
|
|
258
|
-
<Box sx={SECTION_NAV_CONTENT_SX}>{children}</Box>
|
|
259
|
-
</Box>
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<>
|
|
265
|
-
{canOpenTrigger && (
|
|
266
|
-
<ButtonBase
|
|
267
|
-
sx={SECTION_NAV_TRIGGER_SX}
|
|
268
|
-
onClick={handleTriggerClick}
|
|
269
|
-
aria-haspopup="dialog"
|
|
270
|
-
aria-expanded={drawerOpen}
|
|
271
|
-
aria-controls={drawerId}
|
|
272
|
-
>
|
|
273
|
-
<Box sx={SECTION_NAV_TRIGGER_TEXT_SX}>
|
|
274
|
-
<Typography variant="caption" color="text.secondary">
|
|
275
|
-
{resolvedEyebrow}
|
|
276
|
-
</Typography>
|
|
277
|
-
<Typography variant="subtitle1" sx={SECTION_NAV_TRIGGER_VALUE_SX}>
|
|
278
|
-
{activeItem?.label ?? resolvedTitle}
|
|
279
|
-
</Typography>
|
|
280
|
-
</Box>
|
|
281
|
-
<KeyboardArrowDownIcon
|
|
282
|
-
aria-hidden="true"
|
|
283
|
-
sx={SECTION_NAV_CHEVRON_SX}
|
|
284
|
-
style={{ transform: drawerOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
|
285
|
-
/>
|
|
286
|
-
</ButtonBase>
|
|
287
|
-
)}
|
|
288
|
-
|
|
289
|
-
<Box sx={SECTION_NAV_CONTENT_SX}>{children}</Box>
|
|
290
|
-
|
|
291
|
-
<Drawer
|
|
292
|
-
anchor="bottom"
|
|
293
|
-
open={drawerOpen}
|
|
294
|
-
onClose={handleDrawerClose}
|
|
295
|
-
style={{ zIndex: resolvedZIndex }}
|
|
296
|
-
PaperProps={{
|
|
297
|
-
id: drawerId,
|
|
298
|
-
role: 'dialog',
|
|
299
|
-
'aria-modal': true,
|
|
300
|
-
'aria-labelledby': `${drawerId}-title`,
|
|
301
|
-
sx: SECTION_NAV_DRAWER_PAPER_SX,
|
|
302
|
-
}}
|
|
303
|
-
>
|
|
304
|
-
<Typography id={`${drawerId}-title`} variant="h6" sx={SECTION_NAV_DRAWER_TITLE_SX}>
|
|
305
|
-
{resolvedTitle}
|
|
306
|
-
</Typography>
|
|
307
|
-
<Box sx={SECTION_NAV_DRAWER_SCROLL_SX}>
|
|
308
|
-
<SectionNavList
|
|
309
|
-
groups={groups}
|
|
310
|
-
activeKey={activeKey}
|
|
311
|
-
rememberedKey={rememberedKey}
|
|
312
|
-
overviewItem={overviewItem}
|
|
313
|
-
onSelect={handleDrawerSelect}
|
|
314
|
-
rememberedLabel={rememberedLabel}
|
|
315
|
-
/>
|
|
316
|
-
</Box>
|
|
317
|
-
</Drawer>
|
|
318
|
-
</>
|
|
319
|
-
);
|
|
320
|
-
}
|
|
@@ -1,145 +0,0 @@
|
|
|
1
|
-
import CloseIcon from '@mui/icons-material/Close';
|
|
2
|
-
import DownloadIcon from '@mui/icons-material/DownloadOutlined';
|
|
3
|
-
import InsertDriveFileOutlinedIcon from '@mui/icons-material/InsertDriveFileOutlined';
|
|
4
|
-
import { Alert, Box, ButtonBase, CircularProgress, Dialog, IconButton, Menu, MenuItem, Stack, Typography } from '@mui/material';
|
|
5
|
-
import { useEffect, useRef, useState } from 'react';
|
|
6
|
-
import { useTranslation } from 'react-i18next';
|
|
7
|
-
|
|
8
|
-
import { useMessaging } from './MessagingProvider';
|
|
9
|
-
|
|
10
|
-
export const ATTACHMENT_LIST_LIGHTBOX_ACTION_SX = {
|
|
11
|
-
bgcolor: 'background.paper',
|
|
12
|
-
boxShadow: 1,
|
|
13
|
-
};
|
|
14
|
-
export const ATTACHMENT_LIST_ALERT_SX = { m: 2 };
|
|
15
|
-
|
|
16
|
-
function isImage(attachment) { return attachment.content_type?.startsWith('image/'); }
|
|
17
|
-
// `filename` is dcm's real, sanitized upload name (MSG-12) -- `id` is the
|
|
18
|
-
// last-resort fallback for interop with a host still on a pre-MSG-12 pin.
|
|
19
|
-
function nameOf(attachment) { return attachment.filename || attachment.name || attachment.id; }
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* WhatsApp-style attachment gallery (MSG-6h): a small thumbnail per image
|
|
23
|
-
* attachment; click opens a full-size lightbox, which itself carries its own
|
|
24
|
-
* Download action (reachable by mouse, touch, or keyboard -- right-click is a
|
|
25
|
-
* shortcut, never the only path). A non-image attachment has no preview to
|
|
26
|
-
* open -- click downloads directly. Right-click still opens the same context
|
|
27
|
-
* menu on either kind, for the mouse-user shortcut. Bytes only ever move
|
|
28
|
-
* through the existing authenticated `getAttachment`/`getAttachmentThumbnail`
|
|
29
|
-
* blob endpoints -- no new byte-handling path.
|
|
30
|
-
*/
|
|
31
|
-
export function AttachmentList({ attachments = [] }) {
|
|
32
|
-
const { t } = useTranslation();
|
|
33
|
-
const { getAttachment, getAttachmentThumbnail } = useMessaging();
|
|
34
|
-
const [previews, setPreviews] = useState({});
|
|
35
|
-
// { attachment, url: string|null, status: 'loading'|'ready'|'error' } | null
|
|
36
|
-
const [lightbox, setLightbox] = useState(null);
|
|
37
|
-
const [menu, setMenu] = useState(null); // { attachment, anchorPosition } | null
|
|
38
|
-
// Full-resolution object URLs, fetched once per attachment on first
|
|
39
|
-
// lightbox open and reused for the rest of this mount (a re-open must not
|
|
40
|
-
// re-fetch what's already sitting in memory). Only ever revoked on
|
|
41
|
-
// unmount -- never while the Dialog might still be displaying one.
|
|
42
|
-
const fullUrlCacheRef = useRef({});
|
|
43
|
-
|
|
44
|
-
useEffect(() => {
|
|
45
|
-
let live = true; const urls = [];
|
|
46
|
-
attachments.filter(isImage).forEach(async (attachment) => {
|
|
47
|
-
try {
|
|
48
|
-
const blob = await getAttachmentThumbnail(attachment.id);
|
|
49
|
-
const url = blob instanceof Blob ? URL.createObjectURL(blob) : blob?.url;
|
|
50
|
-
if (url && live) { urls.push(url); setPreviews((current) => ({ ...current, [attachment.id]: url })); }
|
|
51
|
-
} catch { /* The image remains available through the download action. */ }
|
|
52
|
-
});
|
|
53
|
-
return () => { live = false; urls.forEach((url) => URL.revokeObjectURL(url)); };
|
|
54
|
-
}, [attachments, getAttachmentThumbnail]);
|
|
55
|
-
|
|
56
|
-
useEffect(() => () => { Object.values(fullUrlCacheRef.current).forEach((url) => URL.revokeObjectURL(url)); }, []);
|
|
57
|
-
|
|
58
|
-
if (!attachments.length) return null;
|
|
59
|
-
|
|
60
|
-
const download = async (attachment) => {
|
|
61
|
-
const blob = await getAttachment(attachment.id);
|
|
62
|
-
if (!(blob instanceof Blob)) return;
|
|
63
|
-
const url = URL.createObjectURL(blob); const link = document.createElement('a');
|
|
64
|
-
link.href = url; link.download = nameOf(attachment); link.click(); URL.revokeObjectURL(url);
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
const openLightbox = async (attachment) => {
|
|
68
|
-
const cachedUrl = fullUrlCacheRef.current[attachment.id];
|
|
69
|
-
if (cachedUrl) { setLightbox({ attachment, url: cachedUrl, status: 'ready' }); return; }
|
|
70
|
-
setLightbox({ attachment, url: null, status: 'loading' });
|
|
71
|
-
try {
|
|
72
|
-
const blob = await getAttachment(attachment.id);
|
|
73
|
-
if (!(blob instanceof Blob)) throw new Error('Attachment response was not a Blob');
|
|
74
|
-
const url = URL.createObjectURL(blob);
|
|
75
|
-
fullUrlCacheRef.current[attachment.id] = url;
|
|
76
|
-
// A newer open (a different attachment) may have superseded this one
|
|
77
|
-
// while the fetch was in flight -- don't stomp on it with a stale result.
|
|
78
|
-
setLightbox((current) => (current?.attachment.id === attachment.id ? { attachment, url, status: 'ready' } : current));
|
|
79
|
-
} catch {
|
|
80
|
-
setLightbox((current) => (current?.attachment.id === attachment.id ? { ...current, status: 'error' } : current));
|
|
81
|
-
}
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
const openMenu = (event, attachment) => {
|
|
85
|
-
event.preventDefault();
|
|
86
|
-
// Attachments live inside a message bubble that has its own right-click
|
|
87
|
-
// menu (MessageBubble.jsx's onContextMenu) -- this must not also open.
|
|
88
|
-
event.stopPropagation();
|
|
89
|
-
setMenu({ attachment, anchorPosition: { top: event.clientY, left: event.clientX } });
|
|
90
|
-
};
|
|
91
|
-
const closeMenu = () => setMenu(null);
|
|
92
|
-
|
|
93
|
-
return <>
|
|
94
|
-
<Stack direction="row" spacing={1} flexWrap="wrap" aria-label={t('MessagingAttachments.LABEL')}>
|
|
95
|
-
{attachments.map((attachment) => {
|
|
96
|
-
const image = isImage(attachment);
|
|
97
|
-
const preview = previews[attachment.id];
|
|
98
|
-
const label = image ? t('MessagingAttachments.PREVIEW', { name: nameOf(attachment) }) : t('MessagingAttachments.DOWNLOAD', { name: nameOf(attachment) });
|
|
99
|
-
return (
|
|
100
|
-
<ButtonBase
|
|
101
|
-
key={attachment.id}
|
|
102
|
-
type="button"
|
|
103
|
-
title={nameOf(attachment)}
|
|
104
|
-
onClick={() => (image ? openLightbox(attachment) : download(attachment))}
|
|
105
|
-
onContextMenu={(event) => openMenu(event, attachment)}
|
|
106
|
-
aria-label={label}
|
|
107
|
-
sx={{ width: 120, height: 120, borderRadius: 1, overflow: 'hidden', border: '1px solid', borderColor: 'divider', display: 'flex', alignItems: 'center', justifyContent: 'center', bgcolor: 'action.hover' }}
|
|
108
|
-
>
|
|
109
|
-
{image && preview
|
|
110
|
-
? <Box component="img" src={preview} alt="" sx={{ width: '100%', height: '100%', objectFit: 'cover' }} />
|
|
111
|
-
: <Stack alignItems="center" spacing={0.75} sx={{ px: 1, maxWidth: '100%' }}>
|
|
112
|
-
{/* Icon scaled up from `small` -- 120px tile (was 64px) made
|
|
113
|
-
the original small icon+caption pairing read sparse/
|
|
114
|
-
undersized against the much larger tile (MSG-6i). */}
|
|
115
|
-
<InsertDriveFileOutlinedIcon fontSize="large" />
|
|
116
|
-
<Typography variant="caption" noWrap sx={{ maxWidth: '100%' }}>{nameOf(attachment)}</Typography>
|
|
117
|
-
</Stack>}
|
|
118
|
-
</ButtonBase>
|
|
119
|
-
);
|
|
120
|
-
})}
|
|
121
|
-
</Stack>
|
|
122
|
-
|
|
123
|
-
<Dialog open={Boolean(lightbox)} onClose={() => setLightbox(null)} maxWidth="lg" aria-label={lightbox ? nameOf(lightbox.attachment) : undefined}>
|
|
124
|
-
{lightbox && <Box sx={{ position: 'relative', minWidth: 240, minHeight: lightbox.status === 'ready' ? undefined : 160, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
125
|
-
<Stack direction="row" spacing={0.5} sx={{ position: 'absolute', top: 4, right: 4 }}>
|
|
126
|
-
{lightbox.status === 'ready' && <IconButton aria-label={t('MessagingAttachments.DOWNLOAD_ACTION')} onClick={() => download(lightbox.attachment)} sx={ATTACHMENT_LIST_LIGHTBOX_ACTION_SX}>
|
|
127
|
-
<DownloadIcon />
|
|
128
|
-
</IconButton>}
|
|
129
|
-
<IconButton aria-label={t('MessagingAttachments.CLOSE_PREVIEW')} onClick={() => setLightbox(null)} sx={ATTACHMENT_LIST_LIGHTBOX_ACTION_SX}>
|
|
130
|
-
<CloseIcon />
|
|
131
|
-
</IconButton>
|
|
132
|
-
</Stack>
|
|
133
|
-
{lightbox.status === 'loading' && <CircularProgress aria-label={t('MessagingAttachments.LOADING_PREVIEW')} sx={{ m: 4 }} />}
|
|
134
|
-
{lightbox.status === 'error' && <Alert severity="error" role="alert" sx={ATTACHMENT_LIST_ALERT_SX}>{t('MessagingAttachments.PREVIEW_ERROR')}</Alert>}
|
|
135
|
-
{lightbox.status === 'ready' && <Box component="img" src={lightbox.url} alt={nameOf(lightbox.attachment)} sx={{ display: 'block', maxWidth: '90vw', maxHeight: '90vh' }} />}
|
|
136
|
-
</Box>}
|
|
137
|
-
</Dialog>
|
|
138
|
-
|
|
139
|
-
<Menu open={Boolean(menu)} onClose={closeMenu} anchorReference="anchorPosition" anchorPosition={menu?.anchorPosition}>
|
|
140
|
-
<MenuItem onClick={() => { const attachment = menu?.attachment; closeMenu(); if (attachment) download(attachment); }}>
|
|
141
|
-
<DownloadIcon fontSize="small" sx={{ mr: 1 }} />{t('MessagingAttachments.DOWNLOAD_ACTION')}
|
|
142
|
-
</MenuItem>
|
|
143
|
-
</Menu>
|
|
144
|
-
</>;
|
|
145
|
-
}
|