@micha.bigler/ui-core-micha 2.35.0 → 2.37.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 +4 -0
- package/WORK_ORDERS.md +10 -3
- package/dev/entries.jsx +79 -0
- package/dev/main.jsx +7 -1
- package/dist/i18n/authTranslations.js +24 -0
- package/dist/i18n/sectionNavTranslations.js +20 -0
- package/dist/index.js +10 -0
- package/dist/layout/SectionNav.js +138 -0
- package/dist/pages/AccountPage.js +44 -2
- package/dist/theme/kitSxRegistry.js +8 -1
- package/package.json +1 -1
- package/src/i18n/authTranslations.ts +24 -0
- package/src/i18n/sectionNavTranslations.ts +20 -0
- package/src/index.js +20 -0
- package/src/layout/SectionNav.jsx +299 -0
- package/src/pages/AccountPage.jsx +55 -14
- package/src/theme/kitSxRegistry.js +11 -1
- package/tests/AccountPage.test.jsx +193 -0
- package/tests/SectionNav.test.jsx +274 -0
- package/tests/i18nAggregate.test.js +121 -0
- package/work-orders/AUTH-4.md +183 -0
- package/work-orders/I18N-1.md +254 -0
- package/work-orders/SHELL-5.md +345 -0
- package/work-orders/assets/AUTH-4-user-list-card.html +407 -0
|
@@ -17,6 +17,7 @@ import { USER_INVITE_ACTION_SX, USER_INVITE_ALERT_SX } from '../components/UserI
|
|
|
17
17
|
import { USER_LIST_ACTION_SX, USER_LIST_ALERT_SX, USER_LIST_BODY_CELL_SX, USER_LIST_EMPTY_CELL_SX, USER_LIST_HEADER_CELL_SX, } from '../components/UserListComponent';
|
|
18
18
|
import { CHART_FRAME_ALERT_SX, CHART_FRAME_ROOT_SX } from '../components/charts/ChartFrame';
|
|
19
19
|
import { PAGE_LAYOUT_CONTAINER_SX } from '../layout/PageLayout';
|
|
20
|
+
import { SECTION_NAV_DRAWER_PAPER_SX, SECTION_NAV_GROUP_PAPER_SX, } from '../layout/SectionNav';
|
|
20
21
|
import { ATTACHMENT_LIST_ALERT_SX, ATTACHMENT_LIST_LIGHTBOX_ACTION_SX, } from '../messaging/AttachmentList';
|
|
21
22
|
import { COMPOSER_EMOJI_BUTTON_SX } from '../messaging/Composer';
|
|
22
23
|
import { DIRECT_MESSAGE_LAUNCHER_ALERT_SX } from '../messaging/DirectMessageLauncher';
|
|
@@ -88,6 +89,8 @@ const SX_EXPORTS = {
|
|
|
88
89
|
REGISTRATION_METHODS_INFO_SX,
|
|
89
90
|
SECURITY_ALERT_SX,
|
|
90
91
|
SECURITY_DIVIDER_SX,
|
|
92
|
+
SECTION_NAV_DRAWER_PAPER_SX,
|
|
93
|
+
SECTION_NAV_GROUP_PAPER_SX,
|
|
91
94
|
SIGNUP_CONFIRM_PAGE_ALERT_SX,
|
|
92
95
|
SIGN_UP_PAGE_ALERT_SX,
|
|
93
96
|
SUPPORT_RECOVERY_AGENT_BUTTON_SX,
|
|
@@ -103,10 +106,11 @@ const SX_EXPORTS = {
|
|
|
103
106
|
USER_LIST_HEADER_CELL_SX,
|
|
104
107
|
};
|
|
105
108
|
const SX_EXPORT_NAMES = new Map(Object.entries(SX_EXPORTS).map(([name, sx]) => [sx, name]));
|
|
106
|
-
const entry = (component, muiComponent, sx) => ({
|
|
109
|
+
const entry = (component, muiComponent, sx, slots) => ({
|
|
107
110
|
component,
|
|
108
111
|
muiComponent,
|
|
109
112
|
sx,
|
|
113
|
+
slots,
|
|
110
114
|
exportName: SX_EXPORT_NAMES.get(sx),
|
|
111
115
|
});
|
|
112
116
|
/**
|
|
@@ -160,6 +164,9 @@ export const KIT_COMPONENT_SX_REGISTRY = [
|
|
|
160
164
|
entry('ChartFrame.root', 'MuiPaper', CHART_FRAME_ROOT_SX),
|
|
161
165
|
entry('ChartFrame.alert', 'MuiAlert', CHART_FRAME_ALERT_SX),
|
|
162
166
|
entry('PageLayout.container', 'MuiContainer', PAGE_LAYOUT_CONTAINER_SX),
|
|
167
|
+
entry('SectionNav.drawerPaper', 'MuiDrawer', SECTION_NAV_DRAWER_PAPER_SX, ['paper']),
|
|
168
|
+
entry('SectionNav.drawerPaperBase', 'MuiPaper', SECTION_NAV_DRAWER_PAPER_SX),
|
|
169
|
+
entry('SectionNav.groupPaper', 'MuiPaper', SECTION_NAV_GROUP_PAPER_SX),
|
|
163
170
|
entry('AttachmentList.lightboxAction', 'MuiIconButton', ATTACHMENT_LIST_LIGHTBOX_ACTION_SX),
|
|
164
171
|
entry('AttachmentList.alert', 'MuiAlert', ATTACHMENT_LIST_ALERT_SX),
|
|
165
172
|
entry('Composer.emojiAction', 'MuiIconButton', COMPOSER_EMOJI_BUTTON_SX),
|
package/package.json
CHANGED
|
@@ -1667,6 +1667,30 @@ export const authTranslations = {
|
|
|
1667
1667
|
"en": "Support",
|
|
1668
1668
|
"sw": "Usaidizi"
|
|
1669
1669
|
},
|
|
1670
|
+
"Account.GROUP_MY_ACCOUNT": {
|
|
1671
|
+
"de": "Mein Konto",
|
|
1672
|
+
"fr": "Mon compte",
|
|
1673
|
+
"en": "My account",
|
|
1674
|
+
"sw": "Akaunti yangu"
|
|
1675
|
+
},
|
|
1676
|
+
"Account.GROUP_MANAGEMENT": {
|
|
1677
|
+
"de": "Verwaltung",
|
|
1678
|
+
"fr": "Administration",
|
|
1679
|
+
"en": "Management",
|
|
1680
|
+
"sw": "Usimamizi"
|
|
1681
|
+
},
|
|
1682
|
+
"Account.GROUP_HELP": {
|
|
1683
|
+
"de": "Hilfe",
|
|
1684
|
+
"fr": "Aide",
|
|
1685
|
+
"en": "Help",
|
|
1686
|
+
"sw": "Msaada"
|
|
1687
|
+
},
|
|
1688
|
+
"Account.GROUP_MORE": {
|
|
1689
|
+
"de": "Weitere",
|
|
1690
|
+
"fr": "Autres",
|
|
1691
|
+
"en": "More",
|
|
1692
|
+
"sw": "Zaidi"
|
|
1693
|
+
},
|
|
1670
1694
|
"Account.ACCESS_CODES_HINT": {
|
|
1671
1695
|
"de": "Verwalten Sie Zugangscodes für die Selbstregistrierung.",
|
|
1672
1696
|
"fr": "Gérer les codes d'accès pour l'auto-inscription.",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const sectionNavTranslations = {
|
|
2
|
+
'SectionNav.TITLE': {
|
|
3
|
+
de: 'Bereich wechseln',
|
|
4
|
+
fr: 'Changer de section',
|
|
5
|
+
en: 'Switch section',
|
|
6
|
+
sw: 'Badilisha sehemu',
|
|
7
|
+
},
|
|
8
|
+
'SectionNav.TRIGGER_EYEBROW': {
|
|
9
|
+
de: 'Bereich',
|
|
10
|
+
fr: 'Section',
|
|
11
|
+
en: 'Section',
|
|
12
|
+
sw: 'Sehemu',
|
|
13
|
+
},
|
|
14
|
+
'SectionNav.LAST_OPENED': {
|
|
15
|
+
de: 'Zuletzt geöffnet',
|
|
16
|
+
fr: 'Ouvert en dernier',
|
|
17
|
+
en: 'Last opened',
|
|
18
|
+
sw: 'Iliyofunguliwa mwisho',
|
|
19
|
+
},
|
|
20
|
+
};
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
// index.js (Entry Point deiner Library)
|
|
2
2
|
|
|
3
|
+
import { authTranslations } from './i18n/authTranslations';
|
|
4
|
+
import { chartsTranslations } from './i18n/chartsTranslations';
|
|
5
|
+
import { messagingTranslations } from './i18n/messagingTranslations';
|
|
6
|
+
import { notificationsTranslations } from './i18n/notificationsTranslations';
|
|
7
|
+
import { onboardingTranslations } from './i18n/onboardingTranslations';
|
|
8
|
+
import { sectionNavTranslations } from './i18n/sectionNavTranslations';
|
|
9
|
+
import { userMenuTranslations } from './i18n/userMenuTranslations';
|
|
10
|
+
|
|
3
11
|
// --- 0. Theme ---
|
|
4
12
|
export {
|
|
5
13
|
createAppTheme,
|
|
@@ -32,6 +40,7 @@ export * from './utils/authService'; // Service-Funktionen (Passkeys, Social)
|
|
|
32
40
|
|
|
33
41
|
// --- 3. Layouts ---
|
|
34
42
|
export { NarrowPage, WidePage } from './layout/PageLayout';
|
|
43
|
+
export { SectionNav } from './layout/SectionNav';
|
|
35
44
|
|
|
36
45
|
// --- 4. Pages (Vollständige Seiten für Routing) ---
|
|
37
46
|
export { LoginPage } from './pages/LoginPage';
|
|
@@ -78,6 +87,7 @@ export {
|
|
|
78
87
|
// --- 7. Translations ---
|
|
79
88
|
export { authTranslations } from './i18n/authTranslations';
|
|
80
89
|
export { userMenuTranslations } from './i18n/userMenuTranslations';
|
|
90
|
+
export { sectionNavTranslations } from './i18n/sectionNavTranslations';
|
|
81
91
|
|
|
82
92
|
// --- 8. Notifications ---
|
|
83
93
|
export { NotificationSettings } from './notifications/NotificationSettings';
|
|
@@ -123,3 +133,13 @@ export { notificationsTranslations } from './i18n/notificationsTranslations';
|
|
|
123
133
|
export { onboardingTranslations } from './i18n/onboardingTranslations';
|
|
124
134
|
export { chartsTranslations } from './i18n/chartsTranslations';
|
|
125
135
|
export { messagingTranslations } from './i18n/messagingTranslations';
|
|
136
|
+
|
|
137
|
+
export const uiCoreTranslations = {
|
|
138
|
+
...authTranslations,
|
|
139
|
+
...chartsTranslations,
|
|
140
|
+
...messagingTranslations,
|
|
141
|
+
...notificationsTranslations,
|
|
142
|
+
...onboardingTranslations,
|
|
143
|
+
...sectionNavTranslations,
|
|
144
|
+
...userMenuTranslations,
|
|
145
|
+
};
|
|
@@ -0,0 +1,299 @@
|
|
|
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
|
+
ListItemButton,
|
|
9
|
+
ListItemText,
|
|
10
|
+
Paper,
|
|
11
|
+
Stack,
|
|
12
|
+
Typography,
|
|
13
|
+
} from '@mui/material';
|
|
14
|
+
import { useTheme } from '@mui/material/styles';
|
|
15
|
+
import { useTranslation } from 'react-i18next';
|
|
16
|
+
|
|
17
|
+
const defaultZIndex = (theme) => theme.zIndex.drawer + 3;
|
|
18
|
+
|
|
19
|
+
export const SECTION_NAV_DRAWER_PAPER_SX = {
|
|
20
|
+
borderTopLeftRadius: 18,
|
|
21
|
+
borderTopRightRadius: 18,
|
|
22
|
+
maxHeight: '78dvh',
|
|
23
|
+
p: 2,
|
|
24
|
+
pb: 'max(16px, env(safe-area-inset-bottom))',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const SECTION_NAV_GROUP_PAPER_SX = {
|
|
28
|
+
overflow: 'hidden',
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const SECTION_NAV_GRID_SX = {
|
|
32
|
+
display: 'grid',
|
|
33
|
+
gap: 4,
|
|
34
|
+
alignItems: 'start',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const SECTION_NAV_SIDEBAR_SX = {
|
|
38
|
+
position: 'sticky',
|
|
39
|
+
top: 'var(--SectionNav-headerOffset)',
|
|
40
|
+
alignSelf: 'start',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const SECTION_NAV_CONTENT_SX = {
|
|
44
|
+
minWidth: 0,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const SECTION_NAV_GROUP_HEADER_SX = {
|
|
48
|
+
px: 2,
|
|
49
|
+
py: 1.5,
|
|
50
|
+
borderBottom: '1px solid',
|
|
51
|
+
borderColor: 'divider',
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const SECTION_NAV_TRIGGER_SX = {
|
|
55
|
+
width: '100%',
|
|
56
|
+
minHeight: 56,
|
|
57
|
+
mb: 2,
|
|
58
|
+
px: 1.75,
|
|
59
|
+
py: 1,
|
|
60
|
+
display: 'flex',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'space-between',
|
|
63
|
+
gap: 2,
|
|
64
|
+
textAlign: 'left',
|
|
65
|
+
border: '1px solid',
|
|
66
|
+
borderColor: 'divider',
|
|
67
|
+
borderRadius: 1,
|
|
68
|
+
backgroundColor: 'background.paper',
|
|
69
|
+
boxShadow: 1,
|
|
70
|
+
'&:hover': {
|
|
71
|
+
backgroundColor: 'action.hover',
|
|
72
|
+
},
|
|
73
|
+
'&.Mui-focusVisible': {
|
|
74
|
+
outline: '2px solid',
|
|
75
|
+
outlineColor: 'primary.main',
|
|
76
|
+
outlineOffset: 2,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const SECTION_NAV_TRIGGER_TEXT_SX = {
|
|
81
|
+
minWidth: 0,
|
|
82
|
+
display: 'flex',
|
|
83
|
+
flexDirection: 'column',
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const SECTION_NAV_TRIGGER_VALUE_SX = {
|
|
87
|
+
overflow: 'hidden',
|
|
88
|
+
textOverflow: 'ellipsis',
|
|
89
|
+
whiteSpace: 'nowrap',
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const SECTION_NAV_CHEVRON_SX = {
|
|
93
|
+
color: 'text.secondary',
|
|
94
|
+
transition: 'transform 0.18s ease',
|
|
95
|
+
flex: 'none',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const SECTION_NAV_DRAWER_TITLE_SX = {
|
|
99
|
+
mb: 2,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const SECTION_NAV_DRAWER_SCROLL_SX = {
|
|
103
|
+
overflowY: 'auto',
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
function SectionNavList({
|
|
107
|
+
groups,
|
|
108
|
+
activeKey,
|
|
109
|
+
rememberedKey,
|
|
110
|
+
overviewItem,
|
|
111
|
+
onSelect,
|
|
112
|
+
rememberedLabel,
|
|
113
|
+
}) {
|
|
114
|
+
const headingId = useId();
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<Stack spacing={2}>
|
|
118
|
+
{overviewItem && (
|
|
119
|
+
<Paper variant="outlined" sx={SECTION_NAV_GROUP_PAPER_SX}>
|
|
120
|
+
<List disablePadding>
|
|
121
|
+
<ListItemButton
|
|
122
|
+
selected={activeKey == null}
|
|
123
|
+
aria-current={activeKey == null ? 'page' : undefined}
|
|
124
|
+
onClick={() => onSelect?.(null)}
|
|
125
|
+
>
|
|
126
|
+
<ListItemText primary={overviewItem.label} />
|
|
127
|
+
</ListItemButton>
|
|
128
|
+
</List>
|
|
129
|
+
</Paper>
|
|
130
|
+
)}
|
|
131
|
+
|
|
132
|
+
{groups.map((group, groupIndex) => {
|
|
133
|
+
const groupHeadingId = `${headingId}-${groupIndex}`;
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Paper key={group.key} variant="outlined" sx={SECTION_NAV_GROUP_PAPER_SX}>
|
|
137
|
+
<Box id={groupHeadingId} sx={SECTION_NAV_GROUP_HEADER_SX}>
|
|
138
|
+
<Typography variant="subtitle2" fontWeight={700}>
|
|
139
|
+
{group.label}
|
|
140
|
+
</Typography>
|
|
141
|
+
</Box>
|
|
142
|
+
<List disablePadding aria-labelledby={groupHeadingId}>
|
|
143
|
+
{group.items.map((item) => {
|
|
144
|
+
const selected = item.key === activeKey;
|
|
145
|
+
return (
|
|
146
|
+
<ListItemButton
|
|
147
|
+
key={item.key}
|
|
148
|
+
selected={selected}
|
|
149
|
+
aria-current={selected ? 'page' : undefined}
|
|
150
|
+
onClick={() => onSelect?.(item.key)}
|
|
151
|
+
>
|
|
152
|
+
<ListItemText
|
|
153
|
+
primary={item.label}
|
|
154
|
+
secondary={!selected && item.key === rememberedKey ? rememberedLabel : null}
|
|
155
|
+
/>
|
|
156
|
+
</ListItemButton>
|
|
157
|
+
);
|
|
158
|
+
})}
|
|
159
|
+
</List>
|
|
160
|
+
</Paper>
|
|
161
|
+
);
|
|
162
|
+
})}
|
|
163
|
+
</Stack>
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function SectionNav({
|
|
168
|
+
mode,
|
|
169
|
+
groups,
|
|
170
|
+
activeKey = null,
|
|
171
|
+
onSelect,
|
|
172
|
+
open,
|
|
173
|
+
onOpen,
|
|
174
|
+
onClose,
|
|
175
|
+
title,
|
|
176
|
+
triggerEyebrow,
|
|
177
|
+
overviewItem,
|
|
178
|
+
rememberedKey,
|
|
179
|
+
headerOffset = 24,
|
|
180
|
+
// SHELL-3's bottom bar uses drawer + 2, so the drawer must stay above it.
|
|
181
|
+
zIndex = defaultZIndex,
|
|
182
|
+
sidebarWidth = 280,
|
|
183
|
+
children,
|
|
184
|
+
}) {
|
|
185
|
+
const { t } = useTranslation();
|
|
186
|
+
const theme = useTheme();
|
|
187
|
+
const drawerId = useId();
|
|
188
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
189
|
+
const isControlled = open !== undefined;
|
|
190
|
+
const drawerOpen = isControlled ? open : internalOpen;
|
|
191
|
+
const resolvedTitle = title ?? t('SectionNav.TITLE');
|
|
192
|
+
const resolvedEyebrow = triggerEyebrow ?? t('SectionNav.TRIGGER_EYEBROW');
|
|
193
|
+
const rememberedLabel = t('SectionNav.LAST_OPENED');
|
|
194
|
+
const resolvedZIndex = typeof zIndex === 'function' ? zIndex(theme) : zIndex;
|
|
195
|
+
const resolvedHeaderOffset = typeof headerOffset === 'number' ? `${headerOffset}px` : headerOffset;
|
|
196
|
+
const activeItem = activeKey == null
|
|
197
|
+
? overviewItem
|
|
198
|
+
: groups.flatMap((group) => group.items).find((item) => item.key === activeKey);
|
|
199
|
+
|
|
200
|
+
const handleTriggerClick = () => {
|
|
201
|
+
if (isControlled) {
|
|
202
|
+
onOpen?.();
|
|
203
|
+
} else {
|
|
204
|
+
setInternalOpen(true);
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
const handleDrawerClose = () => {
|
|
209
|
+
if (!isControlled) setInternalOpen(false);
|
|
210
|
+
onClose?.();
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const handleDrawerSelect = (key) => {
|
|
214
|
+
onSelect?.(key);
|
|
215
|
+
handleDrawerClose();
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
if (mode === 'desktop') {
|
|
219
|
+
return (
|
|
220
|
+
<Box
|
|
221
|
+
sx={{ ...SECTION_NAV_GRID_SX, gridTemplateColumns: `${sidebarWidth}px minmax(0, 1fr)` }}
|
|
222
|
+
data-section-nav-mode="desktop"
|
|
223
|
+
>
|
|
224
|
+
<Box
|
|
225
|
+
component="nav"
|
|
226
|
+
aria-label={resolvedTitle}
|
|
227
|
+
sx={SECTION_NAV_SIDEBAR_SX}
|
|
228
|
+
style={{ '--SectionNav-headerOffset': resolvedHeaderOffset }}
|
|
229
|
+
>
|
|
230
|
+
<SectionNavList
|
|
231
|
+
groups={groups}
|
|
232
|
+
activeKey={activeKey}
|
|
233
|
+
rememberedKey={rememberedKey}
|
|
234
|
+
overviewItem={overviewItem}
|
|
235
|
+
onSelect={onSelect}
|
|
236
|
+
rememberedLabel={rememberedLabel}
|
|
237
|
+
/>
|
|
238
|
+
</Box>
|
|
239
|
+
<Box sx={SECTION_NAV_CONTENT_SX}>{children}</Box>
|
|
240
|
+
</Box>
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return (
|
|
245
|
+
<>
|
|
246
|
+
<ButtonBase
|
|
247
|
+
sx={SECTION_NAV_TRIGGER_SX}
|
|
248
|
+
onClick={handleTriggerClick}
|
|
249
|
+
aria-haspopup="dialog"
|
|
250
|
+
aria-expanded={drawerOpen}
|
|
251
|
+
aria-controls={drawerId}
|
|
252
|
+
>
|
|
253
|
+
<Box sx={SECTION_NAV_TRIGGER_TEXT_SX}>
|
|
254
|
+
<Typography variant="caption" color="text.secondary">
|
|
255
|
+
{resolvedEyebrow}
|
|
256
|
+
</Typography>
|
|
257
|
+
<Typography variant="subtitle1" sx={SECTION_NAV_TRIGGER_VALUE_SX}>
|
|
258
|
+
{activeItem?.label ?? resolvedTitle}
|
|
259
|
+
</Typography>
|
|
260
|
+
</Box>
|
|
261
|
+
<KeyboardArrowDownIcon
|
|
262
|
+
aria-hidden="true"
|
|
263
|
+
sx={SECTION_NAV_CHEVRON_SX}
|
|
264
|
+
style={{ transform: drawerOpen ? 'rotate(180deg)' : 'rotate(0deg)' }}
|
|
265
|
+
/>
|
|
266
|
+
</ButtonBase>
|
|
267
|
+
|
|
268
|
+
<Box sx={SECTION_NAV_CONTENT_SX}>{children}</Box>
|
|
269
|
+
|
|
270
|
+
<Drawer
|
|
271
|
+
anchor="bottom"
|
|
272
|
+
open={drawerOpen}
|
|
273
|
+
onClose={handleDrawerClose}
|
|
274
|
+
style={{ zIndex: resolvedZIndex }}
|
|
275
|
+
PaperProps={{
|
|
276
|
+
id: drawerId,
|
|
277
|
+
role: 'dialog',
|
|
278
|
+
'aria-modal': true,
|
|
279
|
+
'aria-labelledby': `${drawerId}-title`,
|
|
280
|
+
sx: SECTION_NAV_DRAWER_PAPER_SX,
|
|
281
|
+
}}
|
|
282
|
+
>
|
|
283
|
+
<Typography id={`${drawerId}-title`} variant="h6" sx={SECTION_NAV_DRAWER_TITLE_SX}>
|
|
284
|
+
{resolvedTitle}
|
|
285
|
+
</Typography>
|
|
286
|
+
<Box sx={SECTION_NAV_DRAWER_SCROLL_SX}>
|
|
287
|
+
<SectionNavList
|
|
288
|
+
groups={groups}
|
|
289
|
+
activeKey={activeKey}
|
|
290
|
+
rememberedKey={rememberedKey}
|
|
291
|
+
overviewItem={overviewItem}
|
|
292
|
+
onSelect={handleDrawerSelect}
|
|
293
|
+
rememberedLabel={rememberedLabel}
|
|
294
|
+
/>
|
|
295
|
+
</Box>
|
|
296
|
+
</Drawer>
|
|
297
|
+
</>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
@@ -2,15 +2,15 @@ import React, { useContext, useEffect, useMemo, useState } from 'react';
|
|
|
2
2
|
import { Helmet } from 'react-helmet';
|
|
3
3
|
import { useSearchParams } from 'react-router-dom';
|
|
4
4
|
import {
|
|
5
|
-
Tabs,
|
|
6
|
-
Tab,
|
|
7
5
|
Box,
|
|
8
6
|
Typography,
|
|
9
7
|
Alert,
|
|
10
8
|
CircularProgress,
|
|
11
9
|
Paper,
|
|
12
10
|
Stack,
|
|
11
|
+
useMediaQuery,
|
|
13
12
|
} from '@mui/material';
|
|
13
|
+
import { useTheme } from '@mui/material/styles';
|
|
14
14
|
import { useTranslation } from 'react-i18next';
|
|
15
15
|
|
|
16
16
|
// Internal context
|
|
@@ -20,6 +20,7 @@ export const ACCOUNT_PAGE_SECTION_PAPER_SX = { p: 2.5 };
|
|
|
20
20
|
|
|
21
21
|
// Falls die Komponenten noch lokal sind:
|
|
22
22
|
import { WidePage } from '../layout/PageLayout';
|
|
23
|
+
import { SectionNav } from '../layout/SectionNav';
|
|
23
24
|
import { ProfileComponent } from '../components/ProfileComponent';
|
|
24
25
|
import { SecurityComponent } from '../components/SecurityComponent';
|
|
25
26
|
import { UserListComponent } from '../components/UserListComponent';
|
|
@@ -57,6 +58,8 @@ export function AccountPage({
|
|
|
57
58
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
58
59
|
const [authPolicy, setAuthPolicy] = useState(null);
|
|
59
60
|
const [authPolicyError, setAuthPolicyError] = useState('');
|
|
61
|
+
const theme = useTheme();
|
|
62
|
+
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
60
63
|
|
|
61
64
|
// 1. URL State Management
|
|
62
65
|
const currentTabRaw = searchParams.get('tab') || 'profile';
|
|
@@ -159,6 +162,49 @@ export function AccountPage({
|
|
|
159
162
|
return list;
|
|
160
163
|
}, [user, perms, t, isSuperUser, extraTabs, canViewUsers, canViewInvite]);
|
|
161
164
|
|
|
165
|
+
const groups = useMemo(() => {
|
|
166
|
+
const groupDefinitions = [
|
|
167
|
+
{
|
|
168
|
+
key: 'my-account',
|
|
169
|
+
label: t('Account.GROUP_MY_ACCOUNT', 'My account'),
|
|
170
|
+
values: new Set(['profile', 'security']),
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'management',
|
|
174
|
+
label: t('Account.GROUP_MANAGEMENT', 'Management'),
|
|
175
|
+
values: new Set(['users', 'invite']),
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
key: 'help',
|
|
179
|
+
label: t('Account.GROUP_HELP', 'Help'),
|
|
180
|
+
values: new Set(['support']),
|
|
181
|
+
},
|
|
182
|
+
];
|
|
183
|
+
const assignedValues = new Set(
|
|
184
|
+
groupDefinitions.flatMap((group) => [...group.values]),
|
|
185
|
+
);
|
|
186
|
+
const mappedGroups = groupDefinitions.map((group) => ({
|
|
187
|
+
key: group.key,
|
|
188
|
+
label: group.label,
|
|
189
|
+
items: tabs
|
|
190
|
+
.filter((tab) => group.values.has(tab.value))
|
|
191
|
+
.map((tab) => ({ key: tab.value, label: tab.label })),
|
|
192
|
+
}));
|
|
193
|
+
const extraItems = tabs
|
|
194
|
+
.filter((tab) => !assignedValues.has(tab.value))
|
|
195
|
+
.map((tab) => ({ key: tab.value, label: tab.label }));
|
|
196
|
+
|
|
197
|
+
if (extraItems.length > 0) {
|
|
198
|
+
mappedGroups.push({
|
|
199
|
+
key: 'more',
|
|
200
|
+
label: t('Account.GROUP_MORE', 'More'),
|
|
201
|
+
items: extraItems,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return mappedGroups.filter((group) => group.items.length > 0);
|
|
206
|
+
}, [tabs, t]);
|
|
207
|
+
|
|
162
208
|
// 4. Loading & Auth Checks
|
|
163
209
|
if (loading) {
|
|
164
210
|
return (
|
|
@@ -193,19 +239,13 @@ export function AccountPage({
|
|
|
193
239
|
<title>{t('Account.PAGE_TITLE', 'Account')} – {user.email}</title>
|
|
194
240
|
</Helmet>
|
|
195
241
|
|
|
196
|
-
<
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
sx={{ mb: 3, borderBottom: 1, borderColor: 'divider' }}
|
|
242
|
+
<SectionNav
|
|
243
|
+
mode={isMobile ? 'mobile' : 'desktop'}
|
|
244
|
+
groups={groups}
|
|
245
|
+
activeKey={safeTab}
|
|
246
|
+
onSelect={(key) => handleTabChange(null, key)}
|
|
202
247
|
>
|
|
203
|
-
{
|
|
204
|
-
<Tab key={tab.value} label={tab.label} value={tab.value} />
|
|
205
|
-
))}
|
|
206
|
-
</Tabs>
|
|
207
|
-
|
|
208
|
-
{/* --- TAB CONTENT --- */}
|
|
248
|
+
{/* --- TAB CONTENT --- */}
|
|
209
249
|
|
|
210
250
|
{safeTab === 'profile' && (
|
|
211
251
|
<Box sx={{ mt: 2 }}>
|
|
@@ -347,6 +387,7 @@ export function AccountPage({
|
|
|
347
387
|
{activeExtraTab.render?.({ user, perms, isSuperUser, t })}
|
|
348
388
|
</Box>
|
|
349
389
|
)}
|
|
390
|
+
</SectionNav>
|
|
350
391
|
</WidePage>
|
|
351
392
|
);
|
|
352
393
|
}
|
|
@@ -56,6 +56,10 @@ import {
|
|
|
56
56
|
} from '../components/UserListComponent';
|
|
57
57
|
import { CHART_FRAME_ALERT_SX, CHART_FRAME_ROOT_SX } from '../components/charts/ChartFrame';
|
|
58
58
|
import { PAGE_LAYOUT_CONTAINER_SX } from '../layout/PageLayout';
|
|
59
|
+
import {
|
|
60
|
+
SECTION_NAV_DRAWER_PAPER_SX,
|
|
61
|
+
SECTION_NAV_GROUP_PAPER_SX,
|
|
62
|
+
} from '../layout/SectionNav';
|
|
59
63
|
import {
|
|
60
64
|
ATTACHMENT_LIST_ALERT_SX,
|
|
61
65
|
ATTACHMENT_LIST_LIGHTBOX_ACTION_SX,
|
|
@@ -142,6 +146,8 @@ const SX_EXPORTS = {
|
|
|
142
146
|
REGISTRATION_METHODS_INFO_SX,
|
|
143
147
|
SECURITY_ALERT_SX,
|
|
144
148
|
SECURITY_DIVIDER_SX,
|
|
149
|
+
SECTION_NAV_DRAWER_PAPER_SX,
|
|
150
|
+
SECTION_NAV_GROUP_PAPER_SX,
|
|
145
151
|
SIGNUP_CONFIRM_PAGE_ALERT_SX,
|
|
146
152
|
SIGN_UP_PAGE_ALERT_SX,
|
|
147
153
|
SUPPORT_RECOVERY_AGENT_BUTTON_SX,
|
|
@@ -161,10 +167,11 @@ const SX_EXPORT_NAMES = new Map(
|
|
|
161
167
|
Object.entries(SX_EXPORTS).map(([name, sx]) => [sx, name]),
|
|
162
168
|
);
|
|
163
169
|
|
|
164
|
-
const entry = (component, muiComponent, sx) => ({
|
|
170
|
+
const entry = (component, muiComponent, sx, slots) => ({
|
|
165
171
|
component,
|
|
166
172
|
muiComponent,
|
|
167
173
|
sx,
|
|
174
|
+
slots,
|
|
168
175
|
exportName: SX_EXPORT_NAMES.get(sx),
|
|
169
176
|
});
|
|
170
177
|
|
|
@@ -219,6 +226,9 @@ export const KIT_COMPONENT_SX_REGISTRY = [
|
|
|
219
226
|
entry('ChartFrame.root', 'MuiPaper', CHART_FRAME_ROOT_SX),
|
|
220
227
|
entry('ChartFrame.alert', 'MuiAlert', CHART_FRAME_ALERT_SX),
|
|
221
228
|
entry('PageLayout.container', 'MuiContainer', PAGE_LAYOUT_CONTAINER_SX),
|
|
229
|
+
entry('SectionNav.drawerPaper', 'MuiDrawer', SECTION_NAV_DRAWER_PAPER_SX, ['paper']),
|
|
230
|
+
entry('SectionNav.drawerPaperBase', 'MuiPaper', SECTION_NAV_DRAWER_PAPER_SX),
|
|
231
|
+
entry('SectionNav.groupPaper', 'MuiPaper', SECTION_NAV_GROUP_PAPER_SX),
|
|
222
232
|
entry('AttachmentList.lightboxAction', 'MuiIconButton', ATTACHMENT_LIST_LIGHTBOX_ACTION_SX),
|
|
223
233
|
entry('AttachmentList.alert', 'MuiAlert', ATTACHMENT_LIST_ALERT_SX),
|
|
224
234
|
entry('Composer.emojiAction', 'MuiIconButton', COMPOSER_EMOJI_BUTTON_SX),
|