@12-apps/ui 6.24.2 → 6.24.4
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/NATIVE.md +1 -1
- package/dist/_internal/components/data-display/Alert/Alert.js +5 -3
- package/dist/_internal/components/data-display/Alert/Alert.metrics.js +1 -1
- package/dist/_internal/components/data-display/Alert/Alert.styles.js +2 -2
- package/dist/_internal/components/data-display/Banner/Banner.js +7 -2
- package/dist/_internal/components/data-display/Banner/Banner.styles.js +10 -1
- package/dist/_internal/components/data-display/CardKit/card-actions-context.js +4 -4
- package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
- package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +5 -5
- package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +4 -4
- package/dist/_internal/components/data-display/index.js +5 -5
- package/dist/_internal/components/index.js +6 -6
- package/dist/_internal/components/navigation/AppHeader/AppHeader.identity.js +1 -1
- package/dist/_internal/components/navigation/index.js +1 -1
- package/dist/{chunk-7L6LAVJ7.js → chunk-4IVDVZZ2.js} +25 -11
- package/dist/chunk-4IVDVZZ2.js.map +1 -0
- package/dist/{chunk-U2YJCNRU.js → chunk-4T7TXRGM.js} +2 -2
- package/dist/{chunk-E27Y7FF2.js → chunk-57Z2ND3N.js} +9 -10
- package/dist/{chunk-E27Y7FF2.js.map → chunk-57Z2ND3N.js.map} +1 -1
- package/dist/{chunk-DI7HGYPX.js → chunk-JPV45AD6.js} +2 -2
- package/dist/{chunk-PRUSQQDT.js → chunk-KM5O4ICK.js} +2 -2
- package/dist/{chunk-2YEZONLX.js → chunk-M4LFDGI6.js} +30 -5
- package/dist/chunk-M4LFDGI6.js.map +1 -0
- package/dist/{chunk-O3336DJ6.js → chunk-SYJMI2NP.js} +2 -2
- package/dist/{chunk-W2YNUUVJ.js → chunk-TU6RNZUQ.js} +117 -78
- package/dist/chunk-TU6RNZUQ.js.map +1 -0
- package/dist/{chunk-B2Y3BABA.js → chunk-TXKZEKJN.js} +6 -9
- package/dist/chunk-TXKZEKJN.js.map +1 -0
- package/dist/{chunk-QFVFHIHE.js → chunk-WNMYWQ7D.js} +98 -30
- package/dist/chunk-WNMYWQ7D.js.map +1 -0
- package/dist/data-display/Alert.js +3 -3
- package/dist/data-display/Banner.js +7 -2
- package/dist/data-display/CardKit.js +5 -5
- package/dist/data-display/DataViews.js +5 -5
- package/dist/native/data-display/Alert.js +2 -5
- package/dist/native/data-display/Alert.js.map +1 -1
- package/dist/navigation/AppHeader.js +1 -1
- package/dist/types/components/data-display/Alert/Alert.d.ts +14 -0
- package/dist/types/components/data-display/Alert/Alert.d.ts.map +1 -1
- package/dist/types/components/data-display/Alert/Alert.metrics.d.ts +66 -23
- package/dist/types/components/data-display/Alert/Alert.metrics.d.ts.map +1 -1
- package/dist/types/components/data-display/Alert/Alert.styles.d.ts.map +1 -1
- package/dist/types/components/data-display/Banner/Banner.d.ts.map +1 -1
- package/dist/types/components/data-display/Banner/Banner.styles.d.ts +3 -0
- package/dist/types/components/data-display/Banner/Banner.styles.d.ts.map +1 -1
- package/dist/types/components/navigation/AppHeader/AppHeader.identity.d.ts +12 -5
- package/dist/types/components/navigation/AppHeader/AppHeader.identity.d.ts.map +1 -1
- package/dist/types/components/navigation/AppHeader/AppHeader.types.d.ts +12 -4
- package/dist/types/components/navigation/AppHeader/AppHeader.types.d.ts.map +1 -1
- package/dist/types-native/components/data-display/Alert/Alert.metrics.d.ts +66 -23
- package/dist/types-native/components/data-display/Alert/Alert.metrics.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/data-display/Alert/Alert.metrics.ts +66 -9
- package/src/components/data-display/Alert/Alert.styles.tsx +4 -5
- package/src/components/data-display/Alert/Alert.tsx +139 -21
- package/src/components/data-display/Alert/NATIVE-NOTES.md +1 -1
- package/src/components/data-display/Banner/Banner.styles.ts +40 -2
- package/src/components/data-display/Banner/Banner.tsx +22 -9
- package/src/components/navigation/AppHeader/AppHeader.identity.tsx +159 -86
- package/src/components/navigation/AppHeader/AppHeader.md +12 -5
- package/src/components/navigation/AppHeader/AppHeader.types.ts +12 -4
- package/dist/chunk-2YEZONLX.js.map +0 -1
- package/dist/chunk-7L6LAVJ7.js.map +0 -1
- package/dist/chunk-B2Y3BABA.js.map +0 -1
- package/dist/chunk-QFVFHIHE.js.map +0 -1
- package/dist/chunk-W2YNUUVJ.js.map +0 -1
- /package/dist/{chunk-U2YJCNRU.js.map → chunk-4T7TXRGM.js.map} +0 -0
- /package/dist/{chunk-DI7HGYPX.js.map → chunk-JPV45AD6.js.map} +0 -0
- /package/dist/{chunk-PRUSQQDT.js.map → chunk-KM5O4ICK.js.map} +0 -0
- /package/dist/{chunk-O3336DJ6.js.map → chunk-SYJMI2NP.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppHeader.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/navigation/AppHeader/AppHeader.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wBAAwB;IACxB,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,yCAAyC;IACzC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED
|
|
1
|
+
{"version":3,"file":"AppHeader.types.d.ts","sourceRoot":"","sources":["../../../../../src/components/navigation/AppHeader/AppHeader.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;;;;;GAMG;AACH,MAAM,MAAM,iBAAiB,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAC;AAE9D,8DAA8D;AAC9D,MAAM,MAAM,4BAA4B,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAEvE;;;;;;;;GAQG;AACH,MAAM,WAAW,mBAAmB;IAClC,8EAA8E;IAC9E,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iEAAiE;IACjE,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,oBAAoB;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,wBAAwB;IACxB,KAAK,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAoB;IACnC,yCAAyC;IACzC,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,sBACf,SAAQ,IAAI,CAAC,mBAAmB,EAAE,SAAS,GAAG,WAAW,CAAC;IAC1D,sEAAsE;IACtE,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,uEAAuE;IACvE,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,yEAAyE;IACzE,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB,gFAAgF;IAChF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,oBAAoB;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wDAAwD;IACxD,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;;OAIG;IACH,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,gFAAgF;IAChF,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,wBAAwB;IACxB,QAAQ,CAAC,EAAE,iBAAiB,CAAC;IAC7B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC3B,4CAA4C;IAC5C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,6EAA6E;IAC7E,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,iDAAiD;AACjD,MAAM,WAAW,kBAAkB;IACjC,2CAA2C;IAC3C,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,CAAC;IACjB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,UAAU,CAAC;CACnB;AAED,4DAA4D;AAC5D,MAAM,WAAW,sBAAsB;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,yBAAyB;IACzB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,wDAAwD;IACxD,IAAI,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC5B,gEAAgE;IAChE,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,6DAA6D;IAC7D,MAAM,CAAC,EAAE,sBAAsB,CAAC;IAChC,sBAAsB;IACtB,YAAY,CAAC,EAAE,4BAA4B,CAAC;IAC5C,oEAAoE;IACpE,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
|
|
@@ -73,8 +73,6 @@ export declare const CLOSE_BUTTON: {
|
|
|
73
73
|
readonly padding: 5;
|
|
74
74
|
readonly iconSize: 18;
|
|
75
75
|
readonly opacity: 0.7;
|
|
76
|
-
readonly hoverRotateDeg: 90;
|
|
77
|
-
readonly hoverScale: 1.1;
|
|
78
76
|
readonly washAlpha: 0.1;
|
|
79
77
|
};
|
|
80
78
|
/**
|
|
@@ -97,16 +95,22 @@ export declare const GLASS: {
|
|
|
97
95
|
readonly blur: 20;
|
|
98
96
|
readonly saturatePercent: 180;
|
|
99
97
|
};
|
|
100
|
-
/**
|
|
98
|
+
/**
|
|
99
|
+
* `gradient`: light→dark at 0.9 along 135°, a 0.2 white shimmer sweeping 1000px every 3s.
|
|
100
|
+
*
|
|
101
|
+
* It carries no hover of its own, and that is load-bearing rather than an
|
|
102
|
+
* omission. `alertVariantStyles` is spread AFTER the root's `'&:hover'` in the
|
|
103
|
+
* same object literal, so a second `'&:hover'` key does not merge with it — it
|
|
104
|
+
* REPLACES it. While this variant had one, `gradient` was the only alert that
|
|
105
|
+
* brightened on hover while every other variant darkened, and the only one to
|
|
106
|
+
* lose the shimmer reveal the root's hover performs.
|
|
107
|
+
*/
|
|
101
108
|
export declare const GRADIENT: {
|
|
102
109
|
readonly angleDeg: 135;
|
|
103
110
|
readonly stopAlpha: 0.9;
|
|
104
111
|
readonly shimmerAlpha: 0.2;
|
|
105
112
|
readonly shimmerMs: 3000;
|
|
106
113
|
readonly shimmerTravel: 1000;
|
|
107
|
-
readonly hoverBrightness: 1.1;
|
|
108
|
-
readonly hoverLift: 2;
|
|
109
|
-
readonly hoverScale: 1.01;
|
|
110
114
|
};
|
|
111
115
|
/** Emphasis: the glow's shadow and brightness, the pulse's wash. */
|
|
112
116
|
export declare const GLOW: {
|
|
@@ -120,28 +124,67 @@ export declare const PULSE: {
|
|
|
120
124
|
readonly ms: 2000;
|
|
121
125
|
readonly spread: 10;
|
|
122
126
|
};
|
|
123
|
-
/**
|
|
127
|
+
/**
|
|
128
|
+
* Pointer states — DOM only, kept so the web derives them.
|
|
129
|
+
*
|
|
130
|
+
* HOVER and ACTIVE carry NO geometry, and that is the point rather than an
|
|
131
|
+
* omission. They used to lift the card 3px, scale it 1.01, grow a 20px shadow
|
|
132
|
+
* and spin the icon 10deg at 1.15 — four things moving at once on a surface
|
|
133
|
+
* that is not a control, which made a page of alerts twitch under the pointer.
|
|
134
|
+
*
|
|
135
|
+
* Both are a TINT now: a flat inset layer over the surface. Two mechanisms
|
|
136
|
+
* came before it and each failed on a case the next had to fix. Dropping the
|
|
137
|
+
* element's `opacity` composites toward the PAGE, so it inverted the hover on
|
|
138
|
+
* a light tint (L* 95.0 idle, 89.1 hovered, 91.1 pressed) and vanished on a
|
|
139
|
+
* dark one. A `filter: brightness()` fixed the direction but is MULTIPLICATIVE,
|
|
140
|
+
* so it moved a near-black `glass` by four counts (ΔE76 1.71, under the JND)
|
|
141
|
+
* and Banner's 10%-alpha wash by ΔE76 0.44. A layer is additive: it reads the
|
|
142
|
+
* same on an opaque tint, a translucent wash and a gradient, and because it
|
|
143
|
+
* paints above the background and below the content the ink keeps its own
|
|
144
|
+
* contrast.
|
|
145
|
+
*/
|
|
124
146
|
export declare const HOVER: {
|
|
125
|
-
readonly
|
|
126
|
-
readonly scale: 1.01;
|
|
127
|
-
readonly shadowY: 8;
|
|
128
|
-
readonly shadowBlur: 20;
|
|
129
|
-
readonly shadowAlpha: 0.2;
|
|
130
|
-
readonly iconScale: 1.15;
|
|
131
|
-
readonly iconRotateDeg: 10;
|
|
147
|
+
readonly tintAlpha: 0.06;
|
|
132
148
|
};
|
|
149
|
+
/**
|
|
150
|
+
* The press: a 20% tint LAYER, eased back fast.
|
|
151
|
+
*
|
|
152
|
+
* The layer is the point. The first attempt dropped the whole element's
|
|
153
|
+
* opacity, which composites toward the PAGE and so behaves differently on
|
|
154
|
+
* every surface it is asked about. Measured: over a light page it made a light
|
|
155
|
+
* tint LIGHTER (L* 95.0 idle, 89.1 hovered, 91.1 pressed), so pushing the
|
|
156
|
+
* surface looked like the pointer leaving; over a dark page it landed back on
|
|
157
|
+
* the idle surface (ΔE76 0.82 on `glass`); on `gradient` it washed white body
|
|
158
|
+
* copy to 2.31:1, under AA; and it diluted the ink during a text-selection
|
|
159
|
+
* drag. A `brightness()` step instead of it fixed the direction but not the
|
|
160
|
+
* reach — a multiplier does nothing to a near-black `glass` in dark mode
|
|
161
|
+
* (ΔE76 1.71) and nothing to Banner's 10%-alpha wash (ΔE76 0.44), because it
|
|
162
|
+
* scales only what the element already paints.
|
|
163
|
+
*
|
|
164
|
+
* An inset shadow at 100vmax is a flat tint over the whole surface: ADDITIVE,
|
|
165
|
+
* so it reads the same on an opaque tint, a translucent wash and a gradient;
|
|
166
|
+
* painted above the background and below the content, so the ink keeps its
|
|
167
|
+
* own contrast; and animatable, which `background-image` is not.
|
|
168
|
+
*
|
|
169
|
+
* `ms` is 250 and not the 1000 first asked for, and the reason is that ONE
|
|
170
|
+
* property carries both states. CSS runs a transition from the DESTINATION
|
|
171
|
+
* state's declaration, and entering hover and releasing a press share that
|
|
172
|
+
* destination — so they cannot have different durations without a second
|
|
173
|
+
* animatable property, and both free pseudo-elements are already spent on the
|
|
174
|
+
* `gradient` shimmer and the `pulse` wash. At 1000ms the hover inherited the
|
|
175
|
+
* second: crossing an alert in 300ms peaked at a ≈ 0.024, under the JND, and
|
|
176
|
+
* for ~360ms the alert you had just LEFT was darker than the one under the
|
|
177
|
+
* pointer. A fast press-return is the smaller loss, and it is the call the
|
|
178
|
+
* product owner made when shown both.
|
|
179
|
+
*/
|
|
133
180
|
export declare const ACTIVE: {
|
|
134
|
-
readonly
|
|
135
|
-
readonly
|
|
136
|
-
readonly ms: 100;
|
|
181
|
+
readonly tintAlpha: 0.2;
|
|
182
|
+
readonly ms: 250;
|
|
137
183
|
};
|
|
138
184
|
export declare const FOCUS: {
|
|
139
|
-
readonly ringWidth:
|
|
140
|
-
readonly ringAlpha:
|
|
141
|
-
readonly offset:
|
|
142
|
-
readonly haloSpread: 6;
|
|
143
|
-
readonly haloAlpha: 0.1;
|
|
144
|
-
readonly ms: 200;
|
|
185
|
+
readonly ringWidth: 2;
|
|
186
|
+
readonly ringAlpha: 1;
|
|
187
|
+
readonly offset: 2;
|
|
145
188
|
};
|
|
146
189
|
/** `neutral` has no palette slot; the web has always drawn it from three greys. */
|
|
147
190
|
export declare const NEUTRAL_GREY: Record<'main' | 'light' | 'dark', UiGreyStep>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Alert.metrics.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/Alert/Alert.metrics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,OAAO,EAAgB,KAAK,UAAU,EAAE,KAAK,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEpF;;;;;;;;;;GAUG;AAEH,oFAAoF;AACpF,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,mBAAmB;;;CAA6C,CAAC;AAC9E,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,YAAY,iCAAiC,CAAC;AAE3D,4EAA4E;AAC5E,eAAO,MAAM,OAAO;;;;CAA8C,CAAC;AACnE,eAAO,MAAM,SAAS;;;;CAA4C,CAAC;AACnE,oEAAoE;AACpE,eAAO,MAAM,WAAW,MAAM,CAAC;AAC/B,eAAO,MAAM,cAAc,MAAM,CAAC;AAElC,4FAA4F;AAC5F,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,iBAAiB,OAAO,CAAC;AACtC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,2FAA2F;AAC3F,eAAO,MAAM,cAAc;;;CAAsD,CAAC;AAElF,2GAA2G;AAC3G,eAAO,MAAM,KAAK;;;;;CAAsF,CAAC;AACzG,iDAAiD;AACjD,eAAO,MAAM,WAAW;;;CAA4C,CAAC;AAErE,qGAAqG;AACrG,eAAO,MAAM,SAAS;;;;;;CAAuG,CAAC;AAC9H,oGAAoG;AACpG,eAAO,MAAM,WAAW;;;;CAAmE,CAAC;AAC5F,mFAAmF;AACnF,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"Alert.metrics.d.ts","sourceRoot":"","sources":["../../../../../src/components/data-display/Alert/Alert.metrics.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -44,7 +44,7 @@ export const ICON_SLOT = { size: 22, opacity: 0.9, marginRightUnits: 1.75, paddi
|
|
|
44
44
|
/** The action slot: MUI's `4px 0 0 16px` with the 16px ours, pushed right, 8px into the padding. */
|
|
45
45
|
export const ACTION_SLOT = { paddingLeftUnits: 2, paddingTop: 4, marginRight: -8 } as const;
|
|
46
46
|
/** The close button: MUI's small `IconButton` (5px padding, 18px glyph) at 0.7. */
|
|
47
|
-
export const CLOSE_BUTTON = { padding: 5, iconSize: 18, opacity: 0.7,
|
|
47
|
+
export const CLOSE_BUTTON = { padding: 5, iconSize: 18, opacity: 0.7, washAlpha: 0.1 } as const;
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
50
|
* A semantic Alert's surface and ink: an OPAQUE tint of the hue with a dark
|
|
@@ -54,26 +54,83 @@ export const CLOSE_BUTTON = { padding: 5, iconSize: 18, opacity: 0.7, hoverRotat
|
|
|
54
54
|
export const SEMANTIC_SURFACE = { tint: { light: 0.9, dark: 0.8 }, ink: 0.6, borderAlpha: 0.35 } as const;
|
|
55
55
|
/** `glass`: 85% paper under a 20px blur, a 0.4 divider hairline. */
|
|
56
56
|
export const GLASS = { backgroundAlpha: 0.85, borderAlpha: 0.4, blur: 20, saturatePercent: 180 } as const;
|
|
57
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* `gradient`: light→dark at 0.9 along 135°, a 0.2 white shimmer sweeping 1000px every 3s.
|
|
59
|
+
*
|
|
60
|
+
* It carries no hover of its own, and that is load-bearing rather than an
|
|
61
|
+
* omission. `alertVariantStyles` is spread AFTER the root's `'&:hover'` in the
|
|
62
|
+
* same object literal, so a second `'&:hover'` key does not merge with it — it
|
|
63
|
+
* REPLACES it. While this variant had one, `gradient` was the only alert that
|
|
64
|
+
* brightened on hover while every other variant darkened, and the only one to
|
|
65
|
+
* lose the shimmer reveal the root's hover performs.
|
|
66
|
+
*/
|
|
58
67
|
export const GRADIENT = {
|
|
59
68
|
angleDeg: 135,
|
|
60
69
|
stopAlpha: 0.9,
|
|
61
70
|
shimmerAlpha: 0.2,
|
|
62
71
|
shimmerMs: 3000,
|
|
63
72
|
shimmerTravel: 1000,
|
|
64
|
-
hoverBrightness: 1.1,
|
|
65
|
-
hoverLift: 2,
|
|
66
|
-
hoverScale: 1.01,
|
|
67
73
|
} as const;
|
|
68
74
|
|
|
69
75
|
/** Emphasis: the glow's shadow and brightness, the pulse's wash. */
|
|
70
76
|
export const GLOW = { blur: 20, spread: 5, alpha: 0.3, brightness: 1.05 } as const;
|
|
71
77
|
export const PULSE = { alpha: 0.2, ms: 2000, spread: 10 } as const;
|
|
72
78
|
|
|
73
|
-
/**
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
79
|
+
/**
|
|
80
|
+
* Pointer states — DOM only, kept so the web derives them.
|
|
81
|
+
*
|
|
82
|
+
* HOVER and ACTIVE carry NO geometry, and that is the point rather than an
|
|
83
|
+
* omission. They used to lift the card 3px, scale it 1.01, grow a 20px shadow
|
|
84
|
+
* and spin the icon 10deg at 1.15 — four things moving at once on a surface
|
|
85
|
+
* that is not a control, which made a page of alerts twitch under the pointer.
|
|
86
|
+
*
|
|
87
|
+
* Both are a TINT now: a flat inset layer over the surface. Two mechanisms
|
|
88
|
+
* came before it and each failed on a case the next had to fix. Dropping the
|
|
89
|
+
* element's `opacity` composites toward the PAGE, so it inverted the hover on
|
|
90
|
+
* a light tint (L* 95.0 idle, 89.1 hovered, 91.1 pressed) and vanished on a
|
|
91
|
+
* dark one. A `filter: brightness()` fixed the direction but is MULTIPLICATIVE,
|
|
92
|
+
* so it moved a near-black `glass` by four counts (ΔE76 1.71, under the JND)
|
|
93
|
+
* and Banner's 10%-alpha wash by ΔE76 0.44. A layer is additive: it reads the
|
|
94
|
+
* same on an opaque tint, a translucent wash and a gradient, and because it
|
|
95
|
+
* paints above the background and below the content the ink keeps its own
|
|
96
|
+
* contrast.
|
|
97
|
+
*/
|
|
98
|
+
export const HOVER = { tintAlpha: 0.06 } as const;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The press: a 20% tint LAYER, eased back fast.
|
|
102
|
+
*
|
|
103
|
+
* The layer is the point. The first attempt dropped the whole element's
|
|
104
|
+
* opacity, which composites toward the PAGE and so behaves differently on
|
|
105
|
+
* every surface it is asked about. Measured: over a light page it made a light
|
|
106
|
+
* tint LIGHTER (L* 95.0 idle, 89.1 hovered, 91.1 pressed), so pushing the
|
|
107
|
+
* surface looked like the pointer leaving; over a dark page it landed back on
|
|
108
|
+
* the idle surface (ΔE76 0.82 on `glass`); on `gradient` it washed white body
|
|
109
|
+
* copy to 2.31:1, under AA; and it diluted the ink during a text-selection
|
|
110
|
+
* drag. A `brightness()` step instead of it fixed the direction but not the
|
|
111
|
+
* reach — a multiplier does nothing to a near-black `glass` in dark mode
|
|
112
|
+
* (ΔE76 1.71) and nothing to Banner's 10%-alpha wash (ΔE76 0.44), because it
|
|
113
|
+
* scales only what the element already paints.
|
|
114
|
+
*
|
|
115
|
+
* An inset shadow at 100vmax is a flat tint over the whole surface: ADDITIVE,
|
|
116
|
+
* so it reads the same on an opaque tint, a translucent wash and a gradient;
|
|
117
|
+
* painted above the background and below the content, so the ink keeps its
|
|
118
|
+
* own contrast; and animatable, which `background-image` is not.
|
|
119
|
+
*
|
|
120
|
+
* `ms` is 250 and not the 1000 first asked for, and the reason is that ONE
|
|
121
|
+
* property carries both states. CSS runs a transition from the DESTINATION
|
|
122
|
+
* state's declaration, and entering hover and releasing a press share that
|
|
123
|
+
* destination — so they cannot have different durations without a second
|
|
124
|
+
* animatable property, and both free pseudo-elements are already spent on the
|
|
125
|
+
* `gradient` shimmer and the `pulse` wash. At 1000ms the hover inherited the
|
|
126
|
+
* second: crossing an alert in 300ms peaked at a ≈ 0.024, under the JND, and
|
|
127
|
+
* for ~360ms the alert you had just LEFT was darker than the one under the
|
|
128
|
+
* pointer. A fast press-return is the smaller loss, and it is the call the
|
|
129
|
+
* product owner made when shown both.
|
|
130
|
+
*/
|
|
131
|
+
export const ACTIVE = { tintAlpha: 0.2, ms: 250 } as const;
|
|
132
|
+
|
|
133
|
+
export const FOCUS = { ringWidth: 2, ringAlpha: 1, offset: 2 } as const;
|
|
77
134
|
|
|
78
135
|
/** `neutral` has no palette slot; the web has always drawn it from three greys. */
|
|
79
136
|
export const NEUTRAL_GREY: Record<'main' | 'light' | 'dark', UiGreyStep> = { main: 500, light: 300, dark: 700 };
|
|
@@ -333,16 +333,15 @@ export const alertVariantStyles = (
|
|
|
333
333
|
background: `linear-gradient(90deg, transparent, ${alpha(theme.palette.common.white, GRADIENT.shimmerAlpha)}, transparent)`,
|
|
334
334
|
animation: `${shimmerAnimation} ${seconds(GRADIENT.shimmerMs)} infinite`,
|
|
335
335
|
},
|
|
336
|
-
'&:hover': {
|
|
337
|
-
filter: `brightness(${GRADIENT.hoverBrightness})`,
|
|
338
|
-
transform: `translateY(-${GRADIENT.hoverLift}px) scale(${GRADIENT.hoverScale})`,
|
|
339
|
-
},
|
|
340
336
|
}),
|
|
341
337
|
});
|
|
342
338
|
|
|
343
339
|
/** The web's glow: a soft shadow of the hue and a touch of brightness. */
|
|
344
340
|
const glowStyles = (colorPalette: AlertPalette): CSSObject => ({
|
|
345
|
-
|
|
341
|
+
// No `!important`: the pointer states paint their tint as an INSET shadow on
|
|
342
|
+
// the same property, and compose this one alongside it rather than fighting
|
|
343
|
+
// it. `glowShadow` in `Alert.tsx` is the other half of that pair.
|
|
344
|
+
boxShadow: `0 0 ${GLOW.blur}px ${GLOW.spread}px ${alpha(colorPalette.main, GLOW.alpha)}`,
|
|
346
345
|
filter: `brightness(${GLOW.brightness})`,
|
|
347
346
|
});
|
|
348
347
|
|
|
@@ -5,11 +5,14 @@ import Box from '@mui/material/Box/index.js';
|
|
|
5
5
|
import Collapse from '@mui/material/Collapse/index.js';
|
|
6
6
|
import IconButton from '@mui/material/IconButton/index.js';
|
|
7
7
|
import { alpha, styled } from '@mui/material/styles/index.js';
|
|
8
|
+
import type { Theme } from '@mui/material/styles/index.js';
|
|
8
9
|
import React from 'react';
|
|
9
10
|
|
|
10
11
|
import { defaultAriaLive, resolveAlertProps, testIdFor } from './Alert.helpers';
|
|
12
|
+
import type { AlertPalette } from './Alert.metrics';
|
|
11
13
|
import {
|
|
12
14
|
ACTIVE,
|
|
15
|
+
GLOW,
|
|
13
16
|
ALERT_EASING,
|
|
14
17
|
ALERT_RADIUS_UNITS,
|
|
15
18
|
ALERT_TRANSITION_MS,
|
|
@@ -39,6 +42,41 @@ import { px } from '../../../tokens/theme';
|
|
|
39
42
|
const transition = `all ${seconds(ALERT_TRANSITION_MS)} ${ALERT_EASING}`;
|
|
40
43
|
|
|
41
44
|
// Define animations
|
|
45
|
+
/**
|
|
46
|
+
* The pointer states' shadow: a flat tint, plus the glow when the alert has one.
|
|
47
|
+
*
|
|
48
|
+
* The tint is an inset spread at 100vmax, which always exceeds the element, so
|
|
49
|
+
* it covers the surface edge to edge. It paints ABOVE the background and BELOW
|
|
50
|
+
* the content, which is what keeps the ink's own contrast out of these states
|
|
51
|
+
* entirely — see `ACTIVE` in `Alert.metrics.ts` for why a layer replaced the
|
|
52
|
+
* opacity dip and the brightness multiplier that each preceded it.
|
|
53
|
+
*
|
|
54
|
+
* The glow is COMPOSED rather than replaced, so hovering a glowing alert keeps
|
|
55
|
+
* its glow; `glowStyles` gave up its `!important` for this.
|
|
56
|
+
*/
|
|
57
|
+
function pointerShadow(
|
|
58
|
+
theme: Theme,
|
|
59
|
+
colorPalette: AlertPalette,
|
|
60
|
+
glow: boolean,
|
|
61
|
+
variant: string | undefined,
|
|
62
|
+
): (alpha_: number) => string {
|
|
63
|
+
// The tint moves the surface AWAY from its own ink, which is why this reads
|
|
64
|
+
// the variant and not only the mode.
|
|
65
|
+
//
|
|
66
|
+
// `gradient` is the one variant where the two disagree: it paints a
|
|
67
|
+
// mid-lightness sweep (L* 42–61) under WHITE ink in BOTH modes, so the
|
|
68
|
+
// mode-keyed answer tinted it white in dark mode — toward the text. Measured:
|
|
69
|
+
// white-on-gradient fell to 2.90:1 on hover and 2.41:1 pressed, under the 3:1
|
|
70
|
+
// floor and below its own idle. Every other variant's surface tracks the mode,
|
|
71
|
+
// so for them the two rules agree.
|
|
72
|
+
const ink = variant === 'gradient' || theme.palette.mode === 'light' ? '#000000' : '#ffffff';
|
|
73
|
+
const glowShadow = glow
|
|
74
|
+
? `0 0 ${GLOW.blur}px ${GLOW.spread}px ${alpha(colorPalette.main, GLOW.alpha)}`
|
|
75
|
+
: null;
|
|
76
|
+
return (alpha_) =>
|
|
77
|
+
[`inset 0 0 0 100vmax ${alpha(ink, alpha_)}`, glowShadow].filter(Boolean).join(', ');
|
|
78
|
+
}
|
|
79
|
+
|
|
42
80
|
const StyledAlert = styled(MuiAlert, {
|
|
43
81
|
shouldForwardProp: (prop) =>
|
|
44
82
|
!['customVariant', 'customColor', 'glow', 'pulse', 'animate'].includes(prop as string),
|
|
@@ -51,9 +89,11 @@ const StyledAlert = styled(MuiAlert, {
|
|
|
51
89
|
}>(({ theme, customVariant, customColor, glow, pulse, animate }) => {
|
|
52
90
|
const colorPalette = getColorFromTheme(theme, customColor || customVariant || 'info');
|
|
53
91
|
|
|
92
|
+
const withTint = pointerShadow(theme, colorPalette, Boolean(glow), customVariant);
|
|
93
|
+
|
|
54
94
|
return {
|
|
55
95
|
borderRadius: theme.spacing(ALERT_RADIUS_UNITS),
|
|
56
|
-
transition,
|
|
96
|
+
transition: `${transition}, box-shadow ${seconds(ACTIVE.ms)} ${ALERT_EASING}`,
|
|
57
97
|
position: 'relative',
|
|
58
98
|
overflow: 'hidden',
|
|
59
99
|
animation: animate ? `${fadeInScale} ${seconds(FADE_IN.ms)} ease-out` : 'none',
|
|
@@ -61,33 +101,61 @@ const StyledAlert = styled(MuiAlert, {
|
|
|
61
101
|
|
|
62
102
|
...alertLayoutStyles(theme, colorPalette, animate),
|
|
63
103
|
|
|
64
|
-
// Hover
|
|
104
|
+
// Hover: one tint step, and nothing that moves. An alert is a surface
|
|
105
|
+
// rather than a control, so the pointer only needs telling it is here. The
|
|
106
|
+
// icon keeps its own place for the same reason — it is the variant's
|
|
107
|
+
// meaning, not an affordance to animate.
|
|
65
108
|
'&:hover': {
|
|
66
|
-
|
|
67
|
-
boxShadow: `0 ${HOVER.shadowY}px ${HOVER.shadowBlur}px ${alpha(colorPalette.main, HOVER.shadowAlpha)}`,
|
|
68
|
-
transition,
|
|
69
|
-
|
|
70
|
-
'.MuiAlert-icon': {
|
|
71
|
-
transform: `scale(${HOVER.iconScale}) rotate(${HOVER.iconRotateDeg}deg)`,
|
|
72
|
-
},
|
|
109
|
+
boxShadow: withTint(HOVER.tintAlpha),
|
|
73
110
|
|
|
74
111
|
'&::before': {
|
|
75
112
|
opacity: 1,
|
|
76
113
|
},
|
|
77
114
|
},
|
|
78
115
|
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
116
|
+
// Press: a 20% tint, landing at once and easing back. Still no geometry.
|
|
117
|
+
//
|
|
118
|
+
// `transition: none` here is what makes the dip INSTANT: a click's
|
|
119
|
+
// `:active` lasts about as long as the finger is down, so easing into it
|
|
120
|
+
// would be cut off in its first tenth and read as a flicker. The ease is on
|
|
121
|
+
// the way back, governed by the root.
|
|
122
|
+
//
|
|
123
|
+
// Neither `:hover` nor `:focus-visible` may declare `transition`, and that
|
|
124
|
+
// is not a style preference. `:active` under a mouse ALWAYS implies
|
|
125
|
+
// `:hover`, so a bare shorthand in the hover block outranks the root's
|
|
126
|
+
// (0,2,0 beats 0,1,0) and silently resets the timing this state rests on.
|
|
127
|
+
//
|
|
128
|
+
// The `:not(:has(…))` names CONTROLS, not any descendant. `:active`
|
|
129
|
+
// matches an ancestor of whatever is pressed, so dismissing an alert
|
|
130
|
+
// dimmed the alert being dismissed and pressing a call to action dimmed
|
|
131
|
+
// the message explaining it. Excluding `:has(:active)` wholesale does not
|
|
132
|
+
// work and was the first attempt: pressing the TEXT makes that text
|
|
133
|
+
// `:active` too, so the guard matched on every press and the state never
|
|
134
|
+
// fired at all.
|
|
135
|
+
'&:active:not(:has(button:active, a:active, [role="button"]:active))': {
|
|
136
|
+
boxShadow: withTint(ACTIVE.tintAlpha),
|
|
137
|
+
transition: 'none',
|
|
83
138
|
},
|
|
84
139
|
|
|
85
|
-
// Focus
|
|
86
|
-
|
|
140
|
+
// Focus: `:focus-visible`, not `:focus-within`.
|
|
141
|
+
//
|
|
142
|
+
// The alert carries `tabIndex={0}`, so a MOUSE CLICK focused it and painted
|
|
143
|
+
// the ring — a keyboard affordance answering a pointer, which is what made
|
|
144
|
+
// clicking one look like an error state. `:focus-visible` is the browser's
|
|
145
|
+
// own answer to "did this focus come from the keyboard": the ring stays for
|
|
146
|
+
// whoever tabs here and never fires on a click.
|
|
147
|
+
//
|
|
148
|
+
// One ring, not three. It was a 3px outline at 3px offset UNDER a 6px halo,
|
|
149
|
+
// and stacking two rings on one edge is what read as unfinished rather than
|
|
150
|
+
// as emphasis.
|
|
151
|
+
// It declares no `transition`: `outline-style` is discrete, so the ring
|
|
152
|
+
// arrives at full width with only its COLOUR easing — from `currentcolor`,
|
|
153
|
+
// the near-black alert ink, to the blue. That is a visible dark flash on
|
|
154
|
+
// every keyboard focus, and it would also outrank the root's opacity
|
|
155
|
+
// transition the way the hover block did.
|
|
156
|
+
'&:focus-visible': {
|
|
87
157
|
outline: `${FOCUS.ringWidth}px solid ${alpha(colorPalette.main, FOCUS.ringAlpha)}`,
|
|
88
158
|
outlineOffset: `${FOCUS.offset}px`,
|
|
89
|
-
boxShadow: `0 0 0 ${FOCUS.haloSpread}px ${alpha(colorPalette.main, FOCUS.haloAlpha)}`,
|
|
90
|
-
transition: `all ${seconds(FOCUS.ms)} ease`,
|
|
91
159
|
},
|
|
92
160
|
|
|
93
161
|
...alertVariantStyles(theme, customVariant, colorPalette),
|
|
@@ -161,14 +229,39 @@ const AlertCloseButton: React.FC<{ dataTestId?: string; label: string; onClose:
|
|
|
161
229
|
sx={(theme) => ({
|
|
162
230
|
transition,
|
|
163
231
|
opacity: CLOSE_BUTTON.opacity,
|
|
232
|
+
// Opacity and a wash only: the button used to spin 90deg and grow 1.1,
|
|
233
|
+
// which drew the eye to the dismiss rather than to the message.
|
|
164
234
|
'&:hover': {
|
|
165
|
-
transform: `rotate(${CLOSE_BUTTON.hoverRotateDeg}deg) scale(${CLOSE_BUTTON.hoverScale})`,
|
|
166
235
|
opacity: 1,
|
|
167
236
|
backgroundColor: alpha(theme.palette.action.hover, CLOSE_BUTTON.washAlpha),
|
|
168
237
|
},
|
|
169
|
-
|
|
238
|
+
// A REAL ring, on the keyboard-only selector.
|
|
239
|
+
//
|
|
240
|
+
// This used to be `&:focus` setting `outline: 'none'` over a
|
|
241
|
+
// `rgba(0,0,0,0.1)` wash — 1.25:1 against the info surface, and MUI's
|
|
242
|
+
// ButtonBase already sets `outline: 0`, so there was no UA fallback
|
|
243
|
+
// either. It went unnoticed while the ROOT still matched `:focus-within`
|
|
244
|
+
// and drew a ring around the whole alert; moving the root to
|
|
245
|
+
// `:focus-visible` took that away and left this button with no visible
|
|
246
|
+
// focus at all. `Banner`'s dismiss already draws its own outline, so
|
|
247
|
+
// this is also what keeps the two surfaces telling a keyboard user the
|
|
248
|
+
// same thing.
|
|
249
|
+
'&:focus-visible': {
|
|
170
250
|
opacity: 1,
|
|
171
|
-
|
|
251
|
+
// `currentColor` is the alert's own ink, inherited through
|
|
252
|
+
// `color="inherit"`, so the ring is drawn in whatever colour was
|
|
253
|
+
// already chosen to be READ on this surface. Measured 11.2:1 on the
|
|
254
|
+
// semantic variants and 5.7:1 on `gradient`.
|
|
255
|
+
//
|
|
256
|
+
// That is a reasoned choice, not a test-backed one, and the difference
|
|
257
|
+
// matters: `alert-contrast.test.tsx` covers the ink of the four
|
|
258
|
+
// semantic variants only — its `glass` case asserts the pane's alpha
|
|
259
|
+
// rather than its ink, and `gradient` it does not render at all. A
|
|
260
|
+
// translucent surface's contrast depends on what sits behind it, which
|
|
261
|
+
// is why that test stops where it does and why this comment does not
|
|
262
|
+
// borrow a guarantee it never made.
|
|
263
|
+
outline: `${FOCUS.ringWidth}px solid currentColor`,
|
|
264
|
+
outlineOffset: `${FOCUS.offset}px`,
|
|
172
265
|
backgroundColor: alpha(theme.palette.action.focus, CLOSE_BUTTON.washAlpha),
|
|
173
266
|
},
|
|
174
267
|
})}
|
|
@@ -216,7 +309,11 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>((alertProps, r
|
|
|
216
309
|
// Every spelling of the test id the shared contract allows, mapped to the
|
|
217
310
|
// one the DOM reads and defaulted to `alert`; the native Alert does the same.
|
|
218
311
|
const dataTestId = resolveTestId(others, 'alert');
|
|
219
|
-
|
|
312
|
+
// Then the dismiss union, which `dismissButton` reads off `alertProps` rather
|
|
313
|
+
// than through this destructure — so without this strip it rides `...props`
|
|
314
|
+
// onto the DOM. `ALERT_DEFAULTS` supplies `closable: false`, so that happened
|
|
315
|
+
// on EVERY alert, not only the closable ones.
|
|
316
|
+
const props = withoutAlertOnlyProps(withoutTestIdProps(others));
|
|
220
317
|
|
|
221
318
|
// Depends on `variant`, so it cannot live in the static defaults above.
|
|
222
319
|
const ariaLive = alertProps['aria-live'] ?? defaultAriaLive(variant);
|
|
@@ -272,4 +369,25 @@ export const Alert = React.forwardRef<HTMLDivElement, AlertProps>((alertProps, r
|
|
|
272
369
|
);
|
|
273
370
|
});
|
|
274
371
|
|
|
372
|
+
/**
|
|
373
|
+
* The props `Alert` OWNS, which must never reach the DOM.
|
|
374
|
+
*
|
|
375
|
+
* `AlertProps` extends MUI's, whose rest-spread is how a host's real HTML
|
|
376
|
+
* attributes get onto the root — so the same spread carries the alert's own
|
|
377
|
+
* configuration unless it is named here. Everything else the component consumes
|
|
378
|
+
* leaves through the destructure above; these two do not, because
|
|
379
|
+
* `dismissButton` reads them off the original props to keep the dismiss union's
|
|
380
|
+
* narrowing intact.
|
|
381
|
+
*
|
|
382
|
+
* Exported so a test can render an alert carrying every name on it and assert
|
|
383
|
+
* none reaches the DOM — the check that would have caught `closable`.
|
|
384
|
+
*/
|
|
385
|
+
export const ALERT_ONLY_PROPS = ['closable', 'closeLabel'] as const;
|
|
386
|
+
|
|
387
|
+
function withoutAlertOnlyProps<P extends object>(rest: P): P {
|
|
388
|
+
const html = { ...rest } as Record<string, unknown>;
|
|
389
|
+
for (const key of ALERT_ONLY_PROPS) delete html[key];
|
|
390
|
+
return html as P;
|
|
391
|
+
}
|
|
392
|
+
|
|
275
393
|
Alert.displayName = 'Alert';
|
|
@@ -4,7 +4,7 @@ Known rendering gaps, one bullet each; `pnpm native:ledger` reads them.
|
|
|
4
4
|
|
|
5
5
|
- `glass` paints its 0.85 paper wash and hairline without the 20px backdrop blur and saturation — React Native has no backdrop filter.
|
|
6
6
|
- `gradient` paints its first stop (`alpha(light, 0.9)`) flat and has no shimmer sweep — React Native core has no gradient fill and no pseudo-elements.
|
|
7
|
-
- Hover
|
|
7
|
+
- Hover (a 6% tint layer), press (a 20% one, easing back) and `:focus-visible` (the keyboard ring) have no touch equivalent; the close button's pressed state stands in for its hover.
|
|
8
8
|
- The hairline the web draws around a `Button` placed inside the message is a descendant selector; native children are rendered as given. Consecutive text children DO flow as one sentence (`src/platform/text-children.tsx`); an element child breaks the run, as a block would on the web.
|
|
9
9
|
- After dismissal the web keeps a collapsed, hidden node in the DOM; native unmounts once the 300ms collapse has run.
|
|
10
10
|
- Title, description and children are set in MUI's default `body1`/`body2` numbers; a host that re-themes MUI's typography variants moves the web only.
|
|
@@ -2,6 +2,7 @@ import { alpha, keyframes } from '@mui/material/styles/index.js';
|
|
|
2
2
|
import type { CSSObject, PaletteColor, Theme } from '@mui/material/styles/index.js';
|
|
3
3
|
|
|
4
4
|
import type { BannerVariant } from './Banner.types';
|
|
5
|
+
import { ACTIVE, HOVER } from '../Alert/Alert.metrics';
|
|
5
6
|
|
|
6
7
|
// Animations
|
|
7
8
|
export const fadeInSlide = keyframes`
|
|
@@ -105,16 +106,53 @@ export const bannerPartStyles = (theme: Theme, colorPalette: PaletteColor): CSSO
|
|
|
105
106
|
marginLeft: theme.spacing(1),
|
|
106
107
|
flexShrink: 0,
|
|
107
108
|
|
|
109
|
+
// Opacity and a wash only: the dismiss used to spin 90deg, which drew the
|
|
110
|
+
// eye to the way out rather than to the message. Same fix as `Alert`'s
|
|
111
|
+
// close button, which is the sibling surface (FUT-1458).
|
|
108
112
|
'&:hover': {
|
|
109
113
|
opacity: 1,
|
|
110
114
|
backgroundColor: alpha(colorPalette.main, 0.1),
|
|
111
|
-
transform: 'rotate(90deg)',
|
|
112
115
|
},
|
|
113
116
|
|
|
114
|
-
'&:focus': {
|
|
117
|
+
'&:focus-visible': {
|
|
115
118
|
opacity: 1,
|
|
116
119
|
outline: `2px solid ${colorPalette.main}`,
|
|
117
120
|
outlineOffset: '2px',
|
|
118
121
|
},
|
|
119
122
|
},
|
|
120
123
|
});
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* The pointer states, from `Alert`'s own table.
|
|
127
|
+
*
|
|
128
|
+
* Banner had NO hover at all — measured ΔE76 0.00 on every variant in both
|
|
129
|
+
* modes — so moving between the two surfaces gave feedback on one and silence
|
|
130
|
+
* on the other. They are the same kind of thing and now answer a pointer the
|
|
131
|
+
* same way.
|
|
132
|
+
*
|
|
133
|
+
* Neither block may declare `transition`: `:active` under a mouse always
|
|
134
|
+
* implies `:hover`, so a shorthand here would outrank the root's and reset the
|
|
135
|
+
* opacity fade the press is built on.
|
|
136
|
+
*/
|
|
137
|
+
/** The same flat inset tint `Alert` paints; see `ACTIVE` for why it is a layer. */
|
|
138
|
+
const tint = (theme: Theme, a: number): string =>
|
|
139
|
+
`inset 0 0 0 100vmax ${alpha(theme.palette.mode === 'light' ? '#000000' : '#ffffff', a)}`;
|
|
140
|
+
|
|
141
|
+
export const bannerPointerStates = (theme: Theme): CSSObject => ({
|
|
142
|
+
'&:hover': {
|
|
143
|
+
boxShadow: tint(theme, HOVER.tintAlpha),
|
|
144
|
+
},
|
|
145
|
+
|
|
146
|
+
// The `:not(:has(…))` names CONTROLS, not any descendant: `:active` matches
|
|
147
|
+
// an ancestor of whatever is pressed, so without it pressing the dismiss or
|
|
148
|
+
// an action dimmed the message that owns it. Excluding `:has(:active)`
|
|
149
|
+
// wholesale does not work — pressing the TEXT makes that text `:active` too,
|
|
150
|
+
// so the guard matched on every press and the state never fired.
|
|
151
|
+
'&:active:not(:has(button:active, a:active, [role="button"]:active))': {
|
|
152
|
+
boxShadow: tint(theme, ACTIVE.tintAlpha),
|
|
153
|
+
transition: 'none',
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
/** The seconds the tint takes easing back, shared with `Alert`. */
|
|
158
|
+
export const BANNER_TINT_S = ACTIVE.ms / 1000;
|
|
@@ -11,9 +11,12 @@ import { alpha, styled } from '@mui/material/styles/index.js';
|
|
|
11
11
|
import type { Theme } from '@mui/material/styles/index.js';
|
|
12
12
|
import React from 'react';
|
|
13
13
|
|
|
14
|
-
import { bannerPartStyles, fadeInSlide, getVariantColor } from './Banner.styles';
|
|
14
|
+
import { BANNER_TINT_S, bannerPartStyles, bannerPointerStates, fadeInSlide, getVariantColor } from './Banner.styles';
|
|
15
|
+
import { FOCUS } from '../Alert/Alert.metrics';
|
|
15
16
|
import type { BannerProps, BannerVariant } from './Banner.types';
|
|
16
17
|
|
|
18
|
+
|
|
19
|
+
|
|
17
20
|
const StyledBanner = styled(Box, {
|
|
18
21
|
shouldForwardProp: (prop) => !['variant', 'sticky', 'fullWidth'].includes(prop as string),
|
|
19
22
|
})<{
|
|
@@ -40,12 +43,21 @@ const StyledBanner = styled(Box, {
|
|
|
40
43
|
marginLeft: fullWidth ? '50%' : 0,
|
|
41
44
|
transform: fullWidth ? 'translateX(-50%)' : 'none',
|
|
42
45
|
animation: `${fadeInSlide} 0.3s ease-out`,
|
|
43
|
-
transition:
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
transition: `all 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow ${BANNER_TINT_S}s cubic-bezier(0.4, 0, 0.2, 1)`,
|
|
47
|
+
|
|
48
|
+
...bannerPointerStates(theme),
|
|
49
|
+
|
|
50
|
+
// Focus: `:focus-visible`, not `:focus-within`.
|
|
51
|
+
//
|
|
52
|
+
// The banner carries `tabIndex={0}`, so a MOUSE CLICK focused it and
|
|
53
|
+
// painted this ring — a keyboard affordance answering a pointer, which is
|
|
54
|
+
// what made clicking one look like an error state. `:focus-visible` is the
|
|
55
|
+
// browser's own answer to whether the focus came from the keyboard: the
|
|
56
|
+
// ring stays for whoever tabs here and never fires on a click. Same defect
|
|
57
|
+
// and same fix as `Alert`, which is the sibling surface (FUT-1458).
|
|
58
|
+
'&:focus-visible': {
|
|
59
|
+
outline: `${FOCUS.ringWidth}px solid ${alpha(colorPalette.main, FOCUS.ringAlpha)}`,
|
|
60
|
+
outlineOffset: `${FOCUS.offset}px`,
|
|
49
61
|
},
|
|
50
62
|
|
|
51
63
|
...bannerPartStyles(theme, colorPalette),
|
|
@@ -125,9 +137,11 @@ const buildActionSx = (
|
|
|
125
137
|
backgroundColor: palette.main,
|
|
126
138
|
color: theme.palette.getContrastText(palette.main),
|
|
127
139
|
|
|
140
|
+
// Colour only. The banner itself moves nothing under a pointer, and an
|
|
141
|
+
// action that lifts inside one that does not is the inconsistency a
|
|
142
|
+
// reader notices without being able to name it (FUT-1458).
|
|
128
143
|
'&:hover': {
|
|
129
144
|
backgroundColor: palette.dark || palette.main,
|
|
130
|
-
transform: 'translateY(-1px)',
|
|
131
145
|
},
|
|
132
146
|
}),
|
|
133
147
|
|
|
@@ -138,7 +152,6 @@ const buildActionSx = (
|
|
|
138
152
|
'&:hover': {
|
|
139
153
|
backgroundColor: alpha(palette.main, 0.1),
|
|
140
154
|
borderColor: palette.main,
|
|
141
|
-
transform: 'translateY(-1px)',
|
|
142
155
|
},
|
|
143
156
|
}),
|
|
144
157
|
};
|