@12-apps/ui 6.24.1 → 6.24.3

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.
Files changed (79) hide show
  1. package/NATIVE.md +1 -1
  2. package/dist/_internal/components/data-display/Alert/Alert.js +5 -3
  3. package/dist/_internal/components/data-display/Alert/Alert.metrics.js +1 -1
  4. package/dist/_internal/components/data-display/Alert/Alert.styles.js +2 -2
  5. package/dist/_internal/components/data-display/Banner/Banner.js +7 -2
  6. package/dist/_internal/components/data-display/Banner/Banner.styles.js +10 -1
  7. package/dist/_internal/components/data-display/CardKit/card-actions-context.js +4 -4
  8. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +5 -5
  9. package/dist/_internal/components/data-display/DataViews/DataViewsGrid.js +6 -6
  10. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +11 -11
  11. package/dist/_internal/components/data-display/DataViews/data-views-display-panel.js +2 -2
  12. package/dist/_internal/components/data-display/DataViews/data-views-grid-parts.js +5 -5
  13. package/dist/_internal/components/data-display/DataViews/data-views-shell-toolbar.js +4 -4
  14. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +4 -4
  15. package/dist/_internal/components/data-display/DataViews/data-views-toolbar.js +3 -3
  16. package/dist/_internal/components/data-display/DataViews/data-views-view-nav.js +1 -1
  17. package/dist/_internal/components/data-display/index.js +5 -5
  18. package/dist/_internal/components/index.js +5 -5
  19. package/dist/{chunk-7L6LAVJ7.js → chunk-4IVDVZZ2.js} +25 -11
  20. package/dist/chunk-4IVDVZZ2.js.map +1 -0
  21. package/dist/{chunk-U2YJCNRU.js → chunk-4T7TXRGM.js} +2 -2
  22. package/dist/{chunk-E27Y7FF2.js → chunk-57Z2ND3N.js} +9 -10
  23. package/dist/{chunk-E27Y7FF2.js.map → chunk-57Z2ND3N.js.map} +1 -1
  24. package/dist/{chunk-6RZDDUSM.js → chunk-7GKRZJWI.js} +2 -2
  25. package/dist/{chunk-2C47DEVU.js → chunk-JD4IVP22.js} +2 -2
  26. package/dist/{chunk-JQ45NDBS.js → chunk-JPV45AD6.js} +3 -3
  27. package/dist/{chunk-PRUSQQDT.js → chunk-KM5O4ICK.js} +2 -2
  28. package/dist/{chunk-2YEZONLX.js → chunk-M4LFDGI6.js} +30 -5
  29. package/dist/chunk-M4LFDGI6.js.map +1 -0
  30. package/dist/{chunk-TSYWI3NP.js → chunk-NXNNICH7.js} +2 -2
  31. package/dist/{chunk-2FDBR77W.js → chunk-O6AZDQJJ.js} +2 -2
  32. package/dist/chunk-O6AZDQJJ.js.map +1 -0
  33. package/dist/{chunk-XO753WHD.js → chunk-QX2PLXQA.js} +2 -2
  34. package/dist/{chunk-OAYX4XN7.js → chunk-SQNW22ZZ.js} +2 -2
  35. package/dist/{chunk-O3336DJ6.js → chunk-SYJMI2NP.js} +2 -2
  36. package/dist/{chunk-B2Y3BABA.js → chunk-TXKZEKJN.js} +6 -9
  37. package/dist/chunk-TXKZEKJN.js.map +1 -0
  38. package/dist/{chunk-QFVFHIHE.js → chunk-WNMYWQ7D.js} +98 -30
  39. package/dist/chunk-WNMYWQ7D.js.map +1 -0
  40. package/dist/data-display/Alert.js +3 -3
  41. package/dist/data-display/Banner.js +7 -2
  42. package/dist/data-display/CardKit.js +5 -5
  43. package/dist/data-display/DataViews.js +11 -11
  44. package/dist/native/data-display/Alert.js +2 -5
  45. package/dist/native/data-display/Alert.js.map +1 -1
  46. package/dist/types/components/data-display/Alert/Alert.d.ts +14 -0
  47. package/dist/types/components/data-display/Alert/Alert.d.ts.map +1 -1
  48. package/dist/types/components/data-display/Alert/Alert.metrics.d.ts +66 -23
  49. package/dist/types/components/data-display/Alert/Alert.metrics.d.ts.map +1 -1
  50. package/dist/types/components/data-display/Alert/Alert.styles.d.ts.map +1 -1
  51. package/dist/types/components/data-display/Banner/Banner.d.ts.map +1 -1
  52. package/dist/types/components/data-display/Banner/Banner.styles.d.ts +3 -0
  53. package/dist/types/components/data-display/Banner/Banner.styles.d.ts.map +1 -1
  54. package/dist/types/components/data-display/DataViews/data-views-view-nav.d.ts +1 -1
  55. package/dist/types/components/data-display/DataViews/data-views-view-nav.d.ts.map +1 -1
  56. package/dist/types-native/components/data-display/Alert/Alert.metrics.d.ts +66 -23
  57. package/dist/types-native/components/data-display/Alert/Alert.metrics.d.ts.map +1 -1
  58. package/package.json +1 -1
  59. package/src/components/data-display/Alert/Alert.metrics.ts +66 -9
  60. package/src/components/data-display/Alert/Alert.styles.tsx +4 -5
  61. package/src/components/data-display/Alert/Alert.tsx +139 -21
  62. package/src/components/data-display/Alert/NATIVE-NOTES.md +1 -1
  63. package/src/components/data-display/Banner/Banner.styles.ts +40 -2
  64. package/src/components/data-display/Banner/Banner.tsx +22 -9
  65. package/src/components/data-display/DataViews/data-views-view-nav.tsx +3 -3
  66. package/dist/chunk-2FDBR77W.js.map +0 -1
  67. package/dist/chunk-2YEZONLX.js.map +0 -1
  68. package/dist/chunk-7L6LAVJ7.js.map +0 -1
  69. package/dist/chunk-B2Y3BABA.js.map +0 -1
  70. package/dist/chunk-QFVFHIHE.js.map +0 -1
  71. /package/dist/{chunk-U2YJCNRU.js.map → chunk-4T7TXRGM.js.map} +0 -0
  72. /package/dist/{chunk-6RZDDUSM.js.map → chunk-7GKRZJWI.js.map} +0 -0
  73. /package/dist/{chunk-2C47DEVU.js.map → chunk-JD4IVP22.js.map} +0 -0
  74. /package/dist/{chunk-JQ45NDBS.js.map → chunk-JPV45AD6.js.map} +0 -0
  75. /package/dist/{chunk-PRUSQQDT.js.map → chunk-KM5O4ICK.js.map} +0 -0
  76. /package/dist/{chunk-TSYWI3NP.js.map → chunk-NXNNICH7.js.map} +0 -0
  77. /package/dist/{chunk-XO753WHD.js.map → chunk-QX2PLXQA.js.map} +0 -0
  78. /package/dist/{chunk-OAYX4XN7.js.map → chunk-SQNW22ZZ.js.map} +0 -0
  79. /package/dist/{chunk-O3336DJ6.js.map → chunk-SYJMI2NP.js.map} +0 -0
@@ -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 effects
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
- transform: `translateY(-${HOVER.lift}px) scale(${HOVER.scale})`,
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
- // Active state
80
- '&:active': {
81
- transform: `translateY(-${ACTIVE.lift}px) scale(${ACTIVE.scale})`,
82
- transition: `transform ${seconds(ACTIVE.ms)} ease`,
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 styles for accessibility
86
- '&:focus-within': {
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
- '&:focus': {
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
- outline: 'none',
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
- const props = withoutTestIdProps(others);
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, active and focus-within (the lift, the hue shadow, the icon tilt, the focus ring) have no touch equivalent; the close button's pressed state stands in for its 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: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
44
-
45
- // Enhanced accessibility and focus styles
46
- '&:focus-within': {
47
- outline: `3px solid ${alpha(colorPalette.main, 0.5)}`,
48
- outlineOffset: '2px',
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
  };
@@ -37,7 +37,7 @@ import type { SavedViewSummary } from "./data-views-types";
37
37
  export interface ViewNavHandlers {
38
38
  views: SavedViewSummary[];
39
39
  activeViewId?: string | null;
40
- /** `null` selects the built-in "{copy.nav.mainView}". */
40
+ /** `null` selects the built-in view named by `copy.nav.mainView`. */
41
41
  onSelectView: (id: string | null) => void;
42
42
  onEditView: (view: SavedViewSummary) => void;
43
43
  onPatchView: (
@@ -224,7 +224,7 @@ function ToggleContents({
224
224
  return (
225
225
  <>
226
226
  <Box component="span" sx={{ fontWeight: 600 }} data-testid={`${testIdPrefix}-display-view-name`}>
227
- {view.activeViewName ?? "{copy.nav.mainView}"}
227
+ {view.activeViewName ?? copy.nav.mainView}
228
228
  </Box>
229
229
  {view.dirty && (
230
230
  <Box
@@ -345,7 +345,7 @@ export function ViewFooter({
345
345
  {copy.nav.reset}
346
346
  </Button>
347
347
  <Box sx={{ display: "flex", alignItems: "center", gap: 1 }}>
348
- {/* Only meaningful with a view APPLIED: on {copy.nav.mainView} the single
348
+ {/* Only meaningful with a view APPLIED: on the main view the single
349
349
  "Salvar visão" already is "save as new". */}
350
350
  {view.onUpdate && (
351
351
  <Button
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/DataViews/data-views-view-nav.tsx"],"sourcesContent":["\"use client\";\n\n/**\n * The saved views, rendered INSIDE the Exibir panel rather than in a dropdown\n * of their own.\n *\n * Two levels, both of them replacing the panel's BODY (the tabs stay put):\n * the list of views, and one view's actions. It is a drill-down and not a\n * nested menu on purpose — an absolutely positioned popover inside the panel's\n * own scroll container gets clipped by it, which is how copy.nav.deleteView ends\n * up unreachable. One level, no clipping, and it works on touch.\n */\nimport ChevronLeftRoundedIcon from \"@mui/icons-material/ChevronLeftRounded\";\nimport CheckRoundedIcon from \"@mui/icons-material/CheckRounded\";\nimport DeleteOutlineRoundedIcon from \"@mui/icons-material/DeleteOutlineRounded\";\nimport EditOutlinedIcon from \"@mui/icons-material/EditOutlined\";\nimport GroupOutlinedIcon from \"@mui/icons-material/GroupOutlined\";\nimport MoreVertRoundedIcon from \"@mui/icons-material/MoreVertRounded\";\nimport PushPinOutlinedIcon from \"@mui/icons-material/PushPinOutlined\";\nimport StarOutlineRoundedIcon from \"@mui/icons-material/StarOutlined\";\nimport StarOutlineRoundedIcon2 from \"@mui/icons-material/StarOutlineRounded\";\nimport IconButton from \"@mui/material/IconButton/index.js\";\n\nimport KeyboardArrowDownRoundedIcon from \"@mui/icons-material/KeyboardArrowDownRounded\";\nimport RestartAltRoundedIcon from \"@mui/icons-material/RestartAltRounded\";\nimport SaveOutlinedIcon from \"@mui/icons-material/SaveOutlined\";\n\nimport { useDataViewsCopy } from \"./data-views-copy-context\";\nimport { Button } from \"../../form/Button\";\nimport { Box } from \"../../../mui/Box\";\nimport { Text } from \"../../typography/Text\";\n\nimport type { DisplayPanelView } from \"./data-views-display-panel\";\nimport type { SavedViewSummary } from \"./data-views-types\";\n\n/** What the panel needs to drive the views without owning any of them. */\nexport interface ViewNavHandlers {\n views: SavedViewSummary[];\n activeViewId?: string | null;\n /** `null` selects the built-in \"{copy.nav.mainView}\". */\n onSelectView: (id: string | null) => void;\n onEditView: (view: SavedViewSummary) => void;\n onPatchView: (\n view: SavedViewSummary,\n changes: Partial<Pick<SavedViewSummary, \"isDefault\" | \"pinned\" | \"shared\">>,\n ) => void;\n onDeleteView: (view: SavedViewSummary) => void;\n}\n\nconst rowSx = {\n display: \"flex\",\n alignItems: \"center\",\n gap: 1,\n width: \"100%\",\n px: 1.25,\n py: 1,\n border: 0,\n borderRadius: 1,\n bgcolor: \"transparent\",\n cursor: \"pointer\",\n font: \"inherit\",\n fontSize: \"0.8125rem\",\n textAlign: \"left\",\n color: \"text.primary\",\n \"&:hover\": { bgcolor: \"action.hover\" },\n} as const;\n\n/** The list: the built-in view, then each saved one with its own \"⋮\". */\nexport function ViewsList({\n handlers,\n onOpenActions,\n testIdPrefix,\n}: {\n handlers: ViewNavHandlers;\n onOpenActions: (view: SavedViewSummary) => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { views, activeViewId, onSelectView } = handlers;\n const onMain = !activeViewId;\n return (\n <Box sx={{ p: 0.75 }} data-testid={`${testIdPrefix}-views-list`}>\n <Box\n component=\"button\"\n type=\"button\"\n onClick={() => onSelectView(null)}\n data-testid={`${testIdPrefix}-view-main`}\n sx={{ ...rowSx, color: onMain ? \"primary.main\" : \"text.primary\", fontWeight: onMain ? 600 : 400 }}\n >\n <Box component=\"span\" sx={{ flex: 1 }}>\n {copy.nav.mainView}\n </Box>\n {onMain && <CheckRoundedIcon fontSize=\"small\" />}\n </Box>\n\n {views.map((view) => {\n const active = view.id === activeViewId;\n return (\n <Box key={view.id} sx={{ display: \"flex\", alignItems: \"center\" }}>\n <Box\n component=\"button\"\n type=\"button\"\n onClick={() => onSelectView(view.id)}\n data-testid={`${testIdPrefix}-view-${view.id}`}\n sx={{ ...rowSx, minWidth: 0, color: active ? \"primary.main\" : \"text.primary\", fontWeight: active ? 600 : 400 }}\n >\n <Box component=\"span\" sx={{ flex: 1, minWidth: 0, overflow: \"hidden\", textOverflow: \"ellipsis\", whiteSpace: \"nowrap\" }}>\n {view.name}\n </Box>\n {/* What the row-menu toggles DID, readable without opening it:\n a view set as default, pinned or shared says so here. */}\n {view.isDefault && (\n <StarOutlineRoundedIcon fontSize=\"small\" sx={{ color: \"warning.main\" }} titleAccess={copy.nav.defaultTag} />\n )}\n {view.pinned && (\n <PushPinOutlinedIcon fontSize=\"small\" sx={{ color: \"text.disabled\" }} titleAccess=\"Fixada\" />\n )}\n {view.shared && (\n <GroupOutlinedIcon fontSize=\"small\" sx={{ color: \"text.disabled\" }} titleAccess=\"Compartilhada\" />\n )}\n {active && <CheckRoundedIcon fontSize=\"small\" />}\n </Box>\n <IconButton\n size=\"small\"\n onClick={() => onOpenActions(view)}\n aria-label={copy.nav.viewOptions(view.name)}\n data-testid={`${testIdPrefix}-view-${view.id}-menu`}\n >\n <MoreVertRoundedIcon fontSize=\"small\" />\n </IconButton>\n </Box>\n );\n })}\n\n {views.length === 0 && (\n <Text variant=\"caption\" as=\"p\">\n <Box component=\"span\" sx={{ display: \"block\", px: 1.25, py: 1.5, color: \"text.secondary\", lineHeight: 1.6 }}>\n {copy.nav.emptyHint}\n </Box>\n </Text>\n )}\n </Box>\n );\n}\n\n/** One view's actions, reached from its \"⋮\" and left by the back row. */\nexport function ViewActions({\n view,\n handlers,\n onBack,\n testIdPrefix,\n}: {\n view: SavedViewSummary;\n handlers: ViewNavHandlers;\n onBack: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n const { onEditView, onPatchView, onDeleteView } = handlers;\n const action = (\n key: string,\n Icon: typeof StarOutlineRoundedIcon2,\n label: string,\n onClick: () => void,\n opts: { on?: boolean; danger?: boolean } = {},\n ): React.JSX.Element => (\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onClick}\n data-testid={`${testIdPrefix}-view-action-${key}`}\n sx={{\n ...rowSx,\n color: opts.danger ? \"error.main\" : \"text.primary\",\n \"&:hover\": { bgcolor: opts.danger ? \"error.lighter\" : \"action.hover\" },\n }}\n >\n <Icon fontSize=\"small\" sx={{ color: opts.danger ? \"inherit\" : \"text.disabled\" }} />\n <Box component=\"span\" sx={{ flex: 1 }}>\n {label}\n </Box>\n {opts.on && <CheckRoundedIcon fontSize=\"small\" color=\"primary\" />}\n </Box>\n );\n\n return (\n <Box sx={{ p: 0.75 }} data-testid={`${testIdPrefix}-view-actions`}>\n <Box\n component=\"button\"\n type=\"button\"\n onClick={onBack}\n data-testid={`${testIdPrefix}-view-actions-back`}\n sx={{ ...rowSx, color: \"text.secondary\", fontSize: \"0.75rem\" }}\n >\n <ChevronLeftRoundedIcon fontSize=\"small\" />\n <Box component=\"span\" sx={{ flex: 1, fontWeight: 600, color: \"text.primary\" }}>\n {view.name}\n </Box>\n </Box>\n <Box sx={{ my: 0.5, borderTop: 1, borderColor: \"divider\" }} />\n {action(\"edit\", EditOutlinedIcon, copy.nav.editView, () => onEditView(view))}\n {action(\"default\", StarOutlineRoundedIcon2, copy.nav.setDefault, () => onPatchView(view, { isDefault: !view.isDefault }), { on: view.isDefault })}\n {action(\"pin\", PushPinOutlinedIcon, copy.nav.pinToSidebar, () => onPatchView(view, { pinned: !view.pinned }), { on: view.pinned })}\n {action(\"share\", GroupOutlinedIcon, copy.nav.shareWithTeam, () => onPatchView(view, { shared: !view.shared }), { on: view.shared })}\n <Box sx={{ my: 0.5, borderTop: 1, borderColor: \"divider\" }} />\n {action(\"delete\", DeleteOutlineRoundedIcon, copy.nav.deleteView, () => onDeleteView(view), { danger: true })}\n </Box>\n );\n}\n\n/** Name, unsaved dot, chevron — the parts, so the toggle has one decision. */\nfunction ToggleContents({\n view,\n open,\n interactive,\n testIdPrefix,\n}: {\n view: DisplayPanelView;\n open: boolean;\n interactive: boolean;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <>\n <Box component=\"span\" sx={{ fontWeight: 600 }} data-testid={`${testIdPrefix}-display-view-name`}>\n {view.activeViewName ?? \"{copy.nav.mainView}\"}\n </Box>\n {view.dirty && (\n <Box\n component=\"span\"\n aria-label={copy.nav.unsavedChanges}\n data-testid={`${testIdPrefix}-display-dirty`}\n sx={{ height: 6, width: 6, borderRadius: \"50%\", bgcolor: \"primary.main\" }}\n />\n )}\n {interactive && (\n <KeyboardArrowDownRoundedIcon\n fontSize=\"small\"\n sx={{ color: \"text.disabled\", transform: open ? \"rotate(180deg)\" : \"none\" }}\n />\n )}\n </>\n );\n}\n\n/** The name + unsaved dot; a button only when there are views to open. */\nfunction ViewToggle({\n view,\n open,\n onToggle,\n testIdPrefix,\n}: {\n view: DisplayPanelView;\n open: boolean;\n onToggle: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const interactive = Boolean(view.nav);\n // One decision, not eight: a panel with no views renders the same row as\n // plain text rather than as a button that opens nothing.\n const affordance = interactive\n ? { px: 0.75, py: 0.25, cursor: \"pointer\", \"&:hover\": { bgcolor: \"action.hover\" } }\n : { px: 0, py: 0, cursor: \"default\" };\n return (\n <Box\n component={interactive ? \"button\" : \"div\"}\n type={interactive ? \"button\" : undefined}\n onClick={interactive ? onToggle : undefined}\n aria-expanded={interactive ? open : undefined}\n data-testid={`${testIdPrefix}-display-view-toggle`}\n sx={{\n ml: \"auto\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 0.75,\n minWidth: 0,\n border: 0,\n borderRadius: 1,\n font: \"inherit\",\n fontSize: \"0.75rem\",\n bgcolor: open ? \"action.selected\" : \"transparent\",\n ...affordance,\n }}\n >\n <ToggleContents view={view} open={open} interactive={interactive} testIdPrefix={testIdPrefix} />\n </Box>\n );\n}\n\n/** The VISÃO row above the tabs. */\nexport function ViewHeader(props: {\n view: DisplayPanelView;\n open: boolean;\n onToggle: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1, px: 1.5, py: 1, borderBottom: 1, borderColor: \"divider\" }}>\n <Text variant=\"caption\" as=\"span\">\n <Box component=\"span\" sx={{ textTransform: \"uppercase\", letterSpacing: 0.5, color: \"text.disabled\" }}>\n {copy.nav.label}\n </Box>\n </Text>\n <ViewToggle {...props} />\n </Box>\n );\n}\n\n/** Redefinir / Salvar como nova / Atualizar, pinned below the tabs. */\nexport function ViewFooter({\n view,\n onDone,\n testIdPrefix,\n}: {\n view: DisplayPanelView;\n onDone: () => void;\n testIdPrefix: string;\n}): React.JSX.Element {\n const copy = useDataViewsCopy();\n return (\n <Box\n sx={{\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 1,\n px: 1.5,\n py: 1,\n borderTop: 1,\n borderColor: \"divider\",\n bgcolor: \"action.hover\",\n }}\n >\n <Button\n variant=\"text\"\n size=\"sm\"\n color=\"neutral\"\n disabled={!view.dirty}\n startIcon={<RestartAltRoundedIcon fontSize=\"small\" />}\n onClick={view.onReset}\n dataTestId={`${testIdPrefix}-display-reset`}\n >\n {copy.nav.reset}\n </Button>\n <Box sx={{ display: \"flex\", alignItems: \"center\", gap: 1 }}>\n {/* Only meaningful with a view APPLIED: on {copy.nav.mainView} the single\n \"Salvar visão\" already is \"save as new\". */}\n {view.onUpdate && (\n <Button\n variant=\"text\"\n size=\"sm\"\n color=\"neutral\"\n onClick={() => {\n view.onSaveAs();\n onDone();\n }}\n dataTestId={`${testIdPrefix}-display-save-as`}\n >\n {copy.nav.saveAs}\n </Button>\n )}\n {/* Enabled only when something in Ordenar / Colunas / Exibição (or the\n filters the view also stores) differs from the applied view: the\n filled button is a STATE — \"there is something to save\" — not a\n permanent fixture. */}\n <Button\n size=\"sm\"\n disabled={!view.dirty}\n startIcon={<SaveOutlinedIcon fontSize=\"small\" />}\n onClick={() => {\n (view.onUpdate ?? view.onSaveAs)();\n onDone();\n }}\n dataTestId={`${testIdPrefix}-display-save`}\n >\n {view.onUpdate ? copy.nav.update : copy.nav.save}\n </Button>\n </Box>\n </Box>\n );\n}\n\n"],"mappings":"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","names":["default"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Banner/Banner.styles.ts"],"sourcesContent":["import { alpha, keyframes } from '@mui/material/styles/index.js';\nimport type { CSSObject, PaletteColor, Theme } from '@mui/material/styles/index.js';\n\nimport type { BannerVariant } from './Banner.types';\n\n// Animations\nexport const fadeInSlide = keyframes`\n from {\n opacity: 0;\n transform: translateY(-10px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n`;\n\nconst pulseAnimation = keyframes`\n 0% {\n box-shadow: 0 0 0 0 currentColor;\n opacity: 0.7;\n }\n 70% {\n box-shadow: 0 0 0 8px currentColor;\n opacity: 0;\n }\n 100% {\n box-shadow: 0 0 0 0 currentColor;\n opacity: 0;\n }\n`;\n\nexport const getVariantColor = (theme: Theme, variant: BannerVariant) => {\n const colorMap = {\n info: theme.palette.info,\n success: theme.palette.success,\n warning: theme.palette.warning,\n critical: theme.palette.error,\n };\n return colorMap[variant];\n};\n\nexport const bannerPartStyles = (theme: Theme, colorPalette: PaletteColor): CSSObject => ({\n // Icon container\n '.banner-icon': {\n flexShrink: 0,\n marginTop: theme.spacing(0.25),\n color: colorPalette.main,\n fontSize: '1.25rem',\n display: 'flex',\n alignItems: 'center',\n position: 'relative',\n\n '&::before': {\n content: '\"\"',\n position: 'absolute',\n inset: -4,\n borderRadius: '50%',\n background: alpha(colorPalette.main, 0.1),\n animation: `${pulseAnimation} 3s infinite`,\n },\n },\n\n // Content area\n '.banner-content': {\n flex: 1,\n minWidth: 0, // Prevents flex item overflow\n },\n\n // Title styles\n '.banner-title': {\n fontWeight: 600,\n fontSize: '1rem',\n lineHeight: 1.4,\n marginBottom: theme.spacing(0.5),\n color: colorPalette.dark || colorPalette.main,\n },\n\n // Description styles \n '.banner-description': {\n fontSize: '0.875rem',\n lineHeight: 1.5,\n opacity: 0.9,\n color: 'inherit',\n },\n\n // Actions area\n '.banner-actions': {\n display: 'flex',\n flexShrink: 0,\n gap: theme.spacing(1),\n alignItems: 'center',\n marginTop: theme.spacing(1),\n\n [theme.breakpoints.up('sm')]: {\n marginTop: 0,\n marginLeft: theme.spacing(2),\n },\n },\n\n // Dismiss button\n '.banner-dismiss': {\n color: 'inherit',\n opacity: 0.7,\n marginLeft: theme.spacing(1),\n flexShrink: 0,\n\n '&:hover': {\n opacity: 1,\n backgroundColor: alpha(colorPalette.main, 0.1),\n transform: 'rotate(90deg)',\n },\n\n '&:focus': {\n opacity: 1,\n outline: `2px solid ${colorPalette.main}`,\n outlineOffset: '2px',\n },\n },\n});\n"],"mappings":";AAAA,SAAS,OAAO,iBAAiB;AAM1B,IAAM,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAW3B,IAAM,iBAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAehB,IAAM,kBAAkB,CAAC,OAAc,YAA2B;AACvE,QAAM,WAAW;AAAA,IACf,MAAM,MAAM,QAAQ;AAAA,IACpB,SAAS,MAAM,QAAQ;AAAA,IACvB,SAAS,MAAM,QAAQ;AAAA,IACvB,UAAU,MAAM,QAAQ;AAAA,EAC1B;AACA,SAAO,SAAS,OAAO;AACzB;AAEO,IAAM,mBAAmB,CAAC,OAAc,kBAA2C;AAAA;AAAA,EAExF,gBAAgB;AAAA,IACd,YAAY;AAAA,IACZ,WAAW,MAAM,QAAQ,IAAI;AAAA,IAC7B,OAAO,aAAa;AAAA,IACpB,UAAU;AAAA,IACV,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,UAAU;AAAA,IAEV,aAAa;AAAA,MACX,SAAS;AAAA,MACT,UAAU;AAAA,MACV,OAAO;AAAA,MACP,cAAc;AAAA,MACd,YAAY,MAAM,aAAa,MAAM,GAAG;AAAA,MACxC,WAAW,GAAG,cAAc;AAAA,IAC9B;AAAA,EACF;AAAA;AAAA,EAGA,mBAAmB;AAAA,IACjB,MAAM;AAAA,IACN,UAAU;AAAA;AAAA,EACZ;AAAA;AAAA,EAGA,iBAAiB;AAAA,IACf,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,cAAc,MAAM,QAAQ,GAAG;AAAA,IAC/B,OAAO,aAAa,QAAQ,aAAa;AAAA,EAC3C;AAAA;AAAA,EAGA,uBAAuB;AAAA,IACrB,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,SAAS;AAAA,IACT,OAAO;AAAA,EACT;AAAA;AAAA,EAGA,mBAAmB;AAAA,IACjB,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,KAAK,MAAM,QAAQ,CAAC;AAAA,IACpB,YAAY;AAAA,IACZ,WAAW,MAAM,QAAQ,CAAC;AAAA,IAE1B,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,GAAG;AAAA,MAC5B,WAAW;AAAA,MACX,YAAY,MAAM,QAAQ,CAAC;AAAA,IAC7B;AAAA,EACF;AAAA;AAAA,EAGA,mBAAmB;AAAA,IACjB,OAAO;AAAA,IACP,SAAS;AAAA,IACT,YAAY,MAAM,QAAQ,CAAC;AAAA,IAC3B,YAAY;AAAA,IAEZ,WAAW;AAAA,MACT,SAAS;AAAA,MACT,iBAAiB,MAAM,aAAa,MAAM,GAAG;AAAA,MAC7C,WAAW;AAAA,IACb;AAAA,IAEA,WAAW;AAAA,MACT,SAAS;AAAA,MACT,SAAS,aAAa,aAAa,IAAI;AAAA,MACvC,eAAe;AAAA,IACjB;AAAA,EACF;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Banner/Banner.tsx"],"sourcesContent":["import CheckCircle from '@mui/icons-material/CheckCircle';\nimport Close from '@mui/icons-material/Close';\nimport Error from '@mui/icons-material/Error';\nimport Info from '@mui/icons-material/Info';\nimport Warning from '@mui/icons-material/Warning';\nimport Box from '@mui/material/Box/index.js';\nimport Button from '@mui/material/Button/index.js';\nimport IconButton from '@mui/material/IconButton/index.js';\nimport Typography from '@mui/material/Typography/index.js';\nimport { alpha, styled } from '@mui/material/styles/index.js';\nimport type { Theme } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { bannerPartStyles, fadeInSlide, getVariantColor } from './Banner.styles';\nimport type { BannerProps, BannerVariant } from './Banner.types';\n\nconst StyledBanner = styled(Box, {\n shouldForwardProp: (prop) => !['variant', 'sticky', 'fullWidth'].includes(prop as string),\n})<{\n variant: BannerVariant;\n sticky: boolean;\n fullWidth: boolean;\n}>(({ theme, variant, sticky, fullWidth }) => {\n\n const colorPalette = getVariantColor(theme, variant);\n\n return {\n display: 'flex',\n alignItems: 'flex-start',\n gap: theme.spacing(1.5),\n padding: theme.spacing(1.5, 2),\n borderRadius: theme.spacing(1),\n backgroundColor: alpha(colorPalette.main, 0.1),\n border: `1px solid ${alpha(colorPalette.main, 0.2)}`,\n color: colorPalette.main,\n position: sticky ? 'sticky' : 'relative',\n top: sticky ? 0 : 'auto',\n zIndex: sticky ? theme.zIndex.appBar : 'auto',\n width: fullWidth ? '100vw' : '100%',\n marginLeft: fullWidth ? '50%' : 0,\n transform: fullWidth ? 'translateX(-50%)' : 'none',\n animation: `${fadeInSlide} 0.3s ease-out`,\n transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',\n\n // Enhanced accessibility and focus styles\n '&:focus-within': {\n outline: `3px solid ${alpha(colorPalette.main, 0.5)}`,\n outlineOffset: '2px',\n },\n\n ...bannerPartStyles(theme, colorPalette),\n\n // Responsive layout\n [theme.breakpoints.down('sm')]: {\n flexDirection: 'column',\n alignItems: 'stretch',\n gap: theme.spacing(1),\n \n '.banner-content': {\n order: 1,\n },\n\n '.banner-actions': {\n order: 2,\n justifyContent: 'flex-start',\n marginTop: theme.spacing(1),\n marginLeft: 0,\n },\n },\n\n // Glass effect variant (optional enhancement)\n '&.banner-glass': {\n backgroundColor: alpha(theme.palette.background.paper, 0.1),\n backdropFilter: 'blur(20px)',\n border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,\n color: theme.palette.text.primary,\n \n '.banner-icon': {\n color: colorPalette.main,\n },\n },\n };\n});\n\nconst getVariantIcon = (variant: BannerVariant) => {\n const iconMap: Record<BannerVariant, React.ReactNode> = {\n info: <Info />,\n success: <CheckCircle />,\n warning: <Warning />,\n critical: <Error />,\n };\n return iconMap[variant];\n};\n\ntype BannerAction = NonNullable<BannerProps['actions']>[number];\n\n// Warnings and criticals interrupt the screen reader; the rest are announced\n// politely when it next pauses.\nconst resolveAriaAnnouncement = (variant: BannerVariant) => ({\n role: variant === 'warning' || variant === 'critical' ? 'alert' : 'status',\n live: (variant === 'critical' ? 'assertive' : 'polite') as 'assertive' | 'polite',\n});\n\nconst testIdFor = (base: string | undefined, suffix: string) =>\n base ? `${base}-${suffix}` : `banner-${suffix}`;\n\n// Resolves the banner's palette once, rather than per property as the inline\n// version did — getVariantColor was being called nine times to build one button.\nconst buildActionSx = (\n theme: Theme,\n variant: BannerVariant,\n actionVariant: BannerAction['variant'],\n) => {\n const palette = getVariantColor(theme, variant);\n\n return {\n minWidth: 'auto',\n px: 1.5,\n py: 0.5,\n fontSize: '0.8125rem',\n fontWeight: 500,\n borderRadius: 1,\n\n ...(actionVariant === 'primary' && {\n backgroundColor: palette.main,\n color: theme.palette.getContrastText(palette.main),\n\n '&:hover': {\n backgroundColor: palette.dark || palette.main,\n transform: 'translateY(-1px)',\n },\n }),\n\n ...(actionVariant === 'secondary' && {\n borderColor: palette.main,\n color: palette.main,\n\n '&:hover': {\n backgroundColor: alpha(palette.main, 0.1),\n borderColor: palette.main,\n transform: 'translateY(-1px)',\n },\n }),\n };\n};\n\nconst BannerActionButton: React.FC<{\n action: BannerAction;\n variant: BannerVariant;\n dataTestId?: string;\n}> = ({ action, variant, dataTestId }) => (\n <Button\n size=\"small\"\n variant={action.variant === 'primary' ? 'contained' : 'outlined'}\n onClick={action.onClick}\n data-testid={testIdFor(dataTestId, 'action')}\n sx={(theme) => buildActionSx(theme, variant, action.variant)}\n >\n {action.label}\n </Button>\n);\n\nconst BannerBody: React.FC<{\n displayIcon: React.ReactNode;\n title?: BannerProps['title'];\n description?: BannerProps['description'];\n dataTestId?: string;\n children?: React.ReactNode;\n}> = ({ displayIcon, title, description, dataTestId, children }) => (\n <>\n {displayIcon && (\n <Box className=\"banner-icon\" aria-hidden=\"true\" data-testid={testIdFor(dataTestId, 'icon')}>\n {displayIcon}\n </Box>\n )}\n\n <Box className=\"banner-content\">\n {title && (\n <Typography\n className=\"banner-title\"\n variant=\"subtitle2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'title')}\n >\n {title}\n </Typography>\n )}\n {description && (\n <Typography\n className=\"banner-description\"\n variant=\"body2\"\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'message')}\n >\n {description}\n </Typography>\n )}\n {children}\n </Box>\n </>\n);\n\nconst BannerTrailing: React.FC<{\n actions?: BannerProps['actions'];\n variant: BannerVariant;\n dismissible: boolean;\n onDismissClick: () => void;\n dismissLabel: string;\n dataTestId?: string;\n}> = ({ actions, variant, dismissible, onDismissClick, dismissLabel, dataTestId }) => (\n <Box display=\"flex\" alignItems=\"center\" gap={1}>\n {actions && actions.length > 0 && (\n <Box className=\"banner-actions\">\n {actions.map((action, index) => (\n <BannerActionButton\n key={index}\n action={action}\n variant={variant}\n dataTestId={dataTestId}\n />\n ))}\n </Box>\n )}\n\n {dismissible && (\n <IconButton\n className=\"banner-dismiss\"\n size=\"small\"\n onClick={onDismissClick}\n aria-label={dismissLabel}\n data-testid={testIdFor(dataTestId, 'close')}\n sx={{ transition: 'all 0.2s ease' }}\n >\n <Close fontSize=\"small\" />\n </IconButton>\n )}\n </Box>\n);\n\nexport const Banner = React.forwardRef<HTMLDivElement, BannerProps>(\n (\n {\n variant = 'info',\n title,\n description,\n icon,\n dismissible = false,\n dismissLabel,\n onDismiss,\n actions,\n sticky = false,\n fullWidth = false,\n className,\n children,\n 'data-testid': dataTestId = 'banner',\n ...props\n },\n ref,\n ) => {\n const [visible, setVisible] = React.useState(true);\n\n const handleDismiss = React.useCallback(() => {\n setVisible(false);\n onDismiss?.();\n }, [onDismiss]);\n\n const displayIcon = icon || getVariantIcon(variant);\n\n const { role: ariaRole, live: ariaLive } = resolveAriaAnnouncement(variant);\n\n if (!visible) {\n return null;\n }\n\n return (\n <StyledBanner\n ref={ref}\n variant={variant}\n sticky={sticky}\n fullWidth={fullWidth}\n className={className}\n role={ariaRole}\n aria-live={ariaLive}\n aria-atomic=\"true\"\n tabIndex={0}\n data-testid={dataTestId}\n {...props}\n >\n <BannerBody\n displayIcon={displayIcon}\n title={title}\n description={description}\n dataTestId={dataTestId}\n >\n {children}\n </BannerBody>\n\n <BannerTrailing\n actions={actions}\n variant={variant}\n dismissible={dismissible}\n onDismissClick={handleDismiss}\n dismissLabel={dismissLabel}\n dataTestId={dataTestId}\n />\n </StyledBanner>\n );\n },\n);\n\nBanner.displayName = 'Banner';"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Alert/Alert.metrics.ts"],"sourcesContent":["import type { AlertVariant } from './Alert.base';\nimport { alpha, darken, lighten } from '../../../tokens/color';\nimport { contrastText, type UiGreyStep, type UiTheme } from '../../../tokens/theme';\n\n/**\n * THE NUMBERS BOTH `Alert` RENDERERS DRAW WITH.\n *\n * `Alert.styles.tsx` / `Alert.tsx` (MUI) and `Alert.native.tsx` (React\n * Native) read this one table. Spacing is in SPACING UNITS where the web\n * writes `theme.spacing(n)`; durations are ms (the web turns them into `s`\n * through {@link seconds} so its emitted CSS is byte-for-byte what it was);\n * everything else is px, a ratio or an alpha. The hover, active and focus\n * numbers are here too although only the DOM can honour them — a number the\n * web draws with belongs in the table whether or not native can reach it.\n */\n\n/** The root: 12px corners, 14px 16px of padding, one 300ms curve for everything. */\nexport const ALERT_RADIUS_UNITS = 1.5;\nexport const ALERT_PADDING_UNITS = { vertical: 1.75, horizontal: 2 } as const;\nexport const ALERT_TRANSITION_MS = 300;\nexport const ALERT_EASING = 'cubic-bezier(0.4, 0, 0.2, 1)';\n\n/** Mount: the web's `fadeInScale` keyframes and the icon's `iconRotate`. */\nexport const FADE_IN = { ms: 300, scale: 0.95, lift: 10 } as const;\nexport const ICON_SPIN = { ms: 600, from: 0.8, mid: 1.1 } as const;\n/** Dismiss: MUI `Collapse` runs 300ms; `onClose` fires 200ms in. */\nexport const COLLAPSE_MS = 300;\nexport const CLOSE_DELAY_MS = 200;\n\n/** The message column: 8px between title, description and children; 0.95rem body on 1.5. */\nexport const MESSAGE_GAP_UNITS = 1;\nexport const MESSAGE_FONT_SIZE = 15.2;\nexport const MESSAGE_LINE_HEIGHT = 1.5;\n/** A control inside the message gets an edge: 4px above, a hairline at 0.45 of the hue. */\nexport const MESSAGE_BUTTON = { marginTopUnits: 0.5, borderAlpha: 0.45 } as const;\n\n/** The title: 1.05rem at 600; MUI's `AlertTitle` lifts it 2px; 4px below it when a description follows. */\nexport const TITLE = { fontSize: 16.8, fontWeight: 600, marginTop: -2, marginBottomUnits: 0.5 } as const;\n/** The description: 0.925rem, slightly faded. */\nexport const DESCRIPTION = { fontSize: 14.8, opacity: 0.9 } as const;\n\n/** The icon slot: MUI's 22px glyph at 0.9, 14px from the words, 2px down (MUI's 7px stays below). */\nexport const ICON_SLOT = { size: 22, opacity: 0.9, marginRightUnits: 1.75, paddingTopUnits: 0.25, paddingBottom: 7 } as const;\n/** The action slot: MUI's `4px 0 0 16px` with the 16px ours, pushed right, 8px into the padding. */\nexport const ACTION_SLOT = { paddingLeftUnits: 2, paddingTop: 4, marginRight: -8 } as const;\n/** The close button: MUI's small `IconButton` (5px padding, 18px glyph) at 0.7. */\nexport const CLOSE_BUTTON = { padding: 5, iconSize: 18, opacity: 0.7, hoverRotateDeg: 90, hoverScale: 1.1, washAlpha: 0.1 } as const;\n\n/**\n * A semantic Alert's surface and ink: an OPAQUE tint of the hue with a dark\n * ink of the same hue — MUI's own `standard` recipe, at its ratios. See\n * `semanticSurface` in `Alert.styles.tsx` for why not `alpha(main, 0.1)`.\n */\nexport const SEMANTIC_SURFACE = { tint: { light: 0.9, dark: 0.8 }, ink: 0.6, borderAlpha: 0.35 } as const;\n/** `glass`: 85% paper under a 20px blur, a 0.4 divider hairline. */\nexport const GLASS = { backgroundAlpha: 0.85, borderAlpha: 0.4, blur: 20, saturatePercent: 180 } as const;\n/** `gradient`: light→dark at 0.9 along 135°, a 0.2 white shimmer sweeping 1000px every 3s. */\nexport const GRADIENT = {\n angleDeg: 135,\n stopAlpha: 0.9,\n shimmerAlpha: 0.2,\n shimmerMs: 3000,\n shimmerTravel: 1000,\n hoverBrightness: 1.1,\n hoverLift: 2,\n hoverScale: 1.01,\n} as const;\n\n/** Emphasis: the glow's shadow and brightness, the pulse's wash. */\nexport const GLOW = { blur: 20, spread: 5, alpha: 0.3, brightness: 1.05 } as const;\nexport const PULSE = { alpha: 0.2, ms: 2000, spread: 10 } as const;\n\n/** Pointer states — DOM only, kept so the web derives them. */\nexport const HOVER = { lift: 3, scale: 1.01, shadowY: 8, shadowBlur: 20, shadowAlpha: 0.2, iconScale: 1.15, iconRotateDeg: 10 } as const;\nexport const ACTIVE = { lift: 1, scale: 0.99, ms: 100 } as const;\nexport const FOCUS = { ringWidth: 3, ringAlpha: 0.5, offset: 3, haloSpread: 6, haloAlpha: 0.1, ms: 200 } as const;\n\n/** `neutral` has no palette slot; the web has always drawn it from three greys. */\nexport const NEUTRAL_GREY: Record<'main' | 'light' | 'dark', UiGreyStep> = { main: 500, light: 300, dark: 700 };\n\n/** A duration as the CSS the web has always emitted: `2000` → `2s`, `300` → `0.3s`. */\nexport const seconds = (ms: number): string => `${ms / 1000}s`;\n\n/** The palette shape the web's `getColorFromTheme` returns — a main plus optional shades. */\nexport interface AlertPalette {\n main: string;\n light?: string;\n dark?: string;\n}\n\n/**\n * The renderer-free twin of the web's `getColorFromTheme`: a variant or\n * colour name to its palette, `neutral` from the greys, anything unknown\n * (`glass`, `gradient`) to `info`.\n */\nexport function alertPalette(theme: UiTheme, key: string | undefined): AlertPalette {\n switch (key) {\n case 'info':\n case 'success':\n case 'warning':\n case 'danger':\n case 'primary':\n case 'secondary':\n return theme.palette[key];\n case 'neutral':\n return {\n main: theme.palette.grey[NEUTRAL_GREY.main],\n light: theme.palette.grey[NEUTRAL_GREY.light],\n dark: theme.palette.grey[NEUTRAL_GREY.dark],\n };\n default:\n return theme.palette.info;\n }\n}\n\n/** What a variant paints: its fill, its ink, its hairline if any, and the colour of its glyph. */\nexport interface AlertSurface {\n backgroundColor: string;\n color: string;\n borderColor?: string;\n iconColor: string;\n}\n\nexport const SEMANTIC_VARIANTS: ReadonlySet<AlertVariant> = new Set(['info', 'success', 'warning', 'danger']);\n\n/** The opaque tint and same-hue ink, inverted for dark mode; the hairline keeps its alpha. */\nexport function semanticSurface(theme: UiTheme, palette: AlertPalette): AlertSurface {\n const dark = theme.mode === 'dark';\n return {\n backgroundColor: dark\n ? darken(palette.main, SEMANTIC_SURFACE.tint.dark)\n : lighten(palette.main, SEMANTIC_SURFACE.tint.light),\n color: dark ? lighten(palette.main, SEMANTIC_SURFACE.ink) : darken(palette.main, SEMANTIC_SURFACE.ink),\n borderColor: alpha(palette.main, SEMANTIC_SURFACE.borderAlpha),\n iconColor: palette.main,\n };\n}\n\nexport function glassSurface(theme: UiTheme): AlertSurface {\n return {\n backgroundColor: alpha(theme.palette.background.paper, GLASS.backgroundAlpha),\n color: theme.palette.text.primary,\n borderColor: alpha(theme.palette.divider, GLASS.borderAlpha),\n iconColor: theme.palette.primary.main,\n };\n}\n\n/** The gradient's two stops; a renderer with no gradient fill paints `from`. */\nexport function gradientStops(palette: AlertPalette): { from: string; to: string } {\n return {\n from: alpha(palette.light || palette.main, GRADIENT.stopAlpha),\n to: alpha(palette.dark || palette.main, GRADIENT.stopAlpha),\n };\n}\n\nexport function gradientSurface(palette: AlertPalette): AlertSurface {\n const ink = contrastText(palette.main);\n return { backgroundColor: gradientStops(palette).from, color: ink, iconColor: ink };\n}\n\n/** The surface for a variant, `glass` and `gradient` included. */\nexport function alertSurface(theme: UiTheme, variant: AlertVariant, palette: AlertPalette): AlertSurface {\n if (variant === 'glass') return glassSurface(theme);\n if (variant === 'gradient') return gradientSurface(palette);\n return semanticSurface(theme, palette);\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/components/data-display/Alert/Alert.tsx"],"sourcesContent":["import Close from '@mui/icons-material/Close';\nimport MuiAlert from '@mui/material/Alert/index.js';\nimport AlertTitle from '@mui/material/AlertTitle/index.js';\nimport Box from '@mui/material/Box/index.js';\nimport Collapse from '@mui/material/Collapse/index.js';\nimport IconButton from '@mui/material/IconButton/index.js';\nimport { alpha, styled } from '@mui/material/styles/index.js';\nimport React from 'react';\n\nimport { defaultAriaLive, resolveAlertProps, testIdFor } from './Alert.helpers';\nimport {\n ACTIVE,\n ALERT_EASING,\n ALERT_RADIUS_UNITS,\n ALERT_TRANSITION_MS,\n CLOSE_BUTTON,\n CLOSE_DELAY_MS,\n COLLAPSE_MS,\n DESCRIPTION,\n FADE_IN,\n FOCUS,\n HOVER,\n seconds,\n TITLE,\n} from './Alert.metrics';\nimport {\n alertEmphasisStyles,\n alertLayoutStyles,\n alertVariantStyles,\n fadeInScale,\n getColorFromTheme,\n getVariantIcon,\n} from './Alert.styles';\nimport type { AlertColor } from '@mui/material/Alert/index.js';\nimport type { AlertProps } from './Alert.types';\nimport { resolveTestId, withoutTestIdProps } from '../../../platform/test-id';\nimport { px } from '../../../tokens/theme';\n\nconst transition = `all ${seconds(ALERT_TRANSITION_MS)} ${ALERT_EASING}`;\n\n// Define animations\nconst StyledAlert = styled(MuiAlert, {\n shouldForwardProp: (prop) =>\n !['customVariant', 'customColor', 'glow', 'pulse', 'animate'].includes(prop as string),\n})<{\n customVariant?: string;\n customColor?: string;\n glow?: boolean;\n pulse?: boolean;\n animate?: boolean;\n}>(({ theme, customVariant, customColor, glow, pulse, animate }) => {\n const colorPalette = getColorFromTheme(theme, customColor || customVariant || 'info');\n\n return {\n borderRadius: theme.spacing(ALERT_RADIUS_UNITS),\n transition,\n position: 'relative',\n overflow: 'hidden',\n animation: animate ? `${fadeInScale} ${seconds(FADE_IN.ms)} ease-out` : 'none',\n willChange: 'transform, opacity',\n\n ...alertLayoutStyles(theme, colorPalette, animate),\n\n // Hover effects\n '&:hover': {\n transform: `translateY(-${HOVER.lift}px) scale(${HOVER.scale})`,\n boxShadow: `0 ${HOVER.shadowY}px ${HOVER.shadowBlur}px ${alpha(colorPalette.main, HOVER.shadowAlpha)}`,\n transition,\n\n '.MuiAlert-icon': {\n transform: `scale(${HOVER.iconScale}) rotate(${HOVER.iconRotateDeg}deg)`,\n },\n\n '&::before': {\n opacity: 1,\n },\n },\n\n // Active state\n '&:active': {\n transform: `translateY(-${ACTIVE.lift}px) scale(${ACTIVE.scale})`,\n transition: `transform ${seconds(ACTIVE.ms)} ease`,\n },\n\n // Focus styles for accessibility\n '&:focus-within': {\n outline: `${FOCUS.ringWidth}px solid ${alpha(colorPalette.main, FOCUS.ringAlpha)}`,\n outlineOffset: `${FOCUS.offset}px`,\n boxShadow: `0 0 0 ${FOCUS.haloSpread}px ${alpha(colorPalette.main, FOCUS.haloAlpha)}`,\n transition: `all ${seconds(FOCUS.ms)} ease`,\n },\n\n ...alertVariantStyles(theme, customVariant, colorPalette),\n ...alertEmphasisStyles(colorPalette, Boolean(glow), Boolean(pulse)),\n };\n});\n\n// glass and gradient are our own looks with no MUI severity of their own; both\n// borrow info's. danger is MUI's error.\nconst MUI_SEVERITY: Record<string, AlertColor> = {\n danger: 'error',\n glass: 'info',\n gradient: 'info',\n};\n\nconst toMuiSeverity = (variant: string): AlertColor =>\n MUI_SEVERITY[variant] ?? (variant as AlertColor);\n\nconst AlertContent: React.FC<{\n title?: AlertProps['title'];\n description?: AlertProps['description'];\n dataTestId?: string;\n children?: React.ReactNode;\n}> = ({ title, description, dataTestId, children }) => (\n <>\n {title && (\n <AlertTitle\n data-testid={testIdFor(dataTestId, 'title')}\n sx={{\n fontWeight: TITLE.fontWeight,\n fontSize: px(TITLE.fontSize),\n marginBottom: description ? TITLE.marginBottomUnits : 0,\n wordWrap: 'break-word',\n overflowWrap: 'break-word',\n wordBreak: 'break-word',\n }}\n >\n {title}\n </AlertTitle>\n )}\n {description && (\n <Box\n component=\"div\"\n data-testid={testIdFor(dataTestId, 'message')}\n sx={{\n opacity: DESCRIPTION.opacity,\n fontSize: px(DESCRIPTION.fontSize),\n wordWrap: 'break-word',\n overflowWrap: 'break-word',\n wordBreak: 'break-word',\n }}\n >\n {description}\n </Box>\n )}\n {children}\n </>\n);\n\nconst AlertCloseButton: React.FC<{ dataTestId?: string; label: string; onClose: () => void }> = ({\n dataTestId,\n label,\n onClose,\n}) => (\n <IconButton\n data-testid={testIdFor(dataTestId, 'close')}\n aria-label={label}\n color=\"inherit\"\n size=\"small\"\n onClick={onClose}\n sx={(theme) => ({\n transition,\n opacity: CLOSE_BUTTON.opacity,\n '&:hover': {\n transform: `rotate(${CLOSE_BUTTON.hoverRotateDeg}deg) scale(${CLOSE_BUTTON.hoverScale})`,\n opacity: 1,\n backgroundColor: alpha(theme.palette.action.hover, CLOSE_BUTTON.washAlpha),\n },\n '&:focus': {\n opacity: 1,\n outline: 'none',\n backgroundColor: alpha(theme.palette.action.focus, CLOSE_BUTTON.washAlpha),\n },\n })}\n >\n <Close fontSize=\"inherit\" />\n </IconButton>\n);\n\n/**\n * The dismiss button, or nothing.\n *\n * Reads `closeLabel` straight off the props rather than through the\n * `definedProps` merge, which would widen it to `string | undefined` and hand\n * the button an empty name. The UNION in `AlertProps` guarantees the label\n * wherever `closable` is true — that narrowing is gone inside the component,\n * which is why the fallback below exists and why it is unreachable.\n */\nfunction dismissButton(\n props: AlertProps,\n dataTestId: string | undefined,\n onClose: () => void,\n): React.JSX.Element | null {\n if (!props.closable) return null;\n return <AlertCloseButton dataTestId={dataTestId} label={props.closeLabel} onClose={onClose} />;\n}\n\nexport const Alert = React.forwardRef<HTMLDivElement, AlertProps>((alertProps, ref) => {\n const {\n variant,\n color,\n glow,\n pulse,\n icon,\n showIcon,\n onClose,\n title,\n description,\n children,\n animate,\n role,\n 'aria-atomic': ariaAtomic,\n ...others\n } = resolveAlertProps(alertProps);\n\n // Every spelling of the test id the shared contract allows, mapped to the\n // one the DOM reads and defaulted to `alert`; the native Alert does the same.\n const dataTestId = resolveTestId(others, 'alert');\n const props = withoutTestIdProps(others);\n\n // Depends on `variant`, so it cannot live in the static defaults above.\n const ariaLive = alertProps['aria-live'] ?? defaultAriaLive(variant);\n const [open, setOpen] = React.useState(true);\n const [isClosing, setIsClosing] = React.useState(false);\n\n const handleClose = () => {\n setIsClosing(true);\n window.setTimeout(() => {\n setOpen(false);\n onClose?.();\n }, CLOSE_DELAY_MS);\n };\n\n const severity = toMuiSeverity(variant);\n\n const displayIcon = showIcon ? (\n <Box component=\"span\" data-testid={testIdFor(dataTestId, 'icon')}>\n {icon || getVariantIcon(variant)}\n </Box>\n ) : (\n false\n );\n\n const content = (\n <AlertContent title={title} description={description} dataTestId={dataTestId}>\n {children}\n </AlertContent>\n );\n\n return (\n <Collapse in={open && !isClosing} timeout={COLLAPSE_MS}>\n <StyledAlert\n ref={ref}\n data-testid={dataTestId}\n severity={severity}\n customVariant={variant}\n customColor={color}\n glow={glow}\n pulse={pulse}\n animate={animate}\n icon={displayIcon}\n role={role}\n aria-live={ariaLive}\n aria-atomic={ariaAtomic}\n tabIndex={0}\n action={dismissButton(alertProps, dataTestId, handleClose)}\n {...props}\n >\n {content}\n </StyledAlert>\n </Collapse>\n );\n});\n\nAlert.displayName = 'Alert';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,WAAW;AAClB,OAAO,cAAc;AACrB,OAAO,gBAAgB;AACvB,OAAO,SAAS;AAChB,OAAO,cAAc;AACrB,OAAO,gBAAgB;AACvB,SAAS,OAAO,cAAc;AAC9B,OAAO,WAAW;AA2GhB,mBAEI,KAFJ;AA5EF,IAAM,aAAa,OAAO,QAAQ,mBAAmB,CAAC,IAAI,YAAY;AAGtE,IAAM,cAAc,OAAO,UAAU;AAAA,EACnC,mBAAmB,CAAC,SAClB,CAAC,CAAC,iBAAiB,eAAe,QAAQ,SAAS,SAAS,EAAE,SAAS,IAAc;AACzF,CAAC,EAME,CAAC,EAAE,OAAO,eAAe,aAAa,MAAM,OAAO,QAAQ,MAAM;AAClE,QAAM,eAAe,kBAAkB,OAAO,eAAe,iBAAiB,MAAM;AAEpF,SAAO;AAAA,IACL,cAAc,MAAM,QAAQ,kBAAkB;AAAA,IAC9C;AAAA,IACA,UAAU;AAAA,IACV,UAAU;AAAA,IACV,WAAW,UAAU,GAAG,WAAW,IAAI,QAAQ,QAAQ,EAAE,CAAC,cAAc;AAAA,IACxE,YAAY;AAAA,IAEZ,GAAG,kBAAkB,OAAO,cAAc,OAAO;AAAA;AAAA,IAGjD,WAAW;AAAA,MACT,WAAW,eAAe,MAAM,IAAI,aAAa,MAAM,KAAK;AAAA,MAC5D,WAAW,KAAK,MAAM,OAAO,MAAM,MAAM,UAAU,MAAM,MAAM,aAAa,MAAM,MAAM,WAAW,CAAC;AAAA,MACpG;AAAA,MAEA,kBAAkB;AAAA,QAChB,WAAW,SAAS,MAAM,SAAS,YAAY,MAAM,aAAa;AAAA,MACpE;AAAA,MAEA,aAAa;AAAA,QACX,SAAS;AAAA,MACX;AAAA,IACF;AAAA;AAAA,IAGA,YAAY;AAAA,MACV,WAAW,eAAe,OAAO,IAAI,aAAa,OAAO,KAAK;AAAA,MAC9D,YAAY,aAAa,QAAQ,OAAO,EAAE,CAAC;AAAA,IAC7C;AAAA;AAAA,IAGA,kBAAkB;AAAA,MAChB,SAAS,GAAG,MAAM,SAAS,YAAY,MAAM,aAAa,MAAM,MAAM,SAAS,CAAC;AAAA,MAChF,eAAe,GAAG,MAAM,MAAM;AAAA,MAC9B,WAAW,SAAS,MAAM,UAAU,MAAM,MAAM,aAAa,MAAM,MAAM,SAAS,CAAC;AAAA,MACnF,YAAY,OAAO,QAAQ,MAAM,EAAE,CAAC;AAAA,IACtC;AAAA,IAEA,GAAG,mBAAmB,OAAO,eAAe,YAAY;AAAA,IACxD,GAAG,oBAAoB,cAAc,QAAQ,IAAI,GAAG,QAAQ,KAAK,CAAC;AAAA,EACpE;AACF,CAAC;AAID,IAAM,eAA2C;AAAA,EAC/C,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,UAAU;AACZ;AAEA,IAAM,gBAAgB,CAAC,YACrB,aAAa,OAAO,KAAM;AAE5B,IAAM,eAKD,CAAC,EAAE,OAAO,aAAa,YAAY,SAAS,MAC/C,iCACG;AAAA,WACC;AAAA,IAAC;AAAA;AAAA,MACC,eAAa,UAAU,YAAY,OAAO;AAAA,MAC1C,IAAI;AAAA,QACF,YAAY,MAAM;AAAA,QAClB,UAAU,GAAG,MAAM,QAAQ;AAAA,QAC3B,cAAc,cAAc,MAAM,oBAAoB;AAAA,QACtD,UAAU;AAAA,QACV,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA,MAEC;AAAA;AAAA,EACH;AAAA,EAED,eACC;AAAA,IAAC;AAAA;AAAA,MACC,WAAU;AAAA,MACV,eAAa,UAAU,YAAY,SAAS;AAAA,MAC5C,IAAI;AAAA,QACF,SAAS,YAAY;AAAA,QACrB,UAAU,GAAG,YAAY,QAAQ;AAAA,QACjC,UAAU;AAAA,QACV,cAAc;AAAA,QACd,WAAW;AAAA,MACb;AAAA,MAEC;AAAA;AAAA,EACH;AAAA,EAED;AAAA,GACH;AAGF,IAAM,mBAA0F,CAAC;AAAA,EAC/F;AAAA,EACA;AAAA,EACA;AACF,MACE;AAAA,EAAC;AAAA;AAAA,IACC,eAAa,UAAU,YAAY,OAAO;AAAA,IAC1C,cAAY;AAAA,IACZ,OAAM;AAAA,IACN,MAAK;AAAA,IACL,SAAS;AAAA,IACT,IAAI,CAAC,WAAW;AAAA,MACd;AAAA,MACA,SAAS,aAAa;AAAA,MACtB,WAAW;AAAA,QACT,WAAW,UAAU,aAAa,cAAc,cAAc,aAAa,UAAU;AAAA,QACrF,SAAS;AAAA,QACT,iBAAiB,MAAM,MAAM,QAAQ,OAAO,OAAO,aAAa,SAAS;AAAA,MAC3E;AAAA,MACA,WAAW;AAAA,QACT,SAAS;AAAA,QACT,SAAS;AAAA,QACT,iBAAiB,MAAM,MAAM,QAAQ,OAAO,OAAO,aAAa,SAAS;AAAA,MAC3E;AAAA,IACF;AAAA,IAEA,8BAAC,SAAM,UAAS,WAAU;AAAA;AAC5B;AAYF,SAAS,cACP,OACA,YACA,SAC0B;AAC1B,MAAI,CAAC,MAAM,SAAU,QAAO;AAC5B,SAAO,oBAAC,oBAAiB,YAAwB,OAAO,MAAM,YAAY,SAAkB;AAC9F;AAEO,IAAM,QAAQ,MAAM,WAAuC,CAAC,YAAY,QAAQ;AACrF,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,GAAG;AAAA,EACL,IAAI,kBAAkB,UAAU;AAIhC,QAAM,aAAa,cAAc,QAAQ,OAAO;AAChD,QAAM,QAAQ,mBAAmB,MAAM;AAGvC,QAAM,WAAW,WAAW,WAAW,KAAK,gBAAgB,OAAO;AACnE,QAAM,CAAC,MAAM,OAAO,IAAI,MAAM,SAAS,IAAI;AAC3C,QAAM,CAAC,WAAW,YAAY,IAAI,MAAM,SAAS,KAAK;AAEtD,QAAM,cAAc,MAAM;AACxB,iBAAa,IAAI;AACjB,WAAO,WAAW,MAAM;AACtB,cAAQ,KAAK;AACb,gBAAU;AAAA,IACZ,GAAG,cAAc;AAAA,EACnB;AAEA,QAAM,WAAW,cAAc,OAAO;AAEtC,QAAM,cAAc,WAClB,oBAAC,OAAI,WAAU,QAAO,eAAa,UAAU,YAAY,MAAM,GAC5D,kBAAQ,eAAe,OAAO,GACjC,IAEA;AAGF,QAAM,UACJ,oBAAC,gBAAa,OAAc,aAA0B,YACnD,UACH;AAGF,SACE,oBAAC,YAAS,IAAI,QAAQ,CAAC,WAAW,SAAS,aACzC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA,eAAa;AAAA,MACb;AAAA,MACA,eAAe;AAAA,MACf,aAAa;AAAA,MACb;AAAA,MACA;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN;AAAA,MACA,aAAW;AAAA,MACX,eAAa;AAAA,MACb,UAAU;AAAA,MACV,QAAQ,cAAc,YAAY,YAAY,WAAW;AAAA,MACxD,GAAG;AAAA,MAEH;AAAA;AAAA,EACH,GACF;AAEJ,CAAC;AAED,MAAM,cAAc;","names":[]}