@carbon/ibm-products 2.85.0 → 2.86.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/css/carbon.css +55 -2
  2. package/css/carbon.css.map +1 -1
  3. package/css/index-full-carbon.css +615 -410
  4. package/css/index-full-carbon.css.map +1 -1
  5. package/css/index-full-carbon.min.css +1 -1
  6. package/css/index-full-carbon.min.css.map +1 -1
  7. package/css/index-without-carbon-released-only.css +3225 -3081
  8. package/css/index-without-carbon-released-only.css.map +1 -1
  9. package/css/index-without-carbon-released-only.min.css +1 -1
  10. package/css/index-without-carbon-released-only.min.css.map +1 -1
  11. package/css/index-without-carbon.css +2958 -2806
  12. package/css/index-without-carbon.css.map +1 -1
  13. package/css/index-without-carbon.min.css +1 -1
  14. package/css/index-without-carbon.min.css.map +1 -1
  15. package/css/index.css +2202 -2011
  16. package/css/index.css.map +1 -1
  17. package/css/index.min.css +1 -1
  18. package/css/index.min.css.map +1 -1
  19. package/es/components/ActionSet/ActionSet.d.ts +6 -0
  20. package/es/components/ActionSet/ActionSet.js +20 -10
  21. package/es/components/Coachmark/next/Coachmark/ContentBody.js +1 -1
  22. package/es/components/DataSpreadsheet/utils/moveColumnIndicatorLine.js +2 -2
  23. package/es/components/EditInPlace/EditInPlace.d.ts +2 -3
  24. package/es/components/OptionsTile/OptionsTile.js +35 -12
  25. package/es/components/PageHeader/next/context.js +1 -1
  26. package/es/components/PageHeader/next/index.js +3 -3
  27. package/es/components/StringFormatter/StringFormatter.js +1 -1
  28. package/es/components/TagSet/TagSet.js +1 -1
  29. package/es/components/Tearsheet/next/StackContext.d.ts +1 -1
  30. package/es/components/Tearsheet/next/Tearsheet.d.ts +19 -5
  31. package/es/components/Tearsheet/next/Tearsheet.js +90 -31
  32. package/es/components/Tearsheet/next/TearsheetBody.js +2 -2
  33. package/es/components/Tearsheet/next/TearsheetFooter.d.ts +31 -0
  34. package/es/components/Tearsheet/next/TearsheetFooter.js +39 -0
  35. package/es/components/Tearsheet/next/TearsheetHeader.d.ts +1 -1
  36. package/es/components/Tearsheet/next/index.d.ts +2 -1
  37. package/es/components/index.d.ts +1 -1
  38. package/es/global/js/hooks/index.d.ts +1 -0
  39. package/es/global/js/utils/devtools.js +1 -1
  40. package/es/index.js +8 -8
  41. package/es/node_modules/@carbon/icons-react/es/generated/bucket-10.js +1184 -1110
  42. package/es/node_modules/@carbon/icons-react/es/generated/bucket-3.js +1337 -1334
  43. package/es/node_modules/@floating-ui/dom/dist/floating-ui.dom.js +2 -2
  44. package/lib/components/ActionSet/ActionSet.d.ts +6 -0
  45. package/lib/components/ActionSet/ActionSet.js +20 -10
  46. package/lib/components/Coachmark/next/Coachmark/ContentBody.js +0 -3
  47. package/lib/components/DataSpreadsheet/utils/moveColumnIndicatorLine.js +2 -2
  48. package/lib/components/EditInPlace/EditInPlace.d.ts +2 -3
  49. package/lib/components/OptionsTile/OptionsTile.js +35 -12
  50. package/lib/components/PageHeader/next/index.js +6 -6
  51. package/lib/components/TagSet/TagSet.js +0 -3
  52. package/lib/components/Tearsheet/next/StackContext.d.ts +1 -1
  53. package/lib/components/Tearsheet/next/Tearsheet.d.ts +19 -5
  54. package/lib/components/Tearsheet/next/Tearsheet.js +90 -31
  55. package/lib/components/Tearsheet/next/TearsheetBody.js +2 -2
  56. package/lib/components/Tearsheet/next/TearsheetFooter.d.ts +31 -0
  57. package/lib/components/Tearsheet/next/TearsheetFooter.js +43 -0
  58. package/lib/components/Tearsheet/next/TearsheetHeader.d.ts +1 -1
  59. package/lib/components/Tearsheet/next/index.d.ts +2 -1
  60. package/lib/components/index.d.ts +1 -1
  61. package/lib/global/js/hooks/index.d.ts +1 -0
  62. package/lib/index.js +50 -50
  63. package/lib/node_modules/@carbon/icons-react/es/generated/bucket-10.js +1204 -1130
  64. package/lib/node_modules/@carbon/icons-react/es/generated/bucket-3.js +1351 -1348
  65. package/package.json +14 -18
  66. package/scss/components/NotificationsPanel/_notifications-panel.scss +3 -0
  67. package/scss/components/OptionsTile/_options-tile.scss +28 -7
  68. package/scss/components/PageHeader/_page-header.scss +14 -4
  69. package/scss/components/SidePanel/_side-panel.scss +0 -2
  70. package/scss/components/Tearsheet/_index-with-carbon.scss +2 -1
  71. package/scss/components/Tearsheet/_index.scss +1 -0
  72. package/scss/components/Tearsheet/_tearsheet.scss +0 -2
  73. package/scss/components/Tearsheet/_tearsheet_next.scss +351 -229
  74. package/telemetry.yml +4 -1
@@ -27,6 +27,12 @@ export interface ActionSetProps {
27
27
  * An optional class or classes to be added to the outermost element.
28
28
  */
29
29
  className?: string;
30
+ /**
31
+ * When true, prevents automatic stacking of buttons even when size would
32
+ * normally trigger stacking (e.g., 'sm' size or 'md' with 3+ actions).
33
+ * Buttons will remain in a horizontal layout.
34
+ */
35
+ disableStacking?: boolean;
30
36
  /**
31
37
  * The size of the action set. Different button arrangements are used at
32
38
  * different sizes, to make best use of the available space.
@@ -44,7 +44,7 @@ ActionSetButton.displayName = 'ActionSetButton';
44
44
  ActionSetButton.propTypes = {
45
45
  /**@ts-ignore*/
46
46
  ...Button.PropTypes,
47
- kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'secondary', 'danger', 'primary']),
47
+ kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'tertiary', 'secondary', 'danger', 'primary']),
48
48
  label: PropTypes.string,
49
49
  loading: PropTypes.bool
50
50
  };
@@ -66,6 +66,7 @@ const validateActionSetProps = _ref2 => {
66
66
  const countActions = kind => actions.filter(action => (action.kind || defaultKind) === kind).length;
67
67
  const primaryActions = countActions('primary');
68
68
  const secondaryActions = countActions('secondary');
69
+ const tertiaryActions = countActions('tertiary');
69
70
  const dangerActions = countActions('danger');
70
71
  const ghostActions = countActions('ghost') + countActions('danger--ghost');
71
72
  if (stacking && actions.length > 3) {
@@ -83,8 +84,8 @@ const validateActionSetProps = _ref2 => {
83
84
  if (stacking && actions.length > 1 && ghostActions > 0) {
84
85
  problems.push(`you cannot have a 'ghost' button in conjunction with other action types in this size of ${componentName}`);
85
86
  }
86
- if (actions.length > primaryActions + secondaryActions + dangerActions + ghostActions) {
87
- problems.push(`you can only have 'primary', 'danger', 'secondary', 'ghost' and 'danger--ghost' buttons in a ${componentName}`);
87
+ if (actions.length > primaryActions + secondaryActions + tertiaryActions + dangerActions + ghostActions) {
88
+ problems.push(`you can only have 'primary', 'danger', 'secondary', 'tertiary', 'ghost' and 'danger--ghost' buttons in a ${componentName}`);
88
89
  }
89
90
  return problems.length > 0 ? pconsole.error(`Invalid prop \`actions\` supplied to \`${componentName}\`: ${problems.join(', and ')}.`) : null;
90
91
  }
@@ -107,6 +108,7 @@ const ActionSet = /*#__PURE__*/React__default.forwardRef((props, ref) => {
107
108
  actions,
108
109
  buttonSize,
109
110
  className,
111
+ disableStacking = false,
110
112
  size = defaults.size,
111
113
  ...rest
112
114
  } = props;
@@ -117,16 +119,18 @@ const ActionSet = /*#__PURE__*/React__default.forwardRef((props, ref) => {
117
119
  const buttons = actions && actions.slice?.(0) || [];
118
120
 
119
121
  // We stack the buttons in a sm set, or if there are three or more in a md set.
120
- const stacking = willStack(size, buttons.length);
122
+ // Unless disableStacking is true, in which case we never stack.
123
+ const stacking = disableStacking ? false : willStack(size, buttons.length);
121
124
 
122
- // Order of button kinds: ghost first, then danger--ghost, then most other types,
123
- // then danger, and finally primary
125
+ // Order of button kinds: ghost first, then danger--ghost, then tertiary,
126
+ // then most other types, then danger, and finally primary
124
127
  const buttonOrder = kind => ({
125
128
  ghost: 1,
126
129
  'danger--ghost': 2,
127
- danger: 4,
128
- primary: 5
129
- })[kind] ?? 3;
130
+ tertiary: 3,
131
+ danger: 5,
132
+ primary: 6
133
+ })[kind] ?? 4;
130
134
 
131
135
  // order the actions with ghost/ghost-danger buttons first and primary/danger buttons last
132
136
  // (or the opposite way if we're stacking)
@@ -183,7 +187,7 @@ ActionSet.propTypes = {
183
187
  actions: allPropTypes([PropTypes.arrayOf(PropTypes.shape({
184
188
  /**@ts-ignore*/
185
189
  ...Button.propTypes,
186
- kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'secondary', 'danger', 'primary']),
190
+ kind: PropTypes.oneOf(['ghost', 'danger--ghost', 'tertiary', 'secondary', 'danger', 'primary']),
187
191
  label: PropTypes.string,
188
192
  loading: PropTypes.bool,
189
193
  // we duplicate this Button prop to improve the DocGen here
@@ -202,6 +206,12 @@ ActionSet.propTypes = {
202
206
  * An optional class or classes to be added to the outermost element.
203
207
  */
204
208
  className: PropTypes.string,
209
+ /**
210
+ * When true, prevents automatic stacking of buttons even when size would
211
+ * normally trigger stacking (e.g., 'sm' size or 'md' with 3+ actions).
212
+ * Buttons will remain in a horizontal layout.
213
+ */
214
+ disableStacking: PropTypes.bool,
205
215
  /**
206
216
  * The size of the action set. Different button arrangements are used at
207
217
  * different sizes, to make best use of the available space.
@@ -34,4 +34,4 @@ ContentBody.propTypes = {
34
34
  className: PropTypes.string
35
35
  };
36
36
 
37
- export { ContentBody, ContentBody as default };
37
+ export { ContentBody };
@@ -38,7 +38,7 @@ const moveColumnIndicatorLine = _ref => {
38
38
 
39
39
  // Is near left side of viewport
40
40
  if (clientX < leftEdgeThreshold) {
41
- window.scrollBy(-10, 0);
41
+ window.scrollBy(-scrollSpeed, 0);
42
42
  }
43
43
 
44
44
  // Is near right side of viewport
@@ -48,7 +48,7 @@ const moveColumnIndicatorLine = _ref => {
48
48
 
49
49
  // Is near left edge of table
50
50
  if (clientX > left && clientX < left + leftEdgeThreshold) {
51
- listContainer.scrollBy(-10, 0);
51
+ listContainer.scrollBy(-scrollSpeed, 0);
52
52
  }
53
53
 
54
54
  // Is near right edge of table
@@ -5,7 +5,6 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
  import React, { PropsWithChildren } from 'react';
8
- import PropTypes from 'prop-types';
9
8
  type Size = 'sm' | 'md' | 'lg';
10
9
  type AlignPropType = 'top' | 'top-left' | 'top-right' | 'bottom' | 'bottom-left' | 'bottom-right' | 'left' | 'right';
11
10
  type Shape = {
@@ -69,11 +68,11 @@ export interface EditInplaceProps extends PropsWithChildren {
69
68
  /**
70
69
  * determines if the input is in readOnly mode
71
70
  */
72
- readOnly: PropTypes.bool;
71
+ readOnly?: boolean;
73
72
  /**
74
73
  * label for the edit off button that displays when in read only mode
75
74
  */
76
- readOnlyLabel?: PropTypes.string;
75
+ readOnlyLabel?: string;
77
76
  /**
78
77
  * text for the toggletip that displays when in read only mode
79
78
  */
@@ -121,7 +121,19 @@ const OptionsTile = /*#__PURE__*/React__default.forwardRef((props, ref) => {
121
121
  setOpen(false);
122
122
  }
123
123
  };
124
- const toggle = evt => {
124
+ const handleSummaryClick = evt => {
125
+ // Check if the click originated from the toggle button
126
+ const target = evt.target;
127
+ const toggleContainer = target.closest(`.${blockClass}__toggle-container`);
128
+
129
+ // If click is on toggle button, don't handle expand/collapse
130
+ if (toggleContainer) {
131
+ evt.preventDefault();
132
+ evt.stopPropagation();
133
+ return;
134
+ }
135
+
136
+ // Prevent default details toggle behavior
125
137
  evt.preventDefault();
126
138
  if (open) {
127
139
  collapse();
@@ -174,8 +186,25 @@ const OptionsTile = /*#__PURE__*/React__default.forwardRef((props, ref) => {
174
186
  [`${blockClass}--closing`]: closing
175
187
  }),
176
188
  ref: ref
177
- }, getDevtoolsProps(componentName)), enabled !== undefined && /*#__PURE__*/React__default.createElement("div", {
178
- className: `${blockClass}__toggle-container`
189
+ }, getDevtoolsProps(componentName)), isExpandable ? /*#__PURE__*/React__default.createElement("details", {
190
+ className: `${blockClass}__details`,
191
+ open: open,
192
+ ref: detailsRef
193
+ }, /*#__PURE__*/React__default.createElement("summary", {
194
+ className: cx(`${blockClass}__header`, {
195
+ [`${blockClass}__header--has-toggle`]: enabled !== undefined
196
+ }),
197
+ onClick: handleSummaryClick,
198
+ "data-testid": "options-tile-header"
199
+ }, enabled !== undefined &&
200
+ /*#__PURE__*/
201
+ // eslint-disable-next-line jsx-a11y/no-static-element-interactions
202
+ React__default.createElement("div", {
203
+ className: `${blockClass}__toggle-container`,
204
+ "data-testid": "options-tile-toggle-container",
205
+ onMouseDown: evt => {
206
+ evt.preventDefault();
207
+ }
179
208
  }, /*#__PURE__*/React__default.createElement(Toggle, {
180
209
  id: `${titleId}-toggle`,
181
210
  className: `${blockClass}__toggle`,
@@ -185,14 +214,7 @@ const OptionsTile = /*#__PURE__*/React__default.forwardRef((props, ref) => {
185
214
  onToggle: onToggle,
186
215
  size: "sm",
187
216
  disabled: isLocked
188
- })), isExpandable ? /*#__PURE__*/React__default.createElement("details", {
189
- className: `${blockClass}__details`,
190
- open: open,
191
- ref: detailsRef
192
- }, /*#__PURE__*/React__default.createElement("summary", {
193
- className: `${blockClass}__header`,
194
- onClick: toggle
195
- }, /*#__PURE__*/React__default.createElement(ChevronDown, {
217
+ })), /*#__PURE__*/React__default.createElement(ChevronDown, {
196
218
  size: 16,
197
219
  className: cx(`${blockClass}__chevron`, {
198
220
  [`${blockClass}__chevron--open`]: open,
@@ -200,7 +222,8 @@ const OptionsTile = /*#__PURE__*/React__default.forwardRef((props, ref) => {
200
222
  })
201
223
  }), renderTitle()), /*#__PURE__*/React__default.createElement("div", {
202
224
  className: `${blockClass}__content`,
203
- ref: contentRef
225
+ ref: contentRef,
226
+ "data-testid": "options-tile-content"
204
227
  }, /*#__PURE__*/React__default.createElement(Layer, null, isLocked && /*#__PURE__*/React__default.createElement("p", {
205
228
  className: `${blockClass}__locked-text`
206
229
  }, _Locked || (_Locked = /*#__PURE__*/React__default.createElement(Locked, {
@@ -5,7 +5,7 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import { createContext, useContext } from 'react';
8
+ import { useContext, createContext } from 'react';
9
9
 
10
10
  /**
11
11
  * -------------
@@ -7,12 +7,12 @@
7
7
 
8
8
  export { BreadcrumbBar, BreadcrumbOverflow, Content, ContentPageActions, ContentText, HeroImage, PageHeader, Root, ScrollButton, TabBar, TagOverflow, TitleBreadcrumb } from './PageHeader.js';
9
9
  export { PageHeaderBreadcrumbBar } from './PageHeaderBreadcrumbBar.js';
10
+ export { PageHeaderBreadcrumbOverflow } from './PageHeaderBreadcrumbOverflow.js';
10
11
  export { PageHeaderContent } from './PageHeaderContent.js';
11
12
  export { PageHeaderContentPageActions } from './PageHeaderContentPageActions.js';
12
13
  export { PageHeaderContentText } from './PageHeaderContentText.js';
13
- export { PageHeaderTabBar } from './PageHeaderTabBar.js';
14
14
  export { PageHeaderHeroImage } from './PageHeaderHeroImage.js';
15
15
  export { PageHeaderScrollButton } from './PageHeaderScrollButton.js';
16
- export { PageHeaderTitleBreadcrumb } from './PageHeaderTitleBreadcrumb.js';
17
- export { PageHeaderBreadcrumbOverflow } from './PageHeaderBreadcrumbOverflow.js';
16
+ export { PageHeaderTabBar } from './PageHeaderTabBar.js';
18
17
  export { PageHeaderTagOverflow } from './PageHeaderTagOverflow.js';
18
+ export { PageHeaderTitleBreadcrumb } from './PageHeaderTitleBreadcrumb.js';
@@ -12,7 +12,7 @@ import cx from 'classnames';
12
12
  import { getDevtoolsProps } from '../../global/js/utils/devtools.js';
13
13
  import { pkg } from '../../settings.js';
14
14
  import { DefinitionTooltip } from '@carbon/react';
15
- import { StringFormatterAlignment, propMappingFunction, deprecated_StringFormatterAlignment } from './utils/enums.js';
15
+ import { StringFormatterAlignment, deprecated_StringFormatterAlignment, propMappingFunction } from './utils/enums.js';
16
16
  import { allPropTypes } from '../../global/js/utils/props-helper.js';
17
17
  import { useIsomorphicEffect } from '../../global/js/hooks/useIsomorphicEffect.js';
18
18
 
@@ -352,4 +352,4 @@ TagSet.propTypes = {
352
352
  };
353
353
  TagSet.displayName = componentName;
354
354
 
355
- export { TagSet, TagSet as default };
355
+ export { TagSet };
@@ -7,7 +7,7 @@
7
7
  import React, { ReactNode } from 'react';
8
8
  export interface StackContextType {
9
9
  stack: string[];
10
- notifyStack: (id: string, open: boolean, container: HTMLDivElement) => void;
10
+ notifyStack: (id: string, open: boolean, container: HTMLDivElement | null) => void;
11
11
  getScaleFactor: (id: string) => number | null;
12
12
  getBlockSizeChange: (id: string) => string | null;
13
13
  getDepth: (id: string) => number | null;
@@ -11,6 +11,7 @@ import { TearsheetHeaderProps, TearsheetNavigationBarProps, TearsheetScrollButto
11
11
  import { TearsheetHeaderContentProps } from './TearsheetHeaderContent';
12
12
  import { InfluencerProps, MainContentProps, SummaryContentProps, TearsheetBodyProps } from './TearsheetBody';
13
13
  import { TearsheetHeaderActionItemProps, TearsheetHeaderActionsProps } from './TearsheetHeaderActions';
14
+ import { TearsheetFooterProps } from './TearsheetFooter';
14
15
  /**
15
16
  * ----------
16
17
  * Tearsheet
@@ -75,6 +76,19 @@ export interface TearsheetProps extends ComposedModalProps {
75
76
  * The DOM element that the tearsheet should be rendered within. Defaults to document.body.
76
77
  */
77
78
  portalTarget?: HTMLElement;
79
+ /**
80
+ * Disable the portal behavior and render the tearsheet in the existing DOM structure.
81
+ * This is useful for testing, when you need to inherit React context from parent components,
82
+ * or when you don't need the z-index isolation that portals provide.
83
+ * @default false
84
+ */
85
+ disablePortal?: boolean;
86
+ /**
87
+ * If true, the tearsheet will remain mounted in the DOM when closed, using CSS to hide it.
88
+ * By default (false), the tearsheet unmounts from the DOM after the exit animation completes.
89
+ * Set to true if you need to preserve component state or avoid remounting overhead.
90
+ */
91
+ keepMounted?: boolean;
78
92
  }
79
93
  export type TearsheetComponentType = React.ForwardRefExoticComponent<TearsheetProps & React.RefAttributes<HTMLDivElement>> & {
80
94
  Header: FC<TearsheetHeaderProps>;
@@ -87,10 +101,10 @@ export type TearsheetComponentType = React.ForwardRefExoticComponent<TearsheetPr
87
101
  MainContent: FC<MainContentProps>;
88
102
  SummaryContent: FC<SummaryContentProps>;
89
103
  Body: FC<TearsheetBodyProps>;
90
- Footer: FC<FooterProps>;
104
+ Footer: FC<TearsheetFooterProps>;
91
105
  };
106
+ /**
107
+ * Wrapper component that handles presence logic and conditionally renders TearsheetInternal.
108
+ * This ensures that all component state and effects are only initialized when the tearsheet is present.
109
+ */
92
110
  export declare const Tearsheet: TearsheetComponentType;
93
- export interface FooterProps {
94
- children: ReactNode;
95
- className?: string;
96
- }
@@ -7,6 +7,7 @@
7
7
 
8
8
  import { extends as _extends } from '../../../_virtual/_rollupPluginBabelHelpers.js';
9
9
  import React__default, { forwardRef, useRef, useState, useEffect } from 'react';
10
+ import { createPortal } from 'react-dom';
10
11
  import cx from 'classnames';
11
12
  import { usePrefix, unstable_FeatureFlags, ComposedModal, ModalBody } from '@carbon/react';
12
13
  import { TearsheetContext, blockClass } from './context.js';
@@ -14,11 +15,13 @@ import TearsheetHeader, { TearsheetNavigationBar, TearsheetScrollButton } from '
14
15
  import TearsheetHeaderContent from './TearsheetHeaderContent.js';
15
16
  import TearsheetBody, { Influencer, MainContent, SummaryContent } from './TearsheetBody.js';
16
17
  import { TearsheetHeaderActions, TearsheetHeaderActionItem } from './TearsheetHeaderActions.js';
18
+ import TearsheetFooter from './TearsheetFooter.js';
17
19
  import { breakpoints } from '@carbon/layout';
18
- import { usePortalTarget } from '../../../global/js/hooks/usePortalTarget.js';
19
20
  import { useStackContext } from './StackContext.js';
20
21
  import { useMatchMedia } from '../../../global/js/hooks/useMatchMedia.js';
21
22
  import { useId } from '../../../global/js/utils/useId.js';
23
+ import { usePresence } from '../usePresence.js';
24
+ import { useMergedRefs } from '../../../global/js/hooks/useMergedRefs.js';
22
25
  import { useIsomorphicEffect } from '../../../global/js/hooks/useIsomorphicEffect.js';
23
26
 
24
27
  /**
@@ -27,7 +30,11 @@ import { useIsomorphicEffect } from '../../../global/js/hooks/useIsomorphicEffec
27
30
  * ----------
28
31
  */
29
32
 
30
- const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
33
+ /**
34
+ * Internal component that handles the actual tearsheet rendering.
35
+ * This component is always "present" when mounted - the wrapper handles presence logic.
36
+ */
37
+ const TearsheetInternal = /*#__PURE__*/forwardRef((_ref, ref) => {
31
38
  let {
32
39
  children,
33
40
  variant = 'wide',
@@ -40,14 +47,18 @@ const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
40
47
  selectorPrimaryFocus,
41
48
  open = false,
42
49
  portalTarget,
50
+ disablePortal = false,
43
51
  verticalGap,
44
52
  containerClassName,
53
+ keepMounted = false,
54
+ isExiting = false,
55
+ presenceRef,
45
56
  ...rest
46
57
  } = _ref;
47
58
  const carbonPrefix = usePrefix();
48
- const localRef = useRef(undefined);
59
+ const localRef = useRef(null);
49
60
  const bodyRef = useRef(null);
50
- const modalRef = ref || localRef;
61
+ const mergedRefs = useMergedRefs([ref, localRef, presenceRef]);
51
62
  const smMediaQuery = `(max-width: ${breakpoints.md.width})`;
52
63
  const isSm = useMatchMedia(smMediaQuery) || variant === 'narrow';
53
64
  const [hasCloseIcon, setHasCloseIcon] = useState(true);
@@ -57,7 +68,7 @@ const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
57
68
  const header = arr.find(child => child.type === TearsheetHeader);
58
69
  const influencer = arr.find(child => child.type === Influencer);
59
70
  const body = arr.find(child => child.type === TearsheetBody);
60
- const footer = arr.find(child => child.type === Footer);
71
+ const footer = arr.find(child => child.type === TearsheetFooter);
61
72
  const uniqueId = useRef(useId());
62
73
  const {
63
74
  notifyStack,
@@ -67,10 +78,17 @@ const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
67
78
  getBlockSizeChange
68
79
  } = useStackContext();
69
80
  const [depth, setDepth] = useState(0);
70
- const renderPortalUse = usePortalTarget(portalTarget);
81
+ const [mountNode, setMountNode] = useState(null);
82
+
83
+ // Set portal mount node using useIsomorphicEffect to avoid SSR issues and double rendering
84
+ useIsomorphicEffect(() => {
85
+ if (!disablePortal) {
86
+ setMountNode(portalTarget || document.body);
87
+ }
88
+ }, [portalTarget, disablePortal]);
71
89
  useIsomorphicEffect(() => {
72
- const AILabelWidth = modalRef.current?.querySelector(`.${carbonPrefix}--ai-label`)?.clientWidth ?? 0;
73
- const headerActionMarginRight = AILabelWidth + 24 + (isSm ? 8 : 0); // 24 is to compeNsate for close button
90
+ const AILabelWidth = localRef.current?.querySelector(`.${carbonPrefix}--ai-label`)?.clientWidth ?? 0;
91
+ const headerActionMarginRight = AILabelWidth + 24 + (isSm ? 8 : 0); // 24 is to compensate for close button
74
92
  document.documentElement.style.setProperty('--tearsheet-header-action-offset', `${headerActionMarginRight}px`);
75
93
  if (influencerWidth) {
76
94
  document.documentElement.style.setProperty('--tearsheet-influencer-width', `${influencerWidth}`);
@@ -85,25 +103,30 @@ const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
85
103
  // eslint-disable-next-line react-hooks/exhaustive-deps
86
104
  }, [isSm, rest.decorator, influencerWidth, summaryContentWidth, verticalGap]);
87
105
  useIsomorphicEffect(() => {
88
- if (bodyRef.current) {
89
- notifyStack?.(uniqueId.current, open, bodyRef.current);
106
+ const id = uniqueId.current;
107
+ if (localRef.current && open) {
108
+ notifyStack?.(id, true, localRef.current);
90
109
  }
91
110
 
111
+ // Cleanup when component unmounts
112
+ return () => {
113
+ notifyStack?.(id, false, null);
114
+ };
92
115
  // eslint-disable-next-line react-hooks/exhaustive-deps
93
- }, [open]);
116
+ }, [localRef.current, open]);
94
117
  useEffect(() => {
95
- if (stack?.length > 0) {
118
+ if (stack?.length > 0 && localRef.current) {
96
119
  const stackDepth = getDepth?.(uniqueId.current),
97
120
  blockSizeChange = getBlockSizeChange?.(uniqueId.current),
98
121
  scaleFactor = getScaleFactor?.(uniqueId.current);
99
122
  setDepth(stackDepth);
100
- modalRef.current.style.setProperty('--stack-depth', stackDepth + '');
101
- modalRef.current.style.setProperty('--block-size-change', blockSizeChange);
102
- modalRef.current.style.setProperty('--scale-factor', scaleFactor + '');
123
+ localRef.current.style.setProperty('--stack-depth', stackDepth + '');
124
+ localRef.current.style.setProperty('--block-size-change', blockSizeChange);
125
+ localRef.current.style.setProperty('--scale-factor', scaleFactor + '');
103
126
  }
104
127
  // eslint-disable-next-line react-hooks/exhaustive-deps
105
128
  }, [stack]);
106
- return renderPortalUse(/*#__PURE__*/React__default.createElement(TearsheetContext.Provider, {
129
+ const content = /*#__PURE__*/React__default.createElement(TearsheetContext.Provider, {
107
130
  value: {
108
131
  hasCloseIcon,
109
132
  setHasCloseIcon,
@@ -126,29 +149,65 @@ const Tearsheet = /*#__PURE__*/forwardRef((_ref, ref) => {
126
149
  [`${blockClass}--stack-activated`]: stack.length > 1,
127
150
  [`${blockClass}--has-ai-label`]: !!rest.decorator && rest.decorator['type']?.displayName === 'AILabel',
128
151
  [`${blockClass}--has-decorator`]: !!rest.decorator && rest.decorator['type']?.displayName !== 'AILabel',
129
- [`${blockClass}--has-close`]: hasCloseIcon
152
+ [`${blockClass}--has-close`]: hasCloseIcon,
153
+ ['is-visible']: keepMounted ? open : true,
154
+ // When keepMounted, use open prop; otherwise always visible
155
+ [`${blockClass}--keep-mounted`]: keepMounted
130
156
  }),
131
157
  containerClassName: cx(`${blockClass}__container`, containerClassName),
132
158
  onClose,
133
- open,
159
+ open: keepMounted ? open : true,
160
+ // When keepMounted, use actual open; otherwise always open
134
161
  selectorPrimaryFocus,
135
- ref: modalRef,
162
+ ref: mergedRefs,
136
163
  selectorsFloatingMenus: [`.${carbonPrefix}--overflow-menu-options`, `.${carbonPrefix}--tooltip`, '.flatpickr-calendar', `.${blockClass}__container`, `.${carbonPrefix}--menu`, ...selectorsFloatingMenus],
137
164
  isFullWidth: true,
138
- size: variant === 'narrow' ? 'sm' : ''
165
+ size: variant === 'narrow' ? 'sm' : '',
166
+ "data-tearsheet-exiting": isExiting ? true : undefined
139
167
  }), header, /*#__PURE__*/React__default.createElement(ModalBody, {
140
- className: `${blockClass}__body-layout`,
168
+ className: cx(`${blockClass}__body-layout`, {
169
+ [`${blockClass}__body-layout--has-influencer`]: influencer && !isSm
170
+ }),
141
171
  ref: bodyRef
142
- }, influencer, body, footer)))));
172
+ }, influencer, body, footer))));
173
+
174
+ // If portal is disabled, return content directly
175
+ if (disablePortal) {
176
+ return content;
177
+ }
178
+
179
+ // Return portal if mountNode is set, otherwise return content directly (SSR-safe)
180
+ return mountNode ? /*#__PURE__*/createPortal(content, mountNode) : content;
143
181
  });
144
- const Footer = /*#__PURE__*/forwardRef((_ref2, ref) => {
145
- let {
146
- children
147
- } = _ref2;
148
- return /*#__PURE__*/React__default.createElement("footer", {
149
- className: `${blockClass}__footer`,
150
- ref: ref
151
- }, children);
182
+
183
+ /**
184
+ * Wrapper component that handles presence logic and conditionally renders TearsheetInternal.
185
+ * This ensures that all component state and effects are only initialized when the tearsheet is present.
186
+ */
187
+ const Tearsheet = /*#__PURE__*/forwardRef((props, ref) => {
188
+ const {
189
+ open = false,
190
+ keepMounted = false
191
+ } = props;
192
+ const presenceRef = useRef(null);
193
+
194
+ // Use presence hook for enter/exit animations (unless keepMounted is true)
195
+ const {
196
+ isPresent,
197
+ isExiting
198
+ } = usePresence(presenceRef, keepMounted ? true : open);
199
+
200
+ // Don't render if not present (after exit animation completes) - unless keepMounted is true
201
+ if (!keepMounted && !isPresent) {
202
+ return null;
203
+ }
204
+
205
+ // When present, render the internal component with all props
206
+ return /*#__PURE__*/React__default.createElement(TearsheetInternal, _extends({}, props, {
207
+ ref: ref,
208
+ presenceRef: presenceRef,
209
+ isExiting: isExiting
210
+ }));
152
211
  });
153
212
  Tearsheet.Header = TearsheetHeader;
154
213
  Tearsheet.HeaderContent = TearsheetHeaderContent;
@@ -156,7 +215,7 @@ Tearsheet.Body = TearsheetBody;
156
215
  Tearsheet.Influencer = Influencer;
157
216
  Tearsheet.MainContent = MainContent;
158
217
  Tearsheet.SummaryContent = SummaryContent;
159
- Tearsheet.Footer = Footer;
218
+ Tearsheet.Footer = TearsheetFooter;
160
219
  Tearsheet.NavigationBar = TearsheetNavigationBar;
161
220
  Tearsheet.ScrollButton = TearsheetScrollButton;
162
221
  Tearsheet.HeaderActions = TearsheetHeaderActions;
@@ -102,7 +102,7 @@ const SummaryContent = /*#__PURE__*/forwardRef((_ref3, ref) => {
102
102
  size: "sm",
103
103
  open: summaryPanelOpen,
104
104
  onRequestClose: onSummaryPanelClose,
105
- className: className
105
+ className: cx(`${blockClass}__side-panel`, className)
106
106
  }, children);
107
107
  });
108
108
  const Influencer = /*#__PURE__*/forwardRef((_ref4, ref) => {
@@ -126,7 +126,7 @@ const Influencer = /*#__PURE__*/forwardRef((_ref4, ref) => {
126
126
  open: influencerPanelOpen,
127
127
  onRequestClose: onInfluencerPanelClose,
128
128
  placement: "left",
129
- className: className
129
+ className: cx(`${blockClass}__side-panel`, className)
130
130
  }, children);
131
131
  });
132
132
 
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Copyright IBM Corp. 2025, 2025
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ import React, { ReactNode } from 'react';
8
+ import { ButtonProps } from '@carbon/react';
9
+ import { ActionSetProps } from '../../ActionSet';
10
+ export interface TearsheetFooterProps {
11
+ /**
12
+ * Optional children to render in the footer. If provided, children are rendered first,
13
+ * followed by the ActionSet (if actions are provided).
14
+ */
15
+ children?: ReactNode;
16
+ /**
17
+ * Optional class name to add to the footer element.
18
+ */
19
+ className?: string;
20
+ /**
21
+ * Optional array of action button configurations. If provided, an ActionSet will be
22
+ * rendered after any children. Each action follows the ActionSet button specification.
23
+ */
24
+ actions?: ButtonProps<React.ElementType>[];
25
+ /**
26
+ * Optional size for the ActionSet buttons. Defaults to the ActionSet's default size.
27
+ */
28
+ buttonSize?: ActionSetProps['buttonSize'];
29
+ }
30
+ declare const TearsheetFooter: React.ForwardRefExoticComponent<TearsheetFooterProps & React.RefAttributes<HTMLDivElement>>;
31
+ export default TearsheetFooter;
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Copyright IBM Corp. 2020, 2026
3
+ *
4
+ * This source code is licensed under the Apache-2.0 license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+
8
+ import React__default, { forwardRef, useContext } from 'react';
9
+ import cx from 'classnames';
10
+ import { ActionSet } from '../../ActionSet/ActionSet.js';
11
+ import { TearsheetContext, blockClass } from './context.js';
12
+
13
+ const TearsheetFooter = /*#__PURE__*/forwardRef((_ref, ref) => {
14
+ let {
15
+ children,
16
+ className,
17
+ actions,
18
+ buttonSize
19
+ } = _ref;
20
+ const {
21
+ variant
22
+ } = useContext(TearsheetContext);
23
+ const actionCount = actions?.length || 0;
24
+ return /*#__PURE__*/React__default.createElement("footer", {
25
+ className: cx(`${blockClass}__footer`, className, {
26
+ [`${blockClass}__footer--three-actions`]: actionCount == 3,
27
+ [`${blockClass}__footer--many-actions`]: actionCount > 3
28
+ }),
29
+ ref: ref
30
+ }, children, actions && actions.length > 0 && /*#__PURE__*/React__default.createElement(ActionSet, {
31
+ actions: actions,
32
+ buttonSize: buttonSize,
33
+ disableStacking: true,
34
+ size: variant == 'wide' ? '2xl' : 'lg'
35
+ }));
36
+ });
37
+ TearsheetFooter.displayName = 'TearsheetFooter';
38
+
39
+ export { TearsheetFooter as default };
@@ -35,7 +35,7 @@ export interface TearsheetHeaderProps {
35
35
  hideCloseButton?: boolean;
36
36
  className?: string;
37
37
  /**
38
- * Default header collapse/expand while scrolling the main content can bd disabled by setting this
38
+ * Default header collapse/expand while scrolling the main content can be disabled by setting this
39
39
  */
40
40
  disableHeaderCollapse?: boolean;
41
41
  }
@@ -7,8 +7,9 @@
7
7
  export { Tearsheet } from './Tearsheet';
8
8
  export { StackProvider, useStackContext } from './StackContext';
9
9
  export type { StackContextType } from './StackContext';
10
- export type { TearsheetProps, TearsheetComponentType, FooterProps, } from './Tearsheet';
10
+ export type { TearsheetProps, TearsheetComponentType } from './Tearsheet';
11
11
  export type { MainContentProps, SummaryContentProps, TearsheetBodyProps, InfluencerProps, } from './TearsheetBody';
12
12
  export type { TearsheetHeaderProps, TearsheetNavigationBarProps, TearsheetScrollButtonProps, } from './TearsheetHeader';
13
13
  export type { TearsheetHeaderActionItemProps, TearsheetHeaderActionsProps, } from './TearsheetHeaderActions';
14
14
  export type { TearsheetHeaderContentProps } from './TearsheetHeaderContent';
15
+ export type { TearsheetFooterProps } from './TearsheetFooter';
@@ -50,7 +50,7 @@ export { TruncatedText as preview__TruncatedText, type TruncatedTextProps, } fro
50
50
  export { FeatureFlags as preview__FeatureFlags, useFeatureFlag as preview__useFeatureFlag, useFeatureFlags as preview__useFeatureFlags, } from './FeatureFlags';
51
51
  export * as preview__PageHeader from './PageHeader/next';
52
52
  export { Tearsheet as preview__Tearsheet, StackProvider, } from './Tearsheet/next';
53
- export type { TearsheetProps as preview__TearsheetProps, TearsheetComponentType, FooterProps, MainContentProps, SummaryContentProps, TearsheetBodyProps, InfluencerProps, TearsheetHeaderProps, TearsheetNavigationBarProps, TearsheetScrollButtonProps, TearsheetHeaderActionItemProps, TearsheetHeaderActionsProps, TearsheetHeaderContentProps, StackContextType, } from './Tearsheet/next';
53
+ export type { TearsheetProps as preview__TearsheetProps, TearsheetComponentType, TearsheetFooterProps, MainContentProps, SummaryContentProps, TearsheetBodyProps, InfluencerProps, TearsheetHeaderProps, TearsheetNavigationBarProps, TearsheetScrollButtonProps, TearsheetHeaderActionItemProps, TearsheetHeaderActionsProps, TearsheetHeaderContentProps, StackContextType, } from './Tearsheet/next';
54
54
  export { BigNumber as previewCandidate__BigNumber, type BigNumberProps, } from './BigNumber';
55
55
  export { Coachmark as previewCandidate__Coachmark, BEACON_KIND, COACHMARK_OVERLAY_KIND, COACHMARK_ALIGNMENT, useCoachmark, type CoachmarkProps, } from './Coachmark';
56
56
  export { CoachmarkBeacon as previewCandidate__CoachmarkBeacon, type CoachmarkBeaconProps, } from './CoachmarkBeacon';