@micha.bigler/ui-core-micha 2.29.1 → 2.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/DESIGN.md +5 -5
- package/WORK_ORDERS.md +9 -3
- package/dev/entries.jsx +159 -11
- package/dev/main.jsx +10 -3
- package/dist/components/charts/palette.js +7 -3
- package/dist/i18n/authTranslations.js +4 -4
- package/dist/index.js +2 -0
- package/dist/pages/SignUpPage.js +6 -2
- package/dist/theme/createAppTheme.js +168 -0
- package/dist/theme/index.js +2 -0
- package/dist/theme/themeCompleteness.js +320 -0
- package/dist/theme/tokens.js +325 -0
- package/package.json +4 -1
- package/src/components/charts/palette.js +6 -3
- package/src/i18n/authTranslations.ts +4 -4
- package/src/index.js +9 -0
- package/src/pages/SignUpPage.jsx +6 -2
- package/src/theme/createAppTheme.js +212 -0
- package/src/theme/index.js +7 -0
- package/src/theme/themeCompleteness.js +355 -0
- package/src/theme/tokens.js +376 -0
- package/tests/SignUpPage.test.jsx +46 -0
- package/tests/chartsPalette.test.js +40 -0
- package/tests/createAppTheme.test.js +103 -0
- package/tests/themeCompleteness.test.js +63 -0
- package/work-orders/CHART-6.md +230 -0
- package/work-orders/SHELL-2.md +197 -0
- package/work-orders/THEME-1.md +587 -0
- package/work-orders/assets/SHELL-2-account-navigation.html +1159 -0
- package/work-orders/assets/THEME-1-baseline-reference-sheet.html +1352 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { darken, lighten } from '@mui/material/styles';
|
|
2
|
+
// MUI's palette augmentation (createPalette -> augmentColor) only runs on the
|
|
3
|
+
// FIRST argument passed to createTheme; every later argument -- which is how
|
|
4
|
+
// createAppTheme layers BASELINE_PALETTE on top -- is deep-merged as a raw
|
|
5
|
+
// object with no augmentation. A status entry missing `main`/`contrastText`
|
|
6
|
+
// therefore silently keeps MUI's stock hue for `color="success"` etc. even
|
|
7
|
+
// though `text`/`fill`/`bg` are set correctly, so every status channel MUI
|
|
8
|
+
// recognises (success/warning/error/info) must carry its own explicit
|
|
9
|
+
// main/light/dark/contrastText, computed the same way augmentColor would.
|
|
10
|
+
const withMainShades = (main, contrastText) => ({
|
|
11
|
+
main,
|
|
12
|
+
light: lighten(main, 0.2),
|
|
13
|
+
dark: darken(main, 0.3),
|
|
14
|
+
contrastText,
|
|
15
|
+
});
|
|
16
|
+
export const SERIES_COLOURS = [
|
|
17
|
+
'#3D5A99',
|
|
18
|
+
'#3E80B8',
|
|
19
|
+
'#2E8F8A',
|
|
20
|
+
'#7A5FA8',
|
|
21
|
+
'#9C4F86',
|
|
22
|
+
'#8A7355',
|
|
23
|
+
];
|
|
24
|
+
export const STATUS_KEYS = ['success', 'warning', 'error', 'info', 'stale'];
|
|
25
|
+
export const TYPOGRAPHY_VARIANTS = [
|
|
26
|
+
'h1',
|
|
27
|
+
'h2',
|
|
28
|
+
'h3',
|
|
29
|
+
'h4',
|
|
30
|
+
'h5',
|
|
31
|
+
'h6',
|
|
32
|
+
'subtitle1',
|
|
33
|
+
'subtitle2',
|
|
34
|
+
'body1',
|
|
35
|
+
'body2',
|
|
36
|
+
'button',
|
|
37
|
+
'caption',
|
|
38
|
+
'overline',
|
|
39
|
+
];
|
|
40
|
+
const typeVariant = (fontSize, fontWeight, lineHeight, extra = {}) => (Object.assign({ fontSize,
|
|
41
|
+
fontWeight,
|
|
42
|
+
lineHeight, letterSpacing: 0 }, extra));
|
|
43
|
+
export const BASELINE_PALETTE = {
|
|
44
|
+
ink: {
|
|
45
|
+
primary: '#212529',
|
|
46
|
+
secondary: '#5B6670',
|
|
47
|
+
muted: '#6A7178',
|
|
48
|
+
},
|
|
49
|
+
text: {
|
|
50
|
+
primary: '#212529',
|
|
51
|
+
secondary: '#5B6670',
|
|
52
|
+
disabled: '#6A7178',
|
|
53
|
+
},
|
|
54
|
+
background: {
|
|
55
|
+
default: '#FAFAFA',
|
|
56
|
+
paper: '#FFFFFF',
|
|
57
|
+
},
|
|
58
|
+
divider: 'rgba(33,37,41,.10)',
|
|
59
|
+
controlBorder: {
|
|
60
|
+
main: 'rgba(33,37,41,.50)',
|
|
61
|
+
hover: 'rgba(33,37,41,.65)',
|
|
62
|
+
error: '#BF3227',
|
|
63
|
+
},
|
|
64
|
+
success: Object.assign(Object.assign({}, withMainShades('#1B8038', '#FFFFFF')), { text: '#35794A', fill: '#1B8038', fillText: '#FFFFFF', bg: '#E5F4E9' }),
|
|
65
|
+
warning: Object.assign(Object.assign({}, withMainShades('#C08A2C', '#212529')), { text: '#976100', fill: '#C08A2C', fillText: '#212529', bg: '#FBF0DC' }),
|
|
66
|
+
error: Object.assign(Object.assign({}, withMainShades('#BF3227', '#FFFFFF')), { text: '#BF3227', fill: '#BF3227', fillText: '#FFFFFF', bg: '#FBEAE8' }),
|
|
67
|
+
// The canonical sheet introduces no new info hue. These values reuse
|
|
68
|
+
// MUI's own info.dark/main family while adding the shared status shape.
|
|
69
|
+
info: Object.assign(Object.assign({}, withMainShades('#01579B', '#FFFFFF')), { text: '#01579B', fill: '#01579B', fillText: '#FFFFFF', bg: '#E5F6FD' }),
|
|
70
|
+
stale: {
|
|
71
|
+
text: '#5B6B7D',
|
|
72
|
+
fill: '#5B6B7D',
|
|
73
|
+
fillText: '#FFFFFF',
|
|
74
|
+
bg: '#EAEDF1',
|
|
75
|
+
},
|
|
76
|
+
dataSeries: {
|
|
77
|
+
categorical: SERIES_COLOURS,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
export const OVERLAY_SHADOW = '0 8px 24px rgba(20,26,31,.16), 0 2px 8px rgba(20,26,31,.08)';
|
|
81
|
+
export const MOTION = {
|
|
82
|
+
duration: {
|
|
83
|
+
fast: 120,
|
|
84
|
+
base: 180,
|
|
85
|
+
overlayEnter: 220,
|
|
86
|
+
overlayExit: 180,
|
|
87
|
+
chart: 300,
|
|
88
|
+
},
|
|
89
|
+
easing: {
|
|
90
|
+
enter: 'cubic-bezier(0.0, 0, 0.2, 1)',
|
|
91
|
+
exit: 'cubic-bezier(0.4, 0, 1, 1)',
|
|
92
|
+
state: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
93
|
+
},
|
|
94
|
+
reduced: '0.01ms',
|
|
95
|
+
};
|
|
96
|
+
const coarseHitArea = {
|
|
97
|
+
'@media (any-pointer: coarse)': {
|
|
98
|
+
minHeight: 44,
|
|
99
|
+
minWidth: 44,
|
|
100
|
+
},
|
|
101
|
+
};
|
|
102
|
+
const autofill = {
|
|
103
|
+
'&:-webkit-autofill': {
|
|
104
|
+
WebkitBoxShadow: '0 0 0 100px #FFFFFF inset',
|
|
105
|
+
WebkitTextFillColor: '#212529',
|
|
106
|
+
caretColor: '#212529',
|
|
107
|
+
},
|
|
108
|
+
'&:autofill': {
|
|
109
|
+
boxShadow: '0 0 0 100px #FFFFFF inset',
|
|
110
|
+
WebkitTextFillColor: '#212529',
|
|
111
|
+
caretColor: '#212529',
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
export const BASELINE_STATIC = {
|
|
115
|
+
typography: {
|
|
116
|
+
fontFamily: "'DM Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
|
|
117
|
+
h1: typeVariant('32px', 600, 1.2),
|
|
118
|
+
h2: typeVariant('28px', 600, 1.22),
|
|
119
|
+
h3: typeVariant('24px', 600, 1.25),
|
|
120
|
+
h4: typeVariant('20px', 600, 1.3),
|
|
121
|
+
h5: typeVariant('18px', 600, 1.3),
|
|
122
|
+
h6: typeVariant('16px', 600, 1.35),
|
|
123
|
+
subtitle1: typeVariant('15px', 600, 1.4),
|
|
124
|
+
subtitle2: typeVariant('13px', 500, 1.4),
|
|
125
|
+
body1: typeVariant('14px', 400, 1.55),
|
|
126
|
+
body2: typeVariant('13px', 400, 1.55),
|
|
127
|
+
button: typeVariant('14px', 500, 1.4, { textTransform: 'none' }),
|
|
128
|
+
caption: typeVariant('12px', 400, 1.4),
|
|
129
|
+
overline: typeVariant('11px', 600, 1.4, {
|
|
130
|
+
textTransform: 'uppercase',
|
|
131
|
+
letterSpacing: '0.4px',
|
|
132
|
+
}),
|
|
133
|
+
},
|
|
134
|
+
shape: {
|
|
135
|
+
borderRadius: 3,
|
|
136
|
+
},
|
|
137
|
+
radius: {
|
|
138
|
+
control: 3,
|
|
139
|
+
card: 8,
|
|
140
|
+
},
|
|
141
|
+
spacing: 8,
|
|
142
|
+
breakpoints: {
|
|
143
|
+
values: { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 },
|
|
144
|
+
},
|
|
145
|
+
density: {
|
|
146
|
+
controlHeight: 40,
|
|
147
|
+
touchTarget: 44,
|
|
148
|
+
tableCellPadding: '10px 16px',
|
|
149
|
+
chip: { height: 32, radius: 16, fontSize: 13 },
|
|
150
|
+
containerGutter: { default: 24, wide: 32 },
|
|
151
|
+
},
|
|
152
|
+
fontLoading: {
|
|
153
|
+
family: 'DM Sans',
|
|
154
|
+
weights: [400, 500, 600],
|
|
155
|
+
source: '@fontsource/dm-sans',
|
|
156
|
+
},
|
|
157
|
+
shadow: {
|
|
158
|
+
rest: 'none',
|
|
159
|
+
overlay: OVERLAY_SHADOW,
|
|
160
|
+
},
|
|
161
|
+
motion: MOTION,
|
|
162
|
+
transitions: {
|
|
163
|
+
duration: {
|
|
164
|
+
fast: MOTION.duration.fast,
|
|
165
|
+
base: MOTION.duration.base,
|
|
166
|
+
overlayEnter: MOTION.duration.overlayEnter,
|
|
167
|
+
overlayExit: MOTION.duration.overlayExit,
|
|
168
|
+
chart: MOTION.duration.chart,
|
|
169
|
+
},
|
|
170
|
+
easing: {
|
|
171
|
+
enter: MOTION.easing.enter,
|
|
172
|
+
exit: MOTION.easing.exit,
|
|
173
|
+
state: MOTION.easing.state,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
// MUI's 25-entry shadows array is intentionally retained intact. Resting
|
|
177
|
+
// surfaces are made shadowless via the Paper/Card defaults below, while
|
|
178
|
+
// named transient surfaces receive shadow.overlay explicitly.
|
|
179
|
+
components: {
|
|
180
|
+
MuiCssBaseline: {
|
|
181
|
+
styleOverrides: {
|
|
182
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
183
|
+
'*, *::before, *::after': {
|
|
184
|
+
animationDuration: '0.01ms !important',
|
|
185
|
+
animationIterationCount: '1 !important',
|
|
186
|
+
transitionDuration: '0.01ms !important',
|
|
187
|
+
scrollBehavior: 'auto !important',
|
|
188
|
+
},
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
},
|
|
192
|
+
MuiButton: {
|
|
193
|
+
defaultProps: { disableElevation: true },
|
|
194
|
+
styleOverrides: {
|
|
195
|
+
root: Object.assign({ minHeight: 40, borderRadius: 3, textTransform: 'none', transitionDuration: '120ms' }, coarseHitArea),
|
|
196
|
+
contained: { boxShadow: 'none', '&:hover': { boxShadow: 'none' } },
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
MuiTableCell: {
|
|
200
|
+
styleOverrides: { root: { padding: '10px 16px' } },
|
|
201
|
+
},
|
|
202
|
+
MuiIconButton: {
|
|
203
|
+
styleOverrides: { root: Object.assign({ minHeight: 40, minWidth: 40 }, coarseHitArea) },
|
|
204
|
+
},
|
|
205
|
+
MuiDivider: {
|
|
206
|
+
styleOverrides: { root: { borderColor: 'rgba(33,37,41,.10)' } },
|
|
207
|
+
},
|
|
208
|
+
MuiSelect: {
|
|
209
|
+
defaultProps: { variant: 'outlined' },
|
|
210
|
+
styleOverrides: {
|
|
211
|
+
select: {
|
|
212
|
+
minHeight: 40,
|
|
213
|
+
boxSizing: 'border-box',
|
|
214
|
+
'@media (any-pointer: coarse)': { minHeight: 44 },
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
MuiCheckbox: {
|
|
219
|
+
styleOverrides: { root: Object.assign({ minHeight: 40, minWidth: 40 }, coarseHitArea) },
|
|
220
|
+
},
|
|
221
|
+
MuiFormControlLabel: {
|
|
222
|
+
styleOverrides: {
|
|
223
|
+
root: Object.assign({ minHeight: 40 }, coarseHitArea),
|
|
224
|
+
label: { fontSize: '14px', lineHeight: 1.55 },
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
MuiContainer: {
|
|
228
|
+
styleOverrides: {
|
|
229
|
+
root: {
|
|
230
|
+
paddingLeft: 24,
|
|
231
|
+
paddingRight: 24,
|
|
232
|
+
'@media (min-width:1200px)': { paddingLeft: 32, paddingRight: 32 },
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
MuiPaper: {
|
|
237
|
+
defaultProps: { elevation: 0, variant: 'outlined' },
|
|
238
|
+
styleOverrides: {
|
|
239
|
+
root: { borderRadius: 8, boxShadow: 'none' },
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
MuiCard: {
|
|
243
|
+
defaultProps: { elevation: 0, variant: 'outlined' },
|
|
244
|
+
styleOverrides: { root: { borderRadius: 8, boxShadow: 'none' } },
|
|
245
|
+
},
|
|
246
|
+
MuiChip: {
|
|
247
|
+
styleOverrides: {
|
|
248
|
+
root: {
|
|
249
|
+
height: 32,
|
|
250
|
+
borderRadius: 16,
|
|
251
|
+
fontSize: '13px',
|
|
252
|
+
position: 'relative',
|
|
253
|
+
'@media (any-pointer: coarse)': {
|
|
254
|
+
'&::after': {
|
|
255
|
+
content: '""',
|
|
256
|
+
position: 'absolute',
|
|
257
|
+
inset: '-6px',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
MuiTextField: {
|
|
264
|
+
defaultProps: { variant: 'outlined' },
|
|
265
|
+
styleOverrides: { root: Object.assign({ minHeight: 40 }, coarseHitArea) },
|
|
266
|
+
},
|
|
267
|
+
MuiOutlinedInput: {
|
|
268
|
+
styleOverrides: {
|
|
269
|
+
root: Object.assign({ minHeight: 40, borderRadius: 3 }, coarseHitArea),
|
|
270
|
+
input: autofill,
|
|
271
|
+
},
|
|
272
|
+
// `variants` is a sibling of `styleOverrides`, not nested inside it -
|
|
273
|
+
// MUI matches these by `props` and applies `style` independently of the
|
|
274
|
+
// styleOverrides merge, so this is the correct placement (a `variants`
|
|
275
|
+
// array embedded inside styleOverrides.root would just be inert CSS-in-JS).
|
|
276
|
+
variants: [
|
|
277
|
+
{ props: { multiline: false }, style: { height: 40 } },
|
|
278
|
+
],
|
|
279
|
+
},
|
|
280
|
+
MuiFilledInput: {
|
|
281
|
+
styleOverrides: { input: autofill },
|
|
282
|
+
},
|
|
283
|
+
MuiInput: {
|
|
284
|
+
styleOverrides: { input: autofill },
|
|
285
|
+
},
|
|
286
|
+
MuiDialog: {
|
|
287
|
+
defaultProps: { transitionDuration: { enter: 220, exit: 180 } },
|
|
288
|
+
styleOverrides: { paper: { borderRadius: 8, boxShadow: OVERLAY_SHADOW } },
|
|
289
|
+
},
|
|
290
|
+
MuiDrawer: {
|
|
291
|
+
defaultProps: { transitionDuration: { enter: 220, exit: 180 } },
|
|
292
|
+
styleOverrides: { paper: { boxShadow: OVERLAY_SHADOW } },
|
|
293
|
+
},
|
|
294
|
+
MuiMenu: {
|
|
295
|
+
defaultProps: { transitionDuration: { enter: 220, exit: 180 } },
|
|
296
|
+
styleOverrides: { paper: { boxShadow: OVERLAY_SHADOW } },
|
|
297
|
+
},
|
|
298
|
+
MuiTooltip: {
|
|
299
|
+
defaultProps: {
|
|
300
|
+
slotProps: { transition: { timeout: { enter: 220, exit: 180 } } },
|
|
301
|
+
},
|
|
302
|
+
styleOverrides: {
|
|
303
|
+
tooltip: { boxShadow: OVERLAY_SHADOW, transitionDuration: '220ms' },
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
export const BASELINE_INTENTIONAL_DEFAULT_EXEMPTIONS = [
|
|
309
|
+
{
|
|
310
|
+
surface: 'palette.background.paper',
|
|
311
|
+
reason: 'The canonical surface is deliberately MUI white.',
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
surface: 'spacing.unit',
|
|
315
|
+
reason: 'The canonical spacing rhythm deliberately retains MUI\'s 8px unit.',
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
surface: 'breakpoints.values',
|
|
319
|
+
reason: 'The canonical breakpoint values deliberately retain MUI\'s defaults.',
|
|
320
|
+
},
|
|
321
|
+
...['subtitle2', 'body1', 'body2', 'button', 'caption'].map((variant) => ({
|
|
322
|
+
surface: `typography.${variant}.fontWeight`,
|
|
323
|
+
reason: `The canonical ${variant} weight deliberately equals MUI's numeric default.`,
|
|
324
|
+
})),
|
|
325
|
+
];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@micha.bigler/ui-core-micha",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.30.0",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"module": "dist/index.js",
|
|
6
6
|
"repository": {
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"url": "https://github.com/bigler-webapps/ui-core-micha"
|
|
9
9
|
},
|
|
10
10
|
"private": false,
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@fontsource/dm-sans": "^5.2.8"
|
|
13
|
+
},
|
|
11
14
|
"peerDependencies": {
|
|
12
15
|
"@emotion/react": "^11.14.0",
|
|
13
16
|
"@emotion/styled": "^11.14.1",
|
|
@@ -7,11 +7,14 @@ import { useTheme } from '@mui/material/styles';
|
|
|
7
7
|
export function getNeutralChartPalette(theme) {
|
|
8
8
|
const palette = theme.palette;
|
|
9
9
|
const isDark = palette.mode === 'dark';
|
|
10
|
+
const dataSeries = palette.dataSeries?.categorical;
|
|
10
11
|
|
|
11
12
|
return {
|
|
12
|
-
categorical:
|
|
13
|
-
? [
|
|
14
|
-
:
|
|
13
|
+
categorical: Array.isArray(dataSeries) && dataSeries.length > 0
|
|
14
|
+
? [...dataSeries]
|
|
15
|
+
: isDark
|
|
16
|
+
? [palette.primary.light, palette.secondary.light, palette.info.light, palette.success.light, palette.warning.light]
|
|
17
|
+
: [palette.primary.main, palette.secondary.main, palette.info.main, palette.success.main, palette.warning.main],
|
|
15
18
|
sequential: isDark
|
|
16
19
|
? [palette.action.disabled, palette.text.secondary, palette.primary.light, palette.primary.main, palette.primary.dark]
|
|
17
20
|
: [palette.action.disabled, palette.divider, palette.text.secondary, palette.primary.light, palette.primary.main],
|
|
@@ -100,10 +100,10 @@ export const authTranslations = {
|
|
|
100
100
|
},
|
|
101
101
|
|
|
102
102
|
"Auth.INVITE_SENT": {
|
|
103
|
-
"de": "Die Einladung wurde
|
|
104
|
-
"fr": "L'invitation a été envoyée.",
|
|
105
|
-
"en": "The invitation has been sent.",
|
|
106
|
-
"sw": "Mwaliko umetumwa."
|
|
103
|
+
"de": "Die Einladung wurde an {{email}} gesendet.",
|
|
104
|
+
"fr": "L'invitation a été envoyée à {{email}}.",
|
|
105
|
+
"en": "The invitation has been sent to {{email}}.",
|
|
106
|
+
"sw": "Mwaliko umetumwa kwa {{email}}."
|
|
107
107
|
},
|
|
108
108
|
|
|
109
109
|
"Auth.RESET_REQUEST_ACCEPTED": {
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
// index.js (Entry Point deiner Library)
|
|
2
2
|
|
|
3
|
+
// --- 0. Theme ---
|
|
4
|
+
export {
|
|
5
|
+
createAppTheme,
|
|
6
|
+
assertThemeComplete,
|
|
7
|
+
calculateContrastRatio,
|
|
8
|
+
reportThemeAdoption,
|
|
9
|
+
THEME_COMPLETENESS_SURFACES,
|
|
10
|
+
} from './theme';
|
|
11
|
+
|
|
3
12
|
// --- 1. Auth Context (Essentiell für den Wrapper) ---
|
|
4
13
|
export { AuthContext, AuthProvider } from './auth/AuthContext';
|
|
5
14
|
export { UserMenu } from './auth/UserMenu';
|
package/src/pages/SignUpPage.jsx
CHANGED
|
@@ -123,14 +123,18 @@ export function SignUpPage() {
|
|
|
123
123
|
|
|
124
124
|
setSubmitting(true);
|
|
125
125
|
try {
|
|
126
|
-
await submitRegistrationRequest({
|
|
126
|
+
const res = await submitRegistrationRequest({
|
|
127
127
|
email,
|
|
128
128
|
mode,
|
|
129
129
|
accessCode,
|
|
130
130
|
registrationContextToken: mode === 'self_signup_qr' ? tokenFromUrl : null,
|
|
131
131
|
turnstileToken: turnstileRequired ? turnstileToken : undefined,
|
|
132
132
|
});
|
|
133
|
-
|
|
133
|
+
// The backend's own response code is already mode-agnostic and accurate
|
|
134
|
+
// (Auth.INVITE_SENT). Falling back to the access-code-specific "if valid,
|
|
135
|
+
// an invite was sent" wording here was wrong for open/email-domain/QR
|
|
136
|
+
// signup, which have no code whose validity needs hiding.
|
|
137
|
+
setSuccessKey(res?.code || 'Auth.INVITE_SENT');
|
|
134
138
|
} catch (err) {
|
|
135
139
|
setErrorKey(err.code || 'Auth.INVITE_FAILED');
|
|
136
140
|
if (turnstileRequired) {
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import '@fontsource/dm-sans/400.css';
|
|
2
|
+
import '@fontsource/dm-sans/500.css';
|
|
3
|
+
import '@fontsource/dm-sans/600.css';
|
|
4
|
+
|
|
5
|
+
import { createTheme, darken, getContrastRatio } from '@mui/material/styles';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
BASELINE_INTENTIONAL_DEFAULT_EXEMPTIONS,
|
|
9
|
+
BASELINE_PALETTE,
|
|
10
|
+
BASELINE_STATIC,
|
|
11
|
+
MOTION,
|
|
12
|
+
} from './tokens';
|
|
13
|
+
|
|
14
|
+
function findFunction(value, path) {
|
|
15
|
+
if (typeof value === 'function') return path;
|
|
16
|
+
if (!value || typeof value !== 'object') return null;
|
|
17
|
+
|
|
18
|
+
for (const [key, child] of Object.entries(value)) {
|
|
19
|
+
const found = findFunction(child, `${path}.${key}`);
|
|
20
|
+
if (found) return found;
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function validateStyleOverrides(components = {}) {
|
|
26
|
+
for (const [componentName, component] of Object.entries(components)) {
|
|
27
|
+
if (!component?.styleOverrides) continue;
|
|
28
|
+
const slot = findFunction(
|
|
29
|
+
component.styleOverrides,
|
|
30
|
+
`components.${componentName}.styleOverrides`,
|
|
31
|
+
);
|
|
32
|
+
if (slot) {
|
|
33
|
+
throw new TypeError(
|
|
34
|
+
`createAppTheme: ${slot} must be an object, not a function. ` +
|
|
35
|
+
'Use object styleOverrides and MUI variants for prop- or state-dependent styles.',
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function clearsContrast(colour, surfaces) {
|
|
42
|
+
return surfaces.every((surface) => getContrastRatio(colour, surface) >= 3);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Darkened until it clears 3:1 against BOTH '#FFFFFF' and background.default
|
|
46
|
+
// -- the exact two surfaces assertThemeComplete's contrastFindings checks --
|
|
47
|
+
// so deriving against background.paper alone (which need not be white for an
|
|
48
|
+
// app that overrides it) could pass here and still fail there.
|
|
49
|
+
function deriveFocusColour(primary, surfaces) {
|
|
50
|
+
let focus = primary;
|
|
51
|
+
for (let coefficient = 0.05; !clearsContrast(focus, surfaces); coefficient += 0.05) {
|
|
52
|
+
focus = darken(primary, Math.min(coefficient, 0.9));
|
|
53
|
+
if (coefficient >= 0.9) break;
|
|
54
|
+
}
|
|
55
|
+
return focus;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createPaletteAwareComponents(palette) {
|
|
59
|
+
const stateTransition = `border-color ${MOTION.duration.fast}ms ${MOTION.easing.state}, color ${MOTION.duration.fast}ms ${MOTION.easing.state}, background-color ${MOTION.duration.fast}ms ${MOTION.easing.state}`;
|
|
60
|
+
const autofill = {
|
|
61
|
+
'&:-webkit-autofill': {
|
|
62
|
+
WebkitBoxShadow: `0 0 0 100px ${palette.background.paper} inset`,
|
|
63
|
+
WebkitTextFillColor: palette.ink.primary,
|
|
64
|
+
caretColor: palette.ink.primary,
|
|
65
|
+
},
|
|
66
|
+
'&:autofill': {
|
|
67
|
+
boxShadow: `0 0 0 100px ${palette.background.paper} inset`,
|
|
68
|
+
WebkitTextFillColor: palette.ink.primary,
|
|
69
|
+
caretColor: palette.ink.primary,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const alertStyles = Object.fromEntries(
|
|
73
|
+
['success', 'warning', 'error', 'info'].map((status) => [
|
|
74
|
+
`standard${status[0].toUpperCase()}${status.slice(1)}`,
|
|
75
|
+
{
|
|
76
|
+
color: palette[status].text,
|
|
77
|
+
backgroundColor: palette[status].bg,
|
|
78
|
+
'& .MuiAlert-icon': { color: palette[status].text },
|
|
79
|
+
},
|
|
80
|
+
]),
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
MuiButton: {
|
|
85
|
+
styleOverrides: {
|
|
86
|
+
root: { transition: stateTransition },
|
|
87
|
+
outlined: {
|
|
88
|
+
borderColor: palette.controlBorder.main,
|
|
89
|
+
'&:hover': { borderColor: palette.controlBorder.hover },
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
MuiDivider: {
|
|
94
|
+
styleOverrides: { root: { borderColor: palette.divider } },
|
|
95
|
+
},
|
|
96
|
+
MuiCheckbox: {
|
|
97
|
+
styleOverrides: {
|
|
98
|
+
root: {
|
|
99
|
+
color: palette.controlBorder.main,
|
|
100
|
+
transition: stateTransition,
|
|
101
|
+
'&:hover': { color: palette.controlBorder.hover },
|
|
102
|
+
'&.Mui-checked': { color: palette.primary.main },
|
|
103
|
+
'&.MuiCheckbox-indeterminate': { color: palette.primary.main },
|
|
104
|
+
'&.Mui-error': { color: palette.controlBorder.error },
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
MuiPaper: {
|
|
109
|
+
styleOverrides: { root: { borderColor: palette.divider } },
|
|
110
|
+
},
|
|
111
|
+
MuiCard: {
|
|
112
|
+
styleOverrides: { root: { borderColor: palette.divider } },
|
|
113
|
+
},
|
|
114
|
+
MuiChip: {
|
|
115
|
+
styleOverrides: {
|
|
116
|
+
outlined: { borderColor: palette.controlBorder.main },
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
MuiOutlinedInput: {
|
|
120
|
+
styleOverrides: {
|
|
121
|
+
root: {
|
|
122
|
+
transition: stateTransition,
|
|
123
|
+
'& .MuiOutlinedInput-notchedOutline': {
|
|
124
|
+
borderColor: palette.controlBorder.main,
|
|
125
|
+
},
|
|
126
|
+
'&:hover .MuiOutlinedInput-notchedOutline': {
|
|
127
|
+
borderColor: palette.controlBorder.hover,
|
|
128
|
+
},
|
|
129
|
+
'&.Mui-focused .MuiOutlinedInput-notchedOutline': {
|
|
130
|
+
borderColor: palette.controlBorder.focus,
|
|
131
|
+
borderWidth: 2,
|
|
132
|
+
},
|
|
133
|
+
'&.Mui-error .MuiOutlinedInput-notchedOutline': {
|
|
134
|
+
borderColor: palette.controlBorder.error,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
input: autofill,
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
MuiFilledInput: {
|
|
141
|
+
styleOverrides: { input: autofill },
|
|
142
|
+
},
|
|
143
|
+
MuiInput: {
|
|
144
|
+
styleOverrides: { input: autofill },
|
|
145
|
+
},
|
|
146
|
+
MuiAlert: {
|
|
147
|
+
styleOverrides: alertStyles,
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Builds the shared application theme while leaving app identity in primary
|
|
154
|
+
* and fontFamily. Palette resolution happens before palette-aware component
|
|
155
|
+
* objects are created, so runtime app/site overrides cannot leave stale values.
|
|
156
|
+
*/
|
|
157
|
+
export function createAppTheme(appConfig = {}) {
|
|
158
|
+
if (!appConfig?.palette?.primary) {
|
|
159
|
+
throw new TypeError('createAppTheme: appConfig.palette.primary is required.');
|
|
160
|
+
}
|
|
161
|
+
validateStyleOverrides(appConfig.components);
|
|
162
|
+
|
|
163
|
+
const paletteTheme = createTheme(
|
|
164
|
+
{ palette: appConfig.palette },
|
|
165
|
+
{ palette: BASELINE_PALETTE },
|
|
166
|
+
{ palette: appConfig.palette },
|
|
167
|
+
);
|
|
168
|
+
const finalPalette = paletteTheme.palette;
|
|
169
|
+
const computedPalette = {
|
|
170
|
+
controlBorder: {
|
|
171
|
+
...finalPalette.controlBorder,
|
|
172
|
+
focus: deriveFocusColour(finalPalette.primary.main, [
|
|
173
|
+
'#FFFFFF',
|
|
174
|
+
finalPalette.background.default,
|
|
175
|
+
]),
|
|
176
|
+
},
|
|
177
|
+
};
|
|
178
|
+
const resolvedPalette = { ...finalPalette, ...computedPalette };
|
|
179
|
+
const fontFamily = appConfig.typography?.fontFamily || BASELINE_STATIC.typography.fontFamily;
|
|
180
|
+
|
|
181
|
+
return createTheme(
|
|
182
|
+
{
|
|
183
|
+
...BASELINE_STATIC,
|
|
184
|
+
typography: {
|
|
185
|
+
...BASELINE_STATIC.typography,
|
|
186
|
+
fontFamily,
|
|
187
|
+
},
|
|
188
|
+
fontLoading: {
|
|
189
|
+
...BASELINE_STATIC.fontLoading,
|
|
190
|
+
weights: [...BASELINE_STATIC.fontLoading.weights],
|
|
191
|
+
},
|
|
192
|
+
palette: resolvedPalette,
|
|
193
|
+
themeCompleteness: {
|
|
194
|
+
baseline: true,
|
|
195
|
+
exemptions: BASELINE_INTENTIONAL_DEFAULT_EXEMPTIONS.map((exemption) => ({
|
|
196
|
+
...exemption,
|
|
197
|
+
})),
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
components: createPaletteAwareComponents(resolvedPalette),
|
|
202
|
+
},
|
|
203
|
+
appConfig,
|
|
204
|
+
{
|
|
205
|
+
palette: {
|
|
206
|
+
dataSeries: {
|
|
207
|
+
categorical: [...resolvedPalette.dataSeries.categorical],
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
);
|
|
212
|
+
}
|