UNPKG

@carbon/react

Version:

React components for the Carbon Design System

328 lines (326 loc) 12.5 kB
/** * Copyright IBM Corp. 2016, 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 { usePrefix } from "../../internal/usePrefix.js"; import { Text } from "../Text/Text.js"; import useIsomorphicEffect from "../../internal/useIsomorphicEffect.js"; import { useId } from "../../internal/useId.js"; import AspectRatio from "../AspectRatio/AspectRatio.js"; import { Popover, PopoverContent } from "../Popover/index.js"; import { DefinitionTooltip } from "../Tooltip/DefinitionTooltip.js"; import { MenuItem } from "../Menu/MenuItem.js"; import { GridAsGridComponent } from "../Grid/Grid.js"; import Column from "../Grid/Column.js"; import { MenuButton } from "../MenuButton/index.js"; import { useMatchMedia } from "../../internal/useMatchMedia.js"; import Tag from "../Tag/Tag.js"; import OperationalTag from "../Tag/OperationalTag.js"; import useOverflowItems from "../../internal/useOverflowItems.js"; import classNames from "classnames"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import PropTypes from "prop-types"; import { Fragment, jsx, jsxs } from "react/jsx-runtime"; import { createOverflowHandler } from "@carbon/utilities"; import { breakpoints } from "@carbon/layout"; //#region src/components/PageHeader/PageHeader.tsx /** * Copyright IBM Corp. 2025, 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. */ const PageHeader = React.forwardRef(({ className, children, ...other }, ref) => { return /* @__PURE__ */ jsx("div", { className: classNames({ [`${usePrefix()}--page-header`]: true }, className), ref, ...other, children }); }); PageHeader.displayName = "PageHeader"; const PageHeaderBreadcrumbBar = React.forwardRef(({ border = true, className, children, renderIcon: IconElement, contentActions, contentActionsFlush, pageActions, pageActionsFlush, ...other }, ref) => { const prefix = usePrefix(); const classNames$1 = classNames({ [`${prefix}--page-header__breadcrumb-bar`]: true, [`${prefix}--page-header__breadcrumb-bar-border`]: border, [`${prefix}--page-header__breadcrumb__actions-flush`]: pageActionsFlush }, className); const contentActionsClasses = classNames({ [`${prefix}--page-header__breadcrumb__content-actions`]: !contentActionsFlush }); return /* @__PURE__ */ jsx("div", { className: classNames$1, ref, ...other, children: /* @__PURE__ */ jsx(GridAsGridComponent, { children: /* @__PURE__ */ jsx(Column, { lg: 16, md: 8, sm: 4, children: /* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__breadcrumb-container`, children: [/* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__breadcrumb-wrapper`, children: [IconElement && /* @__PURE__ */ jsx("div", { className: `${prefix}--page-header__breadcrumb__icon`, children: /* @__PURE__ */ jsx(IconElement, {}) }), children] }), /* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__breadcrumb__actions`, children: [/* @__PURE__ */ jsx("div", { className: contentActionsClasses, children: contentActions }), pageActions] })] }) }) }) }); }); PageHeaderBreadcrumbBar.displayName = "PageHeaderBreadcrumbBar"; const PageHeaderContent = React.forwardRef(({ className, children, title, renderIcon: IconElement, contextualActions, pageActions, ...other }, ref) => { const prefix = usePrefix(); const classNames$2 = classNames({ [`${prefix}--page-header__content`]: true }, className); const titleRef = useRef(null); const [isEllipsisApplied, setIsEllipsisApplied] = useState(false); const isEllipsisActive = (element) => { setIsEllipsisApplied(element.offsetHeight < element.scrollHeight); return element.offsetHeight < element.scrollHeight; }; useIsomorphicEffect(() => { if (titleRef.current) isEllipsisActive(titleRef.current); }, [title]); return /* @__PURE__ */ jsx("div", { className: classNames$2, ref, ...other, children: /* @__PURE__ */ jsx(GridAsGridComponent, { children: /* @__PURE__ */ jsxs(Column, { lg: 16, md: 8, sm: 4, children: [/* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__content__title-wrapper`, children: [/* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__content__start`, children: [/* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__content__title-container`, children: [IconElement && /* @__PURE__ */ jsx("div", { className: `${prefix}--page-header__content__icon`, children: /* @__PURE__ */ jsx(IconElement, {}) }), isEllipsisApplied ? /* @__PURE__ */ jsx(DefinitionTooltip, { definition: title, children: /* @__PURE__ */ jsx(Text, { ref: titleRef, as: "h4", className: `${prefix}--page-header__content__title`, children: title }) }) : /* @__PURE__ */ jsx(Text, { ref: titleRef, as: "h4", className: `${prefix}--page-header__content__title`, children: title })] }), contextualActions && /* @__PURE__ */ jsx("div", { className: `${prefix}--page-header__content__contextual-actions`, children: contextualActions })] }), pageActions] }), children] }) }) }); }); PageHeaderContent.displayName = "PageHeaderContent"; PageHeaderContent.propTypes = { children: PropTypes.node, className: PropTypes.string, renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), title: PropTypes.string.isRequired, subtitle: PropTypes.string, contextualActions: PropTypes.node, pageActions: PropTypes.node }; const PageHeaderContentPageActions = ({ className, children, menuButtonLabel = "Actions", actions, ...other }) => { const classNames$3 = classNames({ [`${usePrefix()}--page-header__content__page-actions`]: true }, className); const containerRef = useRef(null); const offsetRef = useRef(null); const [menuButtonVisibility, setMenuButtonVisibility] = useState(false); const [hiddenItems, setHiddenItems] = useState([]); useIsomorphicEffect(() => { if (menuButtonVisibility && offsetRef.current) { const width = offsetRef.current.offsetWidth; document.documentElement.style.setProperty("--pageheader-title-grid-width", `${width}px`); } }, [menuButtonVisibility]); useEffect(() => { if (!containerRef.current || !Array.isArray(actions)) return; createOverflowHandler({ container: containerRef.current, maxVisibleItems: containerRef.current.children.length - 1, onChange: (visible, hidden) => { setHiddenItems(actions?.slice(visible.length)); if (hidden.length > 0) setMenuButtonVisibility(true); } }); }, []); return /* @__PURE__ */ jsx("div", { className: classNames$3, ref: containerRef, ...other, children: actions && /* @__PURE__ */ jsx(Fragment, { children: Array.isArray(actions) && /* @__PURE__ */ jsxs(Fragment, { children: [actions.map((action) => /* @__PURE__ */ jsx("div", { children: React.cloneElement(action.body, { ...action.body.props, onClick: action.onClick }) }, action.id)), /* @__PURE__ */ jsx("span", { "data-offset": true, "data-hidden": true, ref: offsetRef, children: /* @__PURE__ */ jsx(MenuButton, { menuAlignment: "bottom-end", label: menuButtonLabel, size: "md", children: [...hiddenItems].reverse().map((item) => /* @__PURE__ */ jsx(MenuItem, { onClick: item.onClick, ...item.menuItem }, item.id)) }) })] }) }) }); }; PageHeaderContentPageActions.displayName = "PageHeaderContentPageActions"; PageHeaderContentPageActions.propTypes = { children: PropTypes.node, className: PropTypes.string, menuButtonLabel: PropTypes.string, actions: PropTypes.oneOfType([PropTypes.node, PropTypes.array]) }; const PageHeaderContentText = ({ className, children, subtitle, ...other }) => { const prefix = usePrefix(); return /* @__PURE__ */ jsxs("div", { className: classNames({ [`${prefix}--page-header__content__body`]: true }, className), ...other, children: [subtitle && /* @__PURE__ */ jsx(Text, { as: "h3", className: `${prefix}--page-header__content__subtitle`, children: subtitle }), children] }); }; PageHeaderContentText.displayName = "PageHeaderContentText"; PageHeaderContentText.propTypes = { children: PropTypes.node, className: PropTypes.string, subtitle: PropTypes.string }; const PageHeaderHeroImage = ({ className, children, ...other }) => { const classNames$4 = classNames({ [`${usePrefix()}--page-header__hero-image`]: true }, className); const isLg = useMatchMedia(`(min-width: ${breakpoints.lg.width})`); return /* @__PURE__ */ jsx(AspectRatio, { className: classNames$4, ...other, ratio: isLg ? "2x1" : "3x2", children }); }; PageHeaderHeroImage.displayName = "PageHeaderHeroImage"; PageHeaderHeroImage.propTypes = { children: PropTypes.node, className: PropTypes.string }; const PageHeaderTabBar = React.forwardRef(({ className, children, tags = [], ...other }, ref) => { const prefix = usePrefix(); const classNames$5 = classNames({ [`${prefix}--page-header__tab-bar`]: true }, className); const [openPopover, setOpenPopover] = useState(false); const tagSize = tags[0]?.size || "md"; const instanceId = useId("PageHeaderTabBar"); const tagsWithIds = useMemo(() => { return tags.map((tag, index) => ({ ...tag, id: tag.id || `tag-${index}-${instanceId}` })); }, [instanceId, tags]); const tagsContainerRef = useRef(null); const offsetRef = useRef(null); useEffect(() => { const handleResize = () => { setOpenPopover(false); }; window.addEventListener("resize", handleResize); return () => { window.removeEventListener("resize", handleResize); }; }, []); const { visibleItems = [], hiddenItems = [], itemRefHandler = () => {} } = useOverflowItems(tagsWithIds, tagsContainerRef, offsetRef) || { visibleItems: [], hiddenItems: [], itemRefHandler: () => {} }; const handleOverflowClick = useCallback((event) => { event.stopPropagation(); setOpenPopover((prev) => !prev); }, []); const renderTags = () => /* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__tags`, ref: tagsContainerRef, children: [visibleItems.map((tag) => /* @__PURE__ */ jsx(Tag, { ref: (node) => itemRefHandler(tag.id, node), type: tag.type, size: tag.size, className: `${prefix}--page-header__tag-item`, children: tag.text }, tag.id)), hiddenItems.length > 0 && /* @__PURE__ */ jsxs(Popover, { open: openPopover, onRequestClose: () => setOpenPopover(false), children: [/* @__PURE__ */ jsx(OperationalTag, { onClick: handleOverflowClick, "aria-expanded": openPopover, text: `+${hiddenItems.length}`, size: tagSize }), /* @__PURE__ */ jsx(PopoverContent, { className: "tag-popover-content", children: /* @__PURE__ */ jsx("div", { className: `${prefix}--page-header__tags-popover-list`, children: hiddenItems.map((tag) => /* @__PURE__ */ jsx(Tag, { type: tag.type, size: tag.size, children: tag.text }, tag.id)) }) })] })] }); return /* @__PURE__ */ jsx("div", { className: classNames$5, ref, ...other, children: /* @__PURE__ */ jsx(GridAsGridComponent, { children: /* @__PURE__ */ jsx(Column, { lg: 16, md: 8, sm: 4, children: /* @__PURE__ */ jsxs("div", { className: `${prefix}--page-header__tab-bar--tablist`, children: [children, tags.length > 0 && renderTags()] }) }) }) }); }); PageHeaderTabBar.displayName = "PageHeaderTabBar"; /** * ------- * Exports * ------- */ const Root = PageHeader; Root.displayName = "PageHeader.Root"; const BreadcrumbBar = PageHeaderBreadcrumbBar; BreadcrumbBar.displayName = "PageHeaderBreadcrumbBar"; const Content = PageHeaderContent; Content.displayName = "PageHeaderContent"; const ContentPageActions = PageHeaderContentPageActions; ContentPageActions.displayName = "PageHeaderContentPageActions"; const ContentText = PageHeaderContentText; ContentText.displayName = "PageHeaderContentText"; const HeroImage = PageHeaderHeroImage; HeroImage.displayName = "PageHeaderHeroImage"; const TabBar = PageHeaderTabBar; TabBar.displayName = "PageHeaderTabBar"; //#endregion export { BreadcrumbBar, Content, ContentPageActions, ContentText, HeroImage, PageHeader, PageHeaderBreadcrumbBar, PageHeaderContent, PageHeaderContentPageActions, PageHeaderContentText, PageHeaderHeroImage, PageHeaderTabBar, Root, TabBar };