@12-apps/ui 4.0.1 → 4.0.2
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/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import Error from '@mui/icons-material/Error';
|
|
|
2
2
|
import Info from '@mui/icons-material/Info';
|
|
3
3
|
import CheckCircle from '@mui/icons-material/CheckCircle';
|
|
4
4
|
import Warning from '@mui/icons-material/Warning';
|
|
5
|
-
import { alpha, keyframes } from '@mui/material';
|
|
5
|
+
import { alpha, darken, keyframes, lighten } from '@mui/material';
|
|
6
6
|
import type { CSSObject, Theme } from '@mui/material/styles';
|
|
7
7
|
import React from 'react';
|
|
8
8
|
|
|
@@ -117,58 +117,165 @@ export const getVariantIcon = (variant: string) => {
|
|
|
117
117
|
// which is narrower than MUI's PaletteColor.
|
|
118
118
|
export type AlertPalette = { main: string; light?: string; dark?: string };
|
|
119
119
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
/** The four semantic variants, which differ only in which palette they read. */
|
|
121
|
+
const SEMANTIC_VARIANTS = new Set(['info', 'success', 'warning', 'danger']);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* The room the message is given — padding, the stack's rhythm, and where the
|
|
125
|
+
* icon and the close button sit relative to it.
|
|
126
|
+
*
|
|
127
|
+
* Extracted for the same reason {@link alertVariantStyles} and
|
|
128
|
+
* {@link alertEmphasisStyles} were: `styled()`'s callback is one function, and
|
|
129
|
+
* every block written inline is counted against its length by the complexity
|
|
130
|
+
* gate. Keeping the layout here means the callback assembles four named pieces
|
|
131
|
+
* instead of carrying all of them.
|
|
132
|
+
*/
|
|
133
|
+
export const alertLayoutStyles = (
|
|
124
134
|
theme: Theme,
|
|
125
|
-
customVariant: string | undefined,
|
|
126
135
|
colorPalette: AlertPalette,
|
|
136
|
+
animate: boolean | undefined,
|
|
127
137
|
): CSSObject => ({
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
138
|
+
// ROOM TO READ.
|
|
139
|
+
//
|
|
140
|
+
// MUI's `6px 16px` is sized for one line of text and nothing else. This Alert
|
|
141
|
+
// routinely carries three things stacked — a sentence, a smaller explanation,
|
|
142
|
+
// then a control — and at 6px they touched top and bottom and ran together
|
|
143
|
+
// vertically, so the whole card read as one dense block instead of as a
|
|
144
|
+
// message with parts. The generosity is what makes the structure legible; it
|
|
145
|
+
// is not decoration.
|
|
146
|
+
//
|
|
147
|
+
// Padding stays overridable by an `sx` (a full-bleed strip legitimately wants
|
|
148
|
+
// it tight), because this is a default rather than a rule.
|
|
149
|
+
padding: theme.spacing(1.75, 2),
|
|
136
150
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
151
|
+
'.MuiAlert-message': {
|
|
152
|
+
display: 'flex',
|
|
153
|
+
flexDirection: 'column',
|
|
154
|
+
// 0.5 put a title, its explanation and a button 4px apart, which reads as a
|
|
155
|
+
// spacing bug rather than as a group. 1 separates the lines; the extra
|
|
156
|
+
// margin below goes to whatever CONTROL sits at the end, because the gap
|
|
157
|
+
// between prose and a thing you press has to be bigger than the gap between
|
|
158
|
+
// two lines of prose or the button looks like part of the text.
|
|
159
|
+
gap: theme.spacing(1),
|
|
160
|
+
fontSize: '0.95rem',
|
|
161
|
+
lineHeight: 1.5,
|
|
162
|
+
// No padding of its own — the root's is now doing that job, and MUI's
|
|
163
|
+
// default `8px 0` on top of it would double the vertical space.
|
|
164
|
+
padding: 0,
|
|
165
|
+
// A CONTROL ON A TINTED PANEL NEEDS AN EDGE.
|
|
166
|
+
//
|
|
167
|
+
// `ghost` and `text` paint no background and no border — bare labels, which
|
|
168
|
+
// works on white where the surrounding page is obviously not clickable.
|
|
169
|
+
// Inside a coloured Alert it stops working: the label is one more coloured
|
|
170
|
+
// phrase among several, and a "Cancelar" beside two lines of prose reads as
|
|
171
|
+
// part of the prose.
|
|
172
|
+
//
|
|
173
|
+
// Only the BORDER is set, never the background or the colour, and that is
|
|
174
|
+
// what makes it safe to apply to every button rather than just the bare
|
|
175
|
+
// ones: a `solid` keeps its fill and its light label and gains a hairline
|
|
176
|
+
// in the same hue, while a `ghost` gains the whole affordance.
|
|
177
|
+
'.MuiButton-root': {
|
|
178
|
+
marginTop: theme.spacing(0.5),
|
|
179
|
+
border: `1px solid ${alpha(colorPalette.main, 0.45)}` },
|
|
143
180
|
},
|
|
144
|
-
}),
|
|
145
181
|
|
|
146
|
-
...(customVariant === 'warning' && {
|
|
147
|
-
backgroundColor: alpha(colorPalette.main, 0.1),
|
|
148
|
-
color: colorPalette.main,
|
|
149
|
-
border: `1px solid ${alpha(colorPalette.main, 0.2)}`,
|
|
150
182
|
'.MuiAlert-icon': {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
183
|
+
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
184
|
+
alignItems: 'center',
|
|
185
|
+
// Clear of the words rather than nearly touching them, and aligned to the
|
|
186
|
+
// FIRST line instead of centred on the whole block — an icon floating
|
|
187
|
+
// halfway down a three-line message points at nothing.
|
|
188
|
+
alignSelf: 'flex-start',
|
|
189
|
+
marginRight: theme.spacing(1.75),
|
|
190
|
+
paddingTop: theme.spacing(0.25),
|
|
191
|
+
animation: animate ? `${iconRotate} 0.6s ease-out` : 'none' },
|
|
154
192
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
193
|
+
// The close button, kept off the text it sits beside.
|
|
194
|
+
'.MuiAlert-action': {
|
|
195
|
+
alignItems: 'flex-start',
|
|
196
|
+
paddingLeft: theme.spacing(2) },
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* A semantic Alert's surface and ink.
|
|
201
|
+
*
|
|
202
|
+
* ## Why this is not `alpha(main, 0.1)`
|
|
203
|
+
*
|
|
204
|
+
* It used to be, four times over, with `color: main` for the text. Both halves
|
|
205
|
+
* were wrong, and independently so.
|
|
206
|
+
*
|
|
207
|
+
* The fill was TRANSLUCENT. A 10% wash only reads as a banner when something
|
|
208
|
+
* opaque is behind it, so the component silently depended on where a consumer
|
|
209
|
+
* put it. Placed over content — a floating notice, an invite anchored above a
|
|
210
|
+
* catalogue — the page came straight through and the sentence interleaved with
|
|
211
|
+
* whatever it was over. That is not a faint banner; it is an unreadable one,
|
|
212
|
+
* and nothing in the component said so.
|
|
213
|
+
*
|
|
214
|
+
* The ink was the SEVERITY HUE at body size. `#0288d1`, this library's info
|
|
215
|
+
* blue, is 3.03:1 on white — under the 4.5:1 WCAG AA floor before any wash is
|
|
216
|
+
* involved. So the text failed on a plain white page too, where the fill was
|
|
217
|
+
* behaving exactly as intended.
|
|
218
|
+
*
|
|
219
|
+
* ## Why `lighten`/`darken` rather than a hand-picked pair
|
|
220
|
+
*
|
|
221
|
+
* These are the same transforms MUI's own `standard` Alert uses, at the same
|
|
222
|
+
* ratios — the recipe this component overrode. Reusing it means an OPAQUE tint
|
|
223
|
+
* with a dark ink of the same hue, contrast that holds on any surface because
|
|
224
|
+
* it no longer depends on one, and a pairing already proven across MUI's
|
|
225
|
+
* palette rather than eyeballed here. Both modes are handled: on a dark theme
|
|
226
|
+
* the tint darkens and the ink lightens, which an alpha wash could never do
|
|
227
|
+
* because it carries the seed's own lightness through unchanged.
|
|
228
|
+
*
|
|
229
|
+
* The border keeps an alpha — it is a hairline over the tint it belongs to, so
|
|
230
|
+
* translucency there costs nothing and lets the edge follow the fill.
|
|
231
|
+
*/
|
|
232
|
+
const semanticSurface = (theme: Theme, colorPalette: AlertPalette): CSSObject => {
|
|
233
|
+
const dark = theme.palette.mode === 'dark';
|
|
234
|
+
return {
|
|
235
|
+
backgroundColor: dark ? darken(colorPalette.main, 0.8) : lighten(colorPalette.main, 0.9),
|
|
236
|
+
color: dark ? lighten(colorPalette.main, 0.6) : darken(colorPalette.main, 0.6),
|
|
237
|
+
border: `1px solid ${alpha(colorPalette.main, 0.35)}`,
|
|
238
|
+
'.MuiAlert-icon': {
|
|
239
|
+
color: colorPalette.main,
|
|
240
|
+
},
|
|
241
|
+
};
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
// The six visual variants. Each block is spread in only when it matches, which
|
|
245
|
+
// is how the original inline version read; keeping that shape here means the
|
|
246
|
+
// styled() callback carries one branch instead of six. The four semantics were
|
|
247
|
+
// four IDENTICAL copies of the same block — they now share one, so the next
|
|
248
|
+
// change to a banner's surface cannot land on three of them.
|
|
249
|
+
export const alertVariantStyles = (
|
|
250
|
+
theme: Theme,
|
|
251
|
+
customVariant: string | undefined,
|
|
252
|
+
colorPalette: AlertPalette,
|
|
253
|
+
): CSSObject => ({
|
|
254
|
+
...(customVariant !== undefined &&
|
|
255
|
+
SEMANTIC_VARIANTS.has(customVariant) &&
|
|
256
|
+
semanticSurface(theme, colorPalette)),
|
|
163
257
|
|
|
164
258
|
...(customVariant === 'glass' && {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
259
|
+
// 85%, not 10%. This is the variant that is MEANT to be see-through, and it
|
|
260
|
+
// was the same mistake in a costume: at 0.1 the blur had almost nothing to
|
|
261
|
+
// sit on, so `glass` was not frosted, it was a window. A frosted pane has to
|
|
262
|
+
// be mostly pane — the backdrop should be legible AS texture behind the
|
|
263
|
+
// text, never as competition with it.
|
|
264
|
+
backgroundColor: alpha(theme.palette.background.paper, 0.85),
|
|
265
|
+
backdropFilter: 'blur(20px) saturate(180%)',
|
|
266
|
+
WebkitBackdropFilter: 'blur(20px) saturate(180%)',
|
|
267
|
+
border: `1px solid ${alpha(theme.palette.divider, 0.4)}`,
|
|
168
268
|
color: theme.palette.text.primary,
|
|
169
269
|
'.MuiAlert-icon': {
|
|
170
270
|
color: theme.palette.primary.main,
|
|
171
271
|
},
|
|
272
|
+
// Safari before 18 and Firefox with the filter disabled paint the declared
|
|
273
|
+
// alpha with nothing blurred behind it — the see-through banner this is
|
|
274
|
+
// fixing. There is no degraded frost to fall back to, so it falls back to an
|
|
275
|
+
// opaque pane: less pretty, still readable.
|
|
276
|
+
'@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px)))': {
|
|
277
|
+
backgroundColor: theme.palette.background.paper,
|
|
278
|
+
},
|
|
172
279
|
}),
|
|
173
280
|
|
|
174
281
|
...(customVariant === 'gradient' && {
|
|
@@ -11,11 +11,11 @@ import React from 'react';
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
alertEmphasisStyles,
|
|
14
|
+
alertLayoutStyles,
|
|
14
15
|
alertVariantStyles,
|
|
15
16
|
fadeInScale,
|
|
16
17
|
getColorFromTheme,
|
|
17
|
-
getVariantIcon
|
|
18
|
-
iconRotate } from './Alert.styles';
|
|
18
|
+
getVariantIcon } from './Alert.styles';
|
|
19
19
|
import type { AlertColor } from '@mui/material';
|
|
20
20
|
import type { AlertProps } from './Alert.types';
|
|
21
21
|
|
|
@@ -39,18 +39,7 @@ const StyledAlert = styled(MuiAlert, {
|
|
|
39
39
|
animation: animate ? `${fadeInScale} 0.3s ease-out` : 'none',
|
|
40
40
|
willChange: 'transform, opacity',
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
'.MuiAlert-message': {
|
|
44
|
-
display: 'flex',
|
|
45
|
-
flexDirection: 'column',
|
|
46
|
-
gap: theme.spacing(0.5),
|
|
47
|
-
fontSize: '0.95rem',
|
|
48
|
-
lineHeight: 1.5 },
|
|
49
|
-
|
|
50
|
-
'.MuiAlert-icon': {
|
|
51
|
-
transition: 'transform 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
|
|
52
|
-
alignItems: 'center',
|
|
53
|
-
animation: animate ? `${iconRotate} 0.6s ease-out` : 'none' },
|
|
42
|
+
...alertLayoutStyles(theme, colorPalette, animate),
|
|
54
43
|
|
|
55
44
|
// Hover effects
|
|
56
45
|
'&:hover': {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { alpha, keyframes } from '@mui/material';
|
|
1
|
+
import { alpha, darken, keyframes, lighten } from '@mui/material';
|
|
2
2
|
import type { CSSObject, Theme } from '@mui/material';
|
|
3
3
|
|
|
4
4
|
// Define pulse animation globally
|
|
@@ -146,6 +146,25 @@ const gradientFor = (theme: Theme, color: string, palette: ColorPalette): string
|
|
|
146
146
|
return `linear-gradient(135deg, ${from} 0%, ${to} 100%)`;
|
|
147
147
|
};
|
|
148
148
|
|
|
149
|
+
/**
|
|
150
|
+
* The label colour for the two variants that paint no background of their own.
|
|
151
|
+
*
|
|
152
|
+
* `ghost` and `text` used `palette.main` directly, which is the same mistake
|
|
153
|
+
* the Alert made with its body text: a palette's `main` is chosen to sit UNDER
|
|
154
|
+
* white — it is the fill of a solid button — and it is not chosen to be read AS
|
|
155
|
+
* text. `#6366F1`, the default primary, is 3.6:1 on white and worse on any
|
|
156
|
+
* tinted surface, so a "Cancelar" or an "Agora não" placed on a coloured panel
|
|
157
|
+
* turned into a smudge. That is precisely where these two variants belong: they
|
|
158
|
+
* exist to be the quiet option NEXT to a solid one, so they are the buttons
|
|
159
|
+
* most likely to be sitting on something other than plain paper.
|
|
160
|
+
*
|
|
161
|
+
* One step darker (lighter, on a dark theme) keeps the hue the caller asked for
|
|
162
|
+
* and buys the contrast back. `main` still drives the hover wash and every
|
|
163
|
+
* bordered variant, so nothing that was already legible moves.
|
|
164
|
+
*/
|
|
165
|
+
const quietInk = (theme: Theme, palette: ColorPalette): string =>
|
|
166
|
+
theme.palette.mode === 'dark' ? lighten(palette.main, 0.35) : darken(palette.main, 0.3);
|
|
167
|
+
|
|
149
168
|
const VARIANT_STYLES: Record<
|
|
150
169
|
string,
|
|
151
170
|
(theme: Theme, palette: ColorPalette, color: string) => CSSObject
|
|
@@ -168,23 +187,23 @@ const VARIANT_STYLES: Record<
|
|
|
168
187
|
borderColor: palette.dark,
|
|
169
188
|
},
|
|
170
189
|
}),
|
|
171
|
-
ghost: (
|
|
190
|
+
ghost: (theme, palette) => ({
|
|
172
191
|
backgroundColor: 'transparent',
|
|
173
|
-
color: palette
|
|
192
|
+
color: quietInk(theme, palette),
|
|
174
193
|
'&:hover': {
|
|
175
194
|
backgroundColor: alpha(palette.main, 0.1),
|
|
176
195
|
},
|
|
177
196
|
}),
|
|
178
|
-
text: (
|
|
197
|
+
text: (theme, palette) => ({
|
|
179
198
|
backgroundColor: 'transparent',
|
|
180
|
-
color: palette
|
|
199
|
+
color: quietInk(theme, palette),
|
|
181
200
|
border: `none`,
|
|
182
201
|
'&:hover': {
|
|
183
202
|
backgroundColor: alpha(palette.main, 0.1),
|
|
184
203
|
},
|
|
185
204
|
'&.active': {
|
|
186
205
|
backgroundColor: alpha(palette.main, 0.1),
|
|
187
|
-
color: palette
|
|
206
|
+
color: quietInk(theme, palette),
|
|
188
207
|
},
|
|
189
208
|
}),
|
|
190
209
|
glass: (theme, palette) => ({
|