UNPKG

@carbon/ibm-products

Version:
189 lines (187 loc) 8.93 kB
/** * Copyright IBM Corp. 2020, 2026 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import { __toESM } from "../../../_virtual/_rolldown/runtime.js"; import { require_classnames } from "../../../node_modules/classnames/index.js"; import { useIsomorphicEffect } from "../../../global/js/hooks/useIsomorphicEffect.js"; import { useMatchMedia } from "../../../global/js/hooks/useMatchMedia.js"; import { useCarbonFeatureFlagsObject } from "../../../global/js/hooks/useCarbonFeatureFlagsObject.js"; import { useMergedRefs } from "../../../global/js/hooks/useMergedRefs.js"; import { useId } from "../../../global/js/utils/useId.js"; import { usePresence } from "../usePresence.js"; import { TearsheetContext, blockClass } from "./context.js"; import TearsheetHeader, { TearsheetNavigationBar, TearsheetScrollButton } from "./TearsheetHeader.js"; import TearsheetHeaderContent from "./TearsheetHeaderContent.js"; import TearsheetBody, { Influencer, MainContent, SummaryContent } from "./TearsheetBody.js"; import { TearsheetHeaderActionItem, TearsheetHeaderActions } from "./TearsheetHeaderActions.js"; import TearsheetFooter from "./TearsheetFooter.js"; import { useStackContext } from "./StackContext.js"; import React, { forwardRef, useEffect, useRef, useState } from "react"; import { ComposedModal, ModalBody, unstable_FeatureFlags, usePrefix } from "@carbon/react"; import { createPortal } from "react-dom"; import { breakpoints } from "@carbon/layout"; //#region src/components/Tearsheet/next/Tearsheet.tsx /** * Copyright IBM Corp. 2025, 2025 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ var import_classnames = /* @__PURE__ */ __toESM(require_classnames()); /** * Internal component that handles the actual tearsheet rendering. * This component is always "present" when mounted - the wrapper handles presence logic. */ const TearsheetInternal = forwardRef(({ children, variant = "wide", selectorsFloatingMenus = [], className, influencerWidth, summaryContentWidth, ariaLabel, onClose, selectorPrimaryFocus, open = false, portalTarget, disablePortal = false, verticalGap, containerClassName, keepMounted = false, isExiting = false, presenceRef, decorator, launcherButtonRef, ...rest }, ref) => { const carbonPrefix = usePrefix(); const localRef = useRef(null); const bodyRef = useRef(null); const mergedRefs = useMergedRefs([ ref, localRef, presenceRef ]); const isSm = useMatchMedia(`(max-width: ${breakpoints.md.width})`) || variant === "narrow"; const parentFlagsObject = useCarbonFeatureFlagsObject(); const [fullyCollapsed, setFullyCollapsed] = useState(false); const [disableHeaderCollapse, setDisableHeaderCollapse] = useState(false); const arr = React.Children.toArray(children); const header = arr.find((child) => child.type === TearsheetHeader); const influencer = arr.find((child) => child.type === Influencer); const body = arr.find((child) => child.type === TearsheetBody); const footer = arr.find((child) => child.type === TearsheetFooter); const uniqueId = useRef(useId()); const titleId = useRef(`${blockClass}__title-${uniqueId.current}`); const { notifyStack, stack, getDepth, getScaleFactor, getBlockSizeChange } = useStackContext(); const [depth, setDepth] = useState(0); const [mountNode, setMountNode] = useState(null); useIsomorphicEffect(() => { if (!disablePortal) setMountNode(portalTarget || document.body); }, [portalTarget, disablePortal]); useIsomorphicEffect(() => { const headerActionMarginRight = (localRef.current?.querySelector(`.${carbonPrefix}--ai-label`)?.clientWidth ?? 0) + 24 + (isSm ? 8 : 0); document.documentElement.style.setProperty("--tearsheet-header-action-offset", `${headerActionMarginRight}px`); if (influencerWidth) document.documentElement.style.setProperty("--tearsheet-influencer-width", `${influencerWidth}`); if (summaryContentWidth) document.documentElement.style.setProperty("--tearsheet-summary-content-width", `${summaryContentWidth}`); if (verticalGap) document.documentElement.style.setProperty("--tearsheet-vertical-gap", `${verticalGap}`); }, [ isSm, decorator, influencerWidth, summaryContentWidth, verticalGap ]); useIsomorphicEffect(() => { const id = uniqueId.current; if (localRef.current && open) notifyStack?.(id, true, localRef.current); return () => { notifyStack?.(id, false, null); }; }, [localRef.current, open]); useEffect(() => { if (stack?.length > 0 && localRef.current) { const stackDepth = getDepth?.(uniqueId.current), blockSizeChange = getBlockSizeChange?.(uniqueId.current), scaleFactor = getScaleFactor?.(uniqueId.current); setDepth(stackDepth); localRef.current.style.setProperty("--stack-depth", stackDepth + ""); localRef.current.style.setProperty("--block-size-change", blockSizeChange); localRef.current.style.setProperty("--scale-factor", scaleFactor + ""); } }, [stack]); useEffect(() => { if (!open && launcherButtonRef?.current) { const timeoutId = setTimeout(() => { const refCurrent = launcherButtonRef.current; if (refCurrent instanceof HTMLElement) { const headerActionItem = refCurrent.closest(`.${blockClass}__header-action-item`); if (headerActionItem) { const menuButton = headerActionItem.closest(`.${blockClass}__content__header-actions`)?.querySelector(`.${blockClass}__header-actions-menuButton:not(.${blockClass}__header-actions-menuButton--hidden) button`); if (menuButton instanceof HTMLElement) menuButton.focus(); else refCurrent.focus(); } else refCurrent.focus(); } }, 100); return () => clearTimeout(timeoutId); } }, [open, launcherButtonRef]); const content = /* @__PURE__ */ React.createElement(TearsheetContext.Provider, { value: { fullyCollapsed, setFullyCollapsed, onClose, disableHeaderCollapse, setDisableHeaderCollapse, variant, isSm, decorator, titleId: titleId.current } }, /* @__PURE__ */ React.createElement(unstable_FeatureFlags, { flags: { ...parentFlagsObject, "enable-experimental-focus-wrap-without-sentinels": true } }, /* @__PURE__ */ React.createElement(ComposedModal, { ...rest, "aria-label": ariaLabel, "aria-labelledby": !ariaLabel ? titleId.current : void 0, className: (0, import_classnames.default)(blockClass, className, { [`${blockClass}--wide`]: variant === "wide", [`${blockClass}--narrow`]: variant === "narrow", [`${blockClass}--stacked`]: depth > 0, [`${blockClass}--stack-activated`]: stack.length > 1, [`${blockClass}--has-ai-label`]: !!decorator && decorator["type"]?.displayName === "AILabel", [`${blockClass}--has-decorator`]: !!decorator && decorator["type"]?.displayName !== "AILabel", ["is-visible"]: keepMounted ? open : true, [`${blockClass}--keep-mounted`]: keepMounted }), containerClassName: (0, import_classnames.default)(`${blockClass}__container`, containerClassName), onClose, open: keepMounted ? open : true, selectorPrimaryFocus, ref: mergedRefs, selectorsFloatingMenus: [ `.${carbonPrefix}--overflow-menu-options`, `.${carbonPrefix}--tooltip`, ".flatpickr-calendar", `.${blockClass}__container`, `.${carbonPrefix}--menu`, ...selectorsFloatingMenus ], isFullWidth: true, size: variant === "narrow" ? "sm" : "", "data-tearsheet-exiting": isExiting ? true : void 0 }, header, /* @__PURE__ */ React.createElement(ModalBody, { className: (0, import_classnames.default)(`${blockClass}__body-layout`, { [`${blockClass}__body-layout--has-influencer`]: influencer && !isSm }), ref: bodyRef }, influencer, body, footer)))); if (disablePortal) return content; return mountNode ? createPortal(content, mountNode) : content; }); /** * Wrapper component that handles presence logic and conditionally renders TearsheetInternal. * This ensures that all component state and effects are only initialized when the tearsheet is present. */ const Tearsheet = forwardRef((props, ref) => { const { open = false, keepMounted = false } = props; const presenceRef = useRef(null); const { isPresent, isExiting } = usePresence(presenceRef, keepMounted ? true : open); if (!keepMounted && !isPresent) return null; return /* @__PURE__ */ React.createElement(TearsheetInternal, { ...props, ref, presenceRef, isExiting }); }); Tearsheet.Header = TearsheetHeader; Tearsheet.HeaderContent = TearsheetHeaderContent; Tearsheet.Body = TearsheetBody; Tearsheet.Influencer = Influencer; Tearsheet.MainContent = MainContent; Tearsheet.SummaryContent = SummaryContent; Tearsheet.Footer = TearsheetFooter; Tearsheet.NavigationBar = TearsheetNavigationBar; Tearsheet.ScrollButton = TearsheetScrollButton; Tearsheet.HeaderActions = TearsheetHeaderActions; Tearsheet.HeaderActionItem = TearsheetHeaderActionItem; //#endregion export { Tearsheet };