@12-apps/ui 6.53.0 → 6.54.1

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 (91) hide show
  1. package/dist/_internal/components/data-display/AlertDialog/AlertDialog.js +2 -1
  2. package/dist/_internal/components/data-display/AlertDialog/AlertDialog.parts.js +9 -0
  3. package/dist/_internal/components/data-display/AlertDialog/AlertDialog.parts.js.map +1 -0
  4. package/dist/_internal/components/data-display/CardKit/use-remove-confirm.js +7 -6
  5. package/dist/_internal/components/data-display/CardKit/use-row-confirm.js +7 -6
  6. package/dist/_internal/components/data-display/DataViews/DataViewsTableBase.js +9 -8
  7. package/dist/_internal/components/data-display/DataViews/ManageViewsDialog.js +7 -6
  8. package/dist/_internal/components/data-display/DataViews/data-views-table-parts.js +8 -7
  9. package/dist/_internal/components/data-display/InteractiveTooltip/InteractiveTooltip.js +2 -2
  10. package/dist/_internal/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.js +1 -1
  11. package/dist/_internal/components/data-display/Tooltip/Tooltip.js +1 -1
  12. package/dist/_internal/components/data-display/index.js +5 -4
  13. package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.js +4 -3
  14. package/dist/_internal/components/feedback/ConfirmAction/ConfirmAction.parts.js +3 -2
  15. package/dist/_internal/components/feedback/ConfirmAction/ConfirmButton.js +5 -4
  16. package/dist/_internal/components/feedback/ConfirmAction/with-confirmation.js +4 -3
  17. package/dist/_internal/components/feedback/index.js +6 -5
  18. package/dist/_internal/components/index.js +14 -13
  19. package/dist/_internal/components/layout/SettingCard/SettingCard.js +4 -4
  20. package/dist/_internal/components/layout/SettingCard/SettingCard.summary.js +3 -3
  21. package/dist/_internal/components/layout/SettingCard/SettingGroup.js +5 -5
  22. package/dist/_internal/components/layout/SettingCard/SettingSwitch.js +4 -4
  23. package/dist/_internal/components/layout/SettingCard/SettingToggle.js +5 -5
  24. package/dist/_internal/components/layout/index.js +7 -7
  25. package/dist/{chunk-QOLIBVGS.js → chunk-3CIIB74Y.js} +2 -2
  26. package/dist/{chunk-OG6LYQ7C.js → chunk-4PFKMNLP.js} +8 -68
  27. package/dist/chunk-4PFKMNLP.js.map +1 -0
  28. package/dist/{chunk-TQCQCW2P.js → chunk-524PRSSD.js} +3 -3
  29. package/dist/chunk-524PRSSD.js.map +1 -0
  30. package/dist/{chunk-Y5QDXG5N.js → chunk-6WNMVWAV.js} +2 -2
  31. package/dist/{chunk-OT3KFZRE.js → chunk-7WWPKA7Y.js} +2 -2
  32. package/dist/chunk-AET54GT6.js +99 -0
  33. package/dist/chunk-AET54GT6.js.map +1 -0
  34. package/dist/{chunk-ZXEDKRRT.js → chunk-ECVNRLKD.js} +2 -2
  35. package/dist/{chunk-JUHNEYCH.js → chunk-GCH7CLM4.js} +2 -2
  36. package/dist/{chunk-HCEJT65A.js → chunk-HKD4TDZP.js} +2 -2
  37. package/dist/{chunk-3GVMUASU.js → chunk-HV7HIMMY.js} +2 -2
  38. package/dist/chunk-IQQX3NDO.js +96 -0
  39. package/dist/chunk-IQQX3NDO.js.map +1 -0
  40. package/dist/{chunk-OESAYTNG.js → chunk-JDVHNDY3.js} +2 -2
  41. package/dist/{chunk-TYBZ4XZZ.js → chunk-KRIPQUFB.js} +27 -32
  42. package/dist/chunk-KRIPQUFB.js.map +1 -0
  43. package/dist/{chunk-4WCDMCIS.js → chunk-PI3GPLE2.js} +2 -2
  44. package/dist/{chunk-N4R6T5K7.js → chunk-PKSU3RBT.js} +2 -2
  45. package/dist/{chunk-IRNKHHQM.js → chunk-QKMXMBUK.js} +2 -2
  46. package/dist/{chunk-FWPFCK4D.js → chunk-S6G3CJFP.js} +2 -2
  47. package/dist/{chunk-BJZOBG24.js → chunk-UJHXEOU3.js} +2 -2
  48. package/dist/{chunk-U4QUDFPJ.js → chunk-VKVOP4OK.js} +2 -2
  49. package/dist/data-display/AlertDialog.js +2 -1
  50. package/dist/data-display/CardKit.js +8 -7
  51. package/dist/data-display/DataViews.js +9 -8
  52. package/dist/data-display/InteractiveTooltip.js +2 -2
  53. package/dist/data-display/Tooltip.js +1 -1
  54. package/dist/feedback/ConfirmAction.js +6 -5
  55. package/dist/layout/SettingCard.js +7 -7
  56. package/dist/types/components/data-display/AlertDialog/AlertDialog.d.ts.map +1 -1
  57. package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts +13 -0
  58. package/dist/types/components/data-display/AlertDialog/AlertDialog.parts.d.ts.map +1 -0
  59. package/dist/types/components/data-display/AlertDialog/AlertDialog.types.d.ts +16 -3
  60. package/dist/types/components/data-display/AlertDialog/AlertDialog.types.d.ts.map +1 -1
  61. package/dist/types/components/data-display/InteractiveTooltip/InteractiveTooltip.d.ts +2 -2
  62. package/dist/types/components/data-display/InteractiveTooltip/InteractiveTooltip.d.ts.map +1 -1
  63. package/dist/types/components/data-display/Tooltip/Tooltip.d.ts +2 -2
  64. package/dist/types/components/data-display/Tooltip/Tooltip.d.ts.map +1 -1
  65. package/package.json +1 -1
  66. package/src/components/data-display/AlertDialog/AlertDialog.md +30 -0
  67. package/src/components/data-display/AlertDialog/AlertDialog.parts.tsx +180 -0
  68. package/src/components/data-display/AlertDialog/AlertDialog.tsx +5 -85
  69. package/src/components/data-display/AlertDialog/AlertDialog.types.ts +17 -3
  70. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.styles.ts +2 -2
  71. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.tsx +102 -99
  72. package/src/components/data-display/Tooltip/Tooltip.tsx +64 -56
  73. package/dist/chunk-OG6LYQ7C.js.map +0 -1
  74. package/dist/chunk-TQCQCW2P.js.map +0 -1
  75. package/dist/chunk-TYBZ4XZZ.js.map +0 -1
  76. package/dist/chunk-XM7QCNZQ.js +0 -110
  77. package/dist/chunk-XM7QCNZQ.js.map +0 -1
  78. /package/dist/{chunk-QOLIBVGS.js.map → chunk-3CIIB74Y.js.map} +0 -0
  79. /package/dist/{chunk-Y5QDXG5N.js.map → chunk-6WNMVWAV.js.map} +0 -0
  80. /package/dist/{chunk-OT3KFZRE.js.map → chunk-7WWPKA7Y.js.map} +0 -0
  81. /package/dist/{chunk-ZXEDKRRT.js.map → chunk-ECVNRLKD.js.map} +0 -0
  82. /package/dist/{chunk-JUHNEYCH.js.map → chunk-GCH7CLM4.js.map} +0 -0
  83. /package/dist/{chunk-HCEJT65A.js.map → chunk-HKD4TDZP.js.map} +0 -0
  84. /package/dist/{chunk-3GVMUASU.js.map → chunk-HV7HIMMY.js.map} +0 -0
  85. /package/dist/{chunk-OESAYTNG.js.map → chunk-JDVHNDY3.js.map} +0 -0
  86. /package/dist/{chunk-4WCDMCIS.js.map → chunk-PI3GPLE2.js.map} +0 -0
  87. /package/dist/{chunk-N4R6T5K7.js.map → chunk-PKSU3RBT.js.map} +0 -0
  88. /package/dist/{chunk-IRNKHHQM.js.map → chunk-QKMXMBUK.js.map} +0 -0
  89. /package/dist/{chunk-FWPFCK4D.js.map → chunk-S6G3CJFP.js.map} +0 -0
  90. /package/dist/{chunk-BJZOBG24.js.map → chunk-UJHXEOU3.js.map} +0 -0
  91. /package/dist/{chunk-U4QUDFPJ.js.map → chunk-VKVOP4OK.js.map} +0 -0
@@ -1,22 +1,19 @@
1
1
  import Close from '@mui/icons-material/Close';
2
2
  import Error from '@mui/icons-material/Error';
3
3
  import Info from '@mui/icons-material/Info';
4
- import type { ButtonProps } from '@mui/material/Button/index.js';
5
- import Button from '@mui/material/Button/index.js';
6
- import CircularProgress from '@mui/material/CircularProgress/index.js';
7
4
  import Dialog from '@mui/material/Dialog/index.js';
8
- import DialogActions from '@mui/material/DialogActions/index.js';
9
5
  import DialogContent from '@mui/material/DialogContent/index.js';
10
6
  import DialogContentText from '@mui/material/DialogContentText/index.js';
11
7
  import DialogTitle from '@mui/material/DialogTitle/index.js';
12
8
  import IconButton from '@mui/material/IconButton/index.js';
13
9
  import Typography from '@mui/material/Typography/index.js';
14
- import { alpha, keyframes, styled, useTheme } from '@mui/material/styles/index.js';
10
+ import { alpha, keyframes, styled } from '@mui/material/styles/index.js';
15
11
  import type { Theme } from '@mui/material/styles/index.js';
16
12
  import React from 'react';
17
13
 
18
14
  import { neutralTones } from '../../../tokens/ink';
19
15
 
16
+ import { AlertDialogFooter } from './AlertDialog.parts';
20
17
  import type { AlertDialogProps } from './AlertDialog.types';
21
18
  import { rem, rems } from '../../../tokens/relative';
22
19
 
@@ -140,11 +137,6 @@ const StyledDialogContent = styled(DialogContent)(({ theme }) => ({
140
137
  paddingBottom: theme.spacing(2),
141
138
  }));
142
139
 
143
- const StyledDialogActions = styled(DialogActions)(({ theme }) => ({
144
- padding: theme.spacing(2, 3, 3, 3),
145
- gap: theme.spacing(1),
146
- }));
147
-
148
140
  const CloseButton = styled(IconButton)(({ theme }) => ({
149
141
  position: 'absolute',
150
142
  right: theme.spacing(1),
@@ -208,11 +200,6 @@ function ariaSlotProps(
208
200
  };
209
201
  }
210
202
 
211
- /** Both variants render a filled confirm; only the colour distinguishes them. */
212
- const CONFIRM_BUTTON_VARIANT: ButtonProps['variant'] = 'contained';
213
- const confirmButtonColor = (variant: AlertDialogProps['variant']): 'error' | 'primary' =>
214
- variant === 'destructive' ? 'error' : 'primary';
215
-
216
203
  /** Title row: the variant icon (unless explicitly suppressed with `null`) + the title. */
217
204
  function AlertDialogHeader({
218
205
  title,
@@ -238,70 +225,6 @@ function AlertDialogHeader({
238
225
  );
239
226
  }
240
227
 
241
- /** Action row: the optional cancel button and the (variant-coloured) confirm. */
242
- function AlertDialogFooter({
243
- variant,
244
- cancelText = 'Cancel',
245
- confirmText = 'Confirm',
246
- showCancel = true,
247
- loading = false,
248
- confirmDisabled = false,
249
- initialFocus = 'confirm',
250
- onCancel,
251
- onConfirm,
252
- dataTestId,
253
- }: Pick<
254
- AlertDialogProps,
255
- | 'variant'
256
- | 'cancelText'
257
- | 'confirmText'
258
- | 'showCancel'
259
- | 'loading'
260
- | 'confirmDisabled'
261
- | 'initialFocus'
262
- > & {
263
- onCancel: () => void;
264
- onConfirm: () => void;
265
- dataTestId: string;
266
- }): React.ReactElement {
267
- const theme = useTheme();
268
- return (
269
- <StyledDialogActions data-testid={`${dataTestId}-actions`}>
270
- {showCancel && (
271
- <Button
272
- onClick={onCancel}
273
- variant="outlined"
274
- color="inherit"
275
- disabled={loading}
276
- autoFocus={initialFocus === 'cancel'}
277
- data-testid={`${dataTestId}-cancel-button`}
278
- >
279
- {cancelText}
280
- </Button>
281
- )}
282
- <Button
283
- onClick={onConfirm}
284
- variant={CONFIRM_BUTTON_VARIANT}
285
- color={confirmButtonColor(variant)}
286
- disabled={confirmDisabled || loading}
287
- startIcon={
288
- loading ? (
289
- <CircularProgress
290
- size={rem(theme, 16)}
291
- color="inherit"
292
- data-testid={`${dataTestId}-loading-spinner`}
293
- />
294
- ) : undefined
295
- }
296
- autoFocus={initialFocus !== 'cancel'}
297
- data-testid={`${dataTestId}-confirm-button`}
298
- >
299
- {confirmText}
300
- </Button>
301
- </StyledDialogActions>
302
- );
303
- }
304
-
305
228
  export const AlertDialog = React.forwardRef<HTMLDivElement, AlertDialogProps>(
306
229
  ({
307
230
  variant = 'default',
@@ -318,6 +241,7 @@ export const AlertDialog = React.forwardRef<HTMLDivElement, AlertDialogProps>(
318
241
  loading,
319
242
  confirmDisabled,
320
243
  initialFocus,
244
+ emphasis,
321
245
  children,
322
246
  onClose,
323
247
  'data-testid': dataTestId = 'alert-dialog',
@@ -348,12 +272,7 @@ export const AlertDialog = React.forwardRef<HTMLDivElement, AlertDialogProps>(
348
272
  <Close />
349
273
  </CloseButton>
350
274
 
351
- <AlertDialogHeader
352
- title={title}
353
- icon={icon}
354
- variant={variant}
355
- dataTestId={dataTestId}
356
- />
275
+ <AlertDialogHeader title={title} icon={icon} variant={variant} dataTestId={dataTestId} />
357
276
 
358
277
  <StyledDialogContent data-testid={`${dataTestId}-content`}>
359
278
  {description && (
@@ -375,6 +294,7 @@ export const AlertDialog = React.forwardRef<HTMLDivElement, AlertDialogProps>(
375
294
  loading={loading}
376
295
  confirmDisabled={confirmDisabled}
377
296
  initialFocus={initialFocus}
297
+ emphasis={emphasis}
378
298
  onCancel={handleCancel}
379
299
  onConfirm={() => onConfirm?.()}
380
300
  dataTestId={dataTestId}
@@ -68,12 +68,26 @@ export interface AlertDialogProps extends Omit<MuiDialogProps, 'variant'> {
68
68
  confirmDisabled?: boolean;
69
69
 
70
70
  /**
71
- * Which button owns focus when the dialog opens. Defaults to `confirm`, which
72
- * is right for an acknowledgement; a destructive dialog should pass `cancel`
73
- * so a stray Enter on the trigger cannot complete the action.
71
+ * Which button owns focus when the dialog opens. Defaults to the emphasised
72
+ * one — `confirm`, unless `emphasis="cancel"` — which is right for an
73
+ * acknowledgement; a destructive dialog should pass `cancel` so a stray Enter
74
+ * on the trigger cannot complete the action.
74
75
  */
75
76
  initialFocus?: 'confirm' | 'cancel';
76
77
 
78
+ /**
79
+ * Which button is the filled, primary one. Defaults to `confirm`. Pass
80
+ * `cancel` when backing out is the action to steer toward — a "discard what
81
+ * you wrote?" question, where the confirm is the loss: cancel is then filled
82
+ * and takes the primary slot (right, or on top once the row stacks), and the
83
+ * confirm steps down to the neutral outline cancel normally wears — primary
84
+ * and neutral, whatever the variant. The two buttons then grow to share the
85
+ * row, so once it stacks each takes its full width; focus opens on the cancel
86
+ * unless `initialFocus` says otherwise. With `showCancel={false}` it has no
87
+ * effect: the confirm keeps its default, filled look.
88
+ */
89
+ emphasis?: 'confirm' | 'cancel';
90
+
77
91
  /**
78
92
  * Optional data-testid attribute for testing
79
93
  */
@@ -34,7 +34,7 @@ export const variantStyles = (theme: Theme, variant?: string): CSSObject => {
34
34
  switch (variant) {
35
35
  case 'default':
36
36
  return {
37
- backgroundColor: alpha(neutralTones(theme).inverseSurface, 0.92),
37
+ backgroundColor: neutralTones(theme).inverseSurface,
38
38
  color: absoluteInk(theme).white,
39
39
  boxShadow: `${rems(theme, 0, 4, 12)} ${shadowInk(theme, 0.3)}`,
40
40
  };
@@ -98,6 +98,6 @@ export const arrowColor = (theme: Theme, variant?: string): string => {
98
98
  case 'dark':
99
99
  return absoluteInk(theme).black;
100
100
  default:
101
- return alpha(neutralTones(theme).inverseSurface, 0.92);
101
+ return neutralTones(theme).inverseSurface;
102
102
  }
103
103
  };
@@ -1,124 +1,127 @@
1
- import MuiTooltip from '@mui/material/Tooltip/index.js';
2
- import { styled } from '@mui/material/styles/index.js';
3
- import React from 'react';
1
+ import MuiTooltip from "@mui/material/Tooltip/index.js";
2
+ import type { CSSObject, Theme } from "@mui/material/styles/index.js";
3
+ import React from "react";
4
4
 
5
- import { resolveTooltipProps, useHoverOpen, usePinnedTooltip } from './InteractiveTooltip.hooks';
5
+ import {
6
+ resolveTooltipProps,
7
+ useHoverOpen,
8
+ usePinnedTooltip,
9
+ } from "./InteractiveTooltip.hooks";
6
10
  import {
7
11
  arrowColor,
8
12
  emphasisStyles,
9
13
  getSizeStyles,
10
14
  tooltipCap,
11
15
  variantStyles,
12
- } from './InteractiveTooltip.styles';
13
- import type { InteractiveTooltipProps } from './InteractiveTooltip.types';
14
-
15
- const StyledTooltip = styled(MuiTooltip, {
16
- shouldForwardProp: (prop) =>
17
- !['customVariant', 'customSize', 'glow', 'pulse'].includes(prop as string),
18
- })<{
19
- customVariant?: string;
20
- customSize?: string;
21
- glow?: boolean;
22
- pulse?: boolean;
23
- }>(({ theme, customVariant, customSize, glow, pulse }) => {
24
- const sizeStyles = getSizeStyles(customSize);
16
+ } from "./InteractiveTooltip.styles";
17
+ import type { InteractiveTooltipProps } from "./InteractiveTooltip.types";
25
18
 
26
- return {
27
- '& .MuiTooltip-tooltip': {
19
+ /**
20
+ * The bubble's own look, applied to the BUBBLE (FUT-3093) — see `Tooltip`'s
21
+ * `bubbleSx`: a `& .MuiTooltip-tooltip` rule on the root never reached the
22
+ * portalled bubble, so the variants had never been on screen.
23
+ */
24
+ const bubbleSx =
25
+ (
26
+ variant: string | undefined,
27
+ size: string | undefined,
28
+ glow: boolean | undefined,
29
+ pulse: boolean | undefined,
30
+ maxWidth: number | undefined,
31
+ ) =>
32
+ (theme: Theme): CSSObject => {
33
+ const sizeStyles = getSizeStyles(size);
34
+ return {
28
35
  borderRadius: theme.spacing(1),
29
36
  fontSize: sizeStyles.fontSize(theme),
30
37
  padding: sizeStyles.padding(theme),
31
38
  fontWeight: 500,
32
- transition: 'all 0.3s ease',
33
- position: 'relative',
34
- overflow: 'hidden',
35
- ...variantStyles(theme, customVariant),
39
+ transition: "all 0.3s ease",
40
+ position: "relative",
41
+ maxWidth: tooltipCap(maxWidth)(theme),
42
+ ...variantStyles(theme, variant),
36
43
  ...emphasisStyles(theme, glow, pulse),
37
- },
38
-
39
- '& .MuiTooltip-arrow': {
40
- color: arrowColor(theme, customVariant),
41
- },
44
+ };
42
45
  };
43
- });
44
46
 
45
- export const InteractiveTooltip = React.forwardRef<HTMLDivElement, InteractiveTooltipProps>(
46
- (rawProps, ref) => {
47
- const {
48
- variant,
49
- size,
50
- glow,
51
- pulse,
52
- maxWidth,
53
- dataTestId,
54
- hoverContent,
55
- pinnedContent,
56
- clickable,
57
- onPin,
58
- onUnpin,
59
- className,
60
- children,
61
- onOpen,
62
- onClose,
63
- ...props
64
- } = resolveTooltipProps(rawProps);
47
+ export const InteractiveTooltip = React.forwardRef<
48
+ HTMLDivElement,
49
+ InteractiveTooltipProps
50
+ >((rawProps, ref) => {
51
+ const {
52
+ variant,
53
+ size,
54
+ glow,
55
+ pulse,
56
+ maxWidth,
57
+ dataTestId,
58
+ hoverContent,
59
+ pinnedContent,
60
+ clickable,
61
+ onPin,
62
+ onUnpin,
63
+ className,
64
+ children,
65
+ onOpen,
66
+ onClose,
67
+ ...props
68
+ } = resolveTooltipProps(rawProps);
65
69
 
66
- const { isPinned, isControlledOpen, wrapperRef, handleClick } = usePinnedTooltip({
70
+ const { isPinned, isControlledOpen, wrapperRef, handleClick } =
71
+ usePinnedTooltip({
67
72
  clickable,
68
73
  onPin,
69
74
  onUnpin,
70
75
  });
71
- const hover = useHoverOpen(isPinned, onOpen, onClose);
76
+ const hover = useHoverOpen(isPinned, onOpen, onClose);
72
77
 
73
- // Determine which content to show
74
- const tooltipContent = isPinned ? pinnedContent : hoverContent;
78
+ // Determine which content to show
79
+ const tooltipContent = isPinned ? pinnedContent : hoverContent;
75
80
 
76
- // Child with click handler and ref
77
- const childElement = children as React.ReactElement<{
78
- onClick?: (e: React.MouseEvent) => void;
79
- 'data-testid'?: string;
80
- }>;
81
+ // Child with click handler and ref
82
+ const childElement = children as React.ReactElement<{
83
+ onClick?: (e: React.MouseEvent) => void;
84
+ "data-testid"?: string;
85
+ }>;
81
86
 
82
- const childWithProps = React.cloneElement(childElement, {
83
- onClick: (e: React.MouseEvent) => {
84
- handleClick(e);
85
- // Call original onClick if it exists
86
- childElement.props.onClick?.(e);
87
- },
88
- 'data-testid': dataTestId ? `${dataTestId}-trigger` : undefined,
89
- });
87
+ const childWithProps = React.cloneElement(childElement, {
88
+ onClick: (e: React.MouseEvent) => {
89
+ handleClick(e);
90
+ // Call original onClick if it exists
91
+ childElement.props.onClick?.(e);
92
+ },
93
+ "data-testid": dataTestId ? `${dataTestId}-trigger` : undefined,
94
+ });
90
95
 
91
- return (
92
- <div ref={wrapperRef} className={className}>
93
- <StyledTooltip
94
- ref={ref}
95
- customVariant={variant}
96
- customSize={size}
97
- glow={glow}
98
- pulse={pulse}
99
- title={tooltipContent}
100
- open={isPinned ? isControlledOpen : hover.open}
101
- onOpen={hover.onOpen}
102
- onClose={hover.onClose}
103
- enterDelay={isPinned ? 0 : 100}
104
- leaveDelay={0}
105
- disableHoverListener={isPinned}
106
- disableFocusListener={isPinned}
107
- disableTouchListener={isPinned}
108
- slotProps={{
109
- tooltip: {
110
- sx: { maxWidth: tooltipCap(maxWidth) },
111
- role: 'tooltip',
112
- ...(dataTestId && { 'data-testid': `${dataTestId}-content` }),
113
- },
114
- }}
115
- {...props}
116
- >
117
- {childWithProps}
118
- </StyledTooltip>
119
- </div>
120
- );
121
- },
122
- );
96
+ return (
97
+ <div ref={wrapperRef} className={className}>
98
+ <MuiTooltip
99
+ ref={ref}
100
+ title={tooltipContent}
101
+ open={isPinned ? isControlledOpen : hover.open}
102
+ onOpen={hover.onOpen}
103
+ onClose={hover.onClose}
104
+ enterDelay={isPinned ? 0 : 100}
105
+ leaveDelay={0}
106
+ disableHoverListener={isPinned}
107
+ disableFocusListener={isPinned}
108
+ disableTouchListener={isPinned}
109
+ slotProps={{
110
+ tooltip: {
111
+ sx: bubbleSx(variant, size, glow, pulse, maxWidth),
112
+ role: "tooltip",
113
+ ...(dataTestId && { "data-testid": `${dataTestId}-content` }),
114
+ },
115
+ arrow: {
116
+ sx: (theme: Theme) => ({ color: arrowColor(theme, variant) }),
117
+ },
118
+ }}
119
+ {...props}
120
+ >
121
+ {childWithProps}
122
+ </MuiTooltip>
123
+ </div>
124
+ );
125
+ });
123
126
 
124
- InteractiveTooltip.displayName = 'InteractiveTooltip';
127
+ InteractiveTooltip.displayName = "InteractiveTooltip";
@@ -1,11 +1,11 @@
1
- import MuiTooltip from '@mui/material/Tooltip/index.js';
2
- import { alpha, keyframes, styled } from '@mui/material/styles/index.js';
3
- import type { CSSObject, Theme } from '@mui/material/styles/index.js';
4
- import React from 'react';
1
+ import MuiTooltip from "@mui/material/Tooltip/index.js";
2
+ import { alpha, keyframes } from "@mui/material/styles/index.js";
3
+ import type { CSSObject, Theme } from "@mui/material/styles/index.js";
4
+ import React from "react";
5
5
 
6
- import type { TooltipProps } from './Tooltip.types';
7
- import { absoluteInk, neutralTones } from '../../../tokens/ink';
8
- import { rem, rems, sxRem } from '../../../tokens/relative';
6
+ import type { TooltipProps } from "./Tooltip.types";
7
+ import { absoluteInk, neutralTones } from "../../../tokens/ink";
8
+ import { rem, rems, sxRem } from "../../../tokens/relative";
9
9
 
10
10
  // Define pulse animation
11
11
  const pulseAnimation = keyframes`
@@ -36,24 +36,24 @@ const getSizeStyles = (
36
36
 
37
37
  const variantStyles = (theme: Theme, variant?: string): CSSObject => {
38
38
  switch (variant) {
39
- case 'default':
39
+ case "default":
40
40
  return {
41
- backgroundColor: alpha(neutralTones(theme).inverseSurface, 0.9),
41
+ backgroundColor: neutralTones(theme).inverseSurface,
42
42
  color: absoluteInk(theme).white,
43
43
  };
44
- case 'dark':
44
+ case "dark":
45
45
  return {
46
46
  backgroundColor: neutralTones(theme).inverseSurface,
47
47
  color: absoluteInk(theme).white,
48
48
  };
49
- case 'light':
49
+ case "light":
50
50
  return {
51
51
  backgroundColor: absoluteInk(theme).white,
52
52
  color: theme.palette.text.primary,
53
53
  border: `1px solid ${alpha(theme.palette.divider, 0.2)}`,
54
54
  boxShadow: theme.shadows[4],
55
55
  };
56
- case 'glass':
56
+ case "glass":
57
57
  return {
58
58
  backgroundColor: alpha(theme.palette.background.paper, 0.1),
59
59
  backdropFilter: `blur(${rem(theme, 20)})`,
@@ -67,10 +67,14 @@ const variantStyles = (theme: Theme, variant?: string): CSSObject => {
67
67
 
68
68
  // glow and pulse are independent flags. The three combinations used to be spelled
69
69
  // out one by one, but each is just the union of whichever flags are set.
70
- const emphasisStyles = (theme: Theme, glow?: boolean, pulse?: boolean): CSSObject => ({
70
+ const emphasisStyles = (
71
+ theme: Theme,
72
+ glow?: boolean,
73
+ pulse?: boolean,
74
+ ): CSSObject => ({
71
75
  ...(glow && {
72
76
  boxShadow: `${rems(theme, 0, 0, 15, 3)} ${alpha(theme.palette.primary.main, 0.4)} !important`,
73
- filter: 'brightness(1.05)',
77
+ filter: "brightness(1.05)",
74
78
  }),
75
79
  ...(pulse && {
76
80
  animation: `${pulseAnimation} 2s infinite`,
@@ -79,46 +83,48 @@ const emphasisStyles = (theme: Theme, glow?: boolean, pulse?: boolean): CSSObjec
79
83
 
80
84
  const arrowColor = (theme: Theme, variant?: string): string => {
81
85
  switch (variant) {
82
- case 'light':
86
+ case "light":
83
87
  return absoluteInk(theme).white;
84
- case 'glass':
88
+ case "glass":
85
89
  return alpha(theme.palette.background.paper, 0.1);
86
- case 'dark':
90
+ case "dark":
87
91
  return neutralTones(theme).inverseSurface;
88
92
  default:
89
- return alpha(neutralTones(theme).inverseSurface, 0.9);
93
+ return neutralTones(theme).inverseSurface;
90
94
  }
91
95
  };
92
96
 
93
- const StyledTooltip = styled(MuiTooltip, {
94
- shouldForwardProp: (prop) =>
95
- !['customVariant', 'customSize', 'glow', 'pulse'].includes(prop as string),
96
- })<{
97
- customVariant?: string;
98
- customSize?: string;
99
- glow?: boolean;
100
- pulse?: boolean;
101
- }>(({ theme, customVariant, customSize, glow, pulse }) => {
102
- const sizeStyles = getSizeStyles(customSize);
103
-
104
- return {
105
- '& .MuiTooltip-tooltip': {
97
+ /**
98
+ * The bubble's own look, applied to the BUBBLE (FUT-3093).
99
+ *
100
+ * It used to sit on a `styled(MuiTooltip)` root as `& .MuiTooltip-tooltip`, but
101
+ * MUI renders the bubble in a portal, outside that root — so none of it ever
102
+ * reached the screen, and every tooltip wore MUI's see-through grey instead of
103
+ * the variant it asked for. `slotProps.tooltip.sx` lands on the bubble itself.
104
+ * No `overflow: hidden`: now that it applies, it would clip an `arrow`.
105
+ */
106
+ const bubbleSx =
107
+ (
108
+ variant: string,
109
+ size: string,
110
+ glow: boolean,
111
+ pulse: boolean,
112
+ maxWidth: number | undefined,
113
+ ) =>
114
+ (theme: Theme): CSSObject => {
115
+ const sizeStyles = getSizeStyles(size);
116
+ return {
106
117
  borderRadius: theme.spacing(1),
107
118
  fontSize: sizeStyles.fontSize(theme),
108
119
  padding: sizeStyles.padding(theme),
109
120
  fontWeight: 500,
110
- transition: 'all 0.3s ease',
111
- position: 'relative',
112
- overflow: 'hidden',
113
- ...variantStyles(theme, customVariant),
121
+ transition: "all 0.3s ease",
122
+ position: "relative",
123
+ maxWidth: tooltipCap(maxWidth)(theme),
124
+ ...variantStyles(theme, variant),
114
125
  ...emphasisStyles(theme, glow, pulse),
115
- },
116
-
117
- '& .MuiTooltip-arrow': {
118
- color: arrowColor(theme, customVariant),
119
- },
126
+ };
120
127
  };
121
- });
122
128
 
123
129
  /**
124
130
  * The tooltip's cap: 300 design px unless the caller says otherwise. `sx` has
@@ -134,8 +140,8 @@ const tooltipCap =
134
140
  export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
135
141
  (
136
142
  {
137
- variant = 'default',
138
- size = 'md',
143
+ variant = "default",
144
+ size = "md",
139
145
  glow = false,
140
146
  pulse = false,
141
147
  maxWidth,
@@ -146,18 +152,17 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
146
152
  ref,
147
153
  ) => {
148
154
  const childWithTestId = dataTestId
149
- ? React.cloneElement(children as React.ReactElement<{ 'data-testid'?: string }>, {
150
- 'data-testid': `${dataTestId}-trigger`,
151
- })
155
+ ? React.cloneElement(
156
+ children as React.ReactElement<{ "data-testid"?: string }>,
157
+ {
158
+ "data-testid": `${dataTestId}-trigger`,
159
+ },
160
+ )
152
161
  : children;
153
162
 
154
163
  return (
155
- <StyledTooltip
164
+ <MuiTooltip
156
165
  ref={ref}
157
- customVariant={variant}
158
- customSize={size}
159
- glow={glow}
160
- pulse={pulse}
161
166
  enterDelay={0}
162
167
  leaveDelay={0}
163
168
  disableHoverListener={false}
@@ -165,17 +170,20 @@ export const Tooltip = React.forwardRef<HTMLDivElement, TooltipProps>(
165
170
  disableTouchListener={false}
166
171
  slotProps={{
167
172
  tooltip: {
168
- sx: { maxWidth: tooltipCap(maxWidth) },
169
- role: 'tooltip',
170
- ...(dataTestId && { 'data-testid': `${dataTestId}-content` }),
173
+ sx: bubbleSx(variant, size, glow, pulse, maxWidth),
174
+ role: "tooltip",
175
+ ...(dataTestId && { "data-testid": `${dataTestId}-content` }),
176
+ },
177
+ arrow: {
178
+ sx: (theme: Theme) => ({ color: arrowColor(theme, variant) }),
171
179
  },
172
180
  }}
173
181
  {...props}
174
182
  >
175
183
  {childWithTestId}
176
- </StyledTooltip>
184
+ </MuiTooltip>
177
185
  );
178
186
  },
179
187
  );
180
188
 
181
- Tooltip.displayName = 'Tooltip';
189
+ Tooltip.displayName = "Tooltip";