@carbon/ibm-products
Version:
Carbon for IBM Products
898 lines (872 loc) • 41 kB
JavaScript
/**
* Copyright IBM Corp. 2020, 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.
*/
'use strict';
var _rollupPluginBabelHelpers = require('../../_virtual/_rollupPluginBabelHelpers.js');
var react = require('@carbon/react');
var React = require('react');
var TagSet = require('../TagSet/TagSet.js');
var layout = require('@carbon/layout');
var PageHeaderUtils = require('./PageHeaderUtils.js');
var propsHelper = require('../../global/js/utils/props-helper.js');
var icons = require('@carbon/react/icons');
var PageHeaderTitle = require('./PageHeaderTitle.js');
var index = require('../../_virtual/index.js');
var cx = require('classnames');
var devtools = require('../../global/js/utils/devtools.js');
var settings = require('../../settings.js');
var useResizeObserver = require('../../global/js/hooks/useResizeObserver.js');
var useOverflowString = require('../../global/js/hooks/useOverflowString.js');
var useWindowScroll = require('../../global/js/hooks/useWindowScroll.js');
var useWindowResize = require('../../global/js/hooks/useWindowResize.js');
var useIsomorphicEffect = require('../../global/js/hooks/useIsomorphicEffect.js');
var BreadcrumbWithOverflow = require('../BreadcrumbWithOverflow/BreadcrumbWithOverflow.js');
var ActionBar = require('../ActionBar/ActionBar.js');
var ButtonSetWithOverflow = require('../ButtonSetWithOverflow/ButtonSetWithOverflow.js');
const componentName = 'PageHeader';
settings.pkg._silenceWarnings(true);
settings.pkg.component.ActionBar = true;
// Default values for props
const defaults = {
fullWidthGrid: false,
narrowGrid: false,
breadcrumbOverflowTooltipAlign: 'right'
};
exports.PageHeader = /*#__PURE__*/React.forwardRef((_ref, ref) => {
let {
// The component props, in alphabetical order (for consistency).
actionBarItems,
actionBarMenuOptionsClass,
actionBarOverflowAriaLabel,
allTagsModalSearchLabel,
allTagsModalSearchPlaceholderText,
allTagsModalTitle,
hasBackgroundAlways: deprecated_hasBackgroundAlways,
breadcrumbOverflowAriaLabel,
breadcrumbLabel,
breadcrumbs,
children,
className,
collapseHeader,
collapseHeaderIconDescription,
collapseTitle,
disableBreadcrumbScroll: deprecated_disableBreadcrumbScroll,
enableBreadcrumbScroll,
expandHeaderIconDescription,
fullWidthGrid = defaults.fullWidthGrid,
hasCollapseHeaderToggle,
narrowGrid = defaults.narrowGrid,
navigation,
pageActions,
pageActionsOverflowLabel,
pageActionsMenuOptionsClass,
showAllTagsLabel,
subtitle,
tags,
title,
withoutBackground,
breadcrumbOverflowTooltipAlign = defaults.breadcrumbOverflowTooltipAlign,
// Collect any other property values passed in.
...rest
} = _ref;
// handle deprecated props - START
// if withoutBackground is nullish check deprecated_hasBackgroundAlways and default false
withoutBackground ??= !(deprecated_hasBackgroundAlways ?? true);
// prefer enabled if nullish check deprecated_disableBreadcrumbScroll and default false
enableBreadcrumbScroll ??= !(deprecated_disableBreadcrumbScroll ?? true);
// handle deprecated props - END
const [metrics, setMetrics] = React.useState({});
const [pageHeaderStyles, setPageHeaderStyles] = React.useState({
...rest?.style
});
// refs
const localHeaderRef = React.useRef(null);
const headerRef = ref || localHeaderRef;
const sizingContainerRef = React.useRef(null);
const offsetTopMeasuringRef = React.useRef(null);
const overflowMenuRef = React.useRef(null);
// state based on props only
const hasActionBar = actionBarItems && actionBarItems.length > 0;
const hasBreadcrumbRow = !!breadcrumbs || !!actionBarItems;
if (tags && tags?.length > 10) {
if (!allTagsModalSearchLabel) {
throw new Error("allTagsModalSearchLabel' is required.");
}
if (!allTagsModalSearchPlaceholderText) {
throw new Error("'allTagsModalSearchPlaceholderText' is required.");
}
if (!allTagsModalTitle) {
throw new Error("'allTagsModalTitle' is required.");
}
if (!showAllTagsLabel) {
throw new Error("'showAllTagsLabel' is required.");
}
}
// utility functions
const checkUpdateVerticalSpace = function () {
return PageHeaderUtils.utilCheckUpdateVerticalSpace(headerRef, offsetTopMeasuringRef, navigation, enableBreadcrumbScroll, hasActionBar || false, widthIsNarrow, setMetrics);
};
// NOTE: The buffer is used to add space between the bottom of the header and the last content
// Not pre-collapsed and (subtitle or children)
const lastRowBufferActive = (title || pageActions) && !collapseTitle || subtitle || children;
// state based on scroll/resize based effects
const [pageActionsInBreadcrumbRow, setPageActionsInBreadcrumbRow] = React.useState(false);
const [scrollYValue, setScrollYValue] = React.useState(0);
const [hasCollapseButton, setHasCollapseButton] = React.useState(false);
const [spaceForCollapseButton, setSpaceForCollapseButton] = React.useState(false);
const [actionBarMaxWidth, setActionBarMaxWidth] = React.useState(0);
const [actionBarMinWidth, setActionBarMinWidth] = React.useState(0);
const [pageActionInBreadcrumbMaxWidth, setPageActionInBreadcrumbMaxWidth] = React.useState(0);
const [pageActionInBreadcrumbMinWidth, setPageActionInBreadcrumbMinWidth] = React.useState(0);
const [actionBarColumnWidth, setActionBarColumnWidth] = React.useState(0);
const [fullyCollapsed, setFullyCollapsed] = React.useState(false);
const [widthIsNarrow, setWidthIsNarrow] = React.useState(false);
const prefix = react.usePrefix();
// handlers
const handleActionBarWidthChange = _ref2 => {
let {
minWidth,
maxWidth
} = _ref2;
if (minWidth !== actionBarMinWidth || maxWidth !== actionBarMaxWidth) {
let overflowMenuWidth = 0;
const overflowMenu = overflowMenuRef?.current?.querySelector(`.${prefix}--overflow-menu`);
if (overflowMenu) {
overflowMenuWidth = overflowMenu.offsetWidth;
}
/* don't know how to test resize */
/* istanbul ignore next */
setActionBarMaxWidth(maxWidth + overflowMenuWidth);
/* don't know how to test resize */
/* istanbul ignore next */
setActionBarMinWidth(minWidth);
}
};
const handlePageActionWidthChange = _ref3 => {
let {
minWidth,
maxWidth
} = _ref3;
/* don't know how to test resize */
/* istanbul ignore next */
setPageActionInBreadcrumbMaxWidth(maxWidth);
/* don't know how to test resize */
/* istanbul ignore next */
setPageActionInBreadcrumbMinWidth(minWidth);
};
/* istanbul ignore next */
const handleResizeActionBarColumn = _ref4 => {
let {
width
} = _ref4;
/* don't know how to test resize */
/* istanbul ignore next */
setActionBarColumnWidth(width);
};
/* istanbul ignore next */
const handleResize = () => {
// receives width and height parameters if needed
/* don't know how to test resize */
/* istanbul ignore next */
checkUpdateVerticalSpace();
};
const handleCollapseToggle = () => {
PageHeaderUtils.utilSetCollapsed(!fullyCollapsed, headerRef, metrics?.headerOffset, metrics?.headerTopValue);
};
// use effects
React.useEffect(() => {
/* istanbul ignore else */
if (pageActions?.content) {
const {
minWidth,
maxWidth
} = pageActions;
handlePageActionWidthChange({
minWidth,
maxWidth
});
}
}, [pageActions]);
React.useEffect(() => {
// Determine the location of the pageAction buttons
setPageActionsInBreadcrumbRow(collapseTitle || hasActionBar && !!metrics?.titleRowSpaceAbove && scrollYValue > metrics?.titleRowSpaceAbove || widthIsNarrow && !!metrics?.pageActionsSpaceAbove && scrollYValue > metrics?.pageActionsSpaceAbove);
}, [hasActionBar, metrics.breadcrumbRowSpaceBelow, metrics.titleRowSpaceAbove, metrics.pageActionsSpaceAbove, collapseTitle, scrollYValue, widthIsNarrow]);
React.useEffect(() => {
// Assesses the size of the action bar and page action area and their required
// space before setting their sizes
//
let newActionBarWidth = 'initial';
let newPageActionInBreadcrumbWidth = 'initial';
/* don't know how to test resize */
/* istanbul ignore if */
if (actionBarColumnWidth > 0) {
if (pageActionInBreadcrumbMaxWidth > 0 && actionBarColumnWidth > actionBarMaxWidth + pageActionInBreadcrumbMaxWidth) {
newPageActionInBreadcrumbWidth = `${pageActionInBreadcrumbMaxWidth}px`;
} else if (pageActionInBreadcrumbMinWidth > 0) {
newPageActionInBreadcrumbWidth = `${pageActionInBreadcrumbMinWidth}px`;
}
if (actionBarMaxWidth > 0 && actionBarColumnWidth > pageActionInBreadcrumbMinWidth + actionBarMaxWidth) {
newActionBarWidth = `${actionBarMaxWidth}px`;
} else {
if (actionBarMinWidth > 0) {
newActionBarWidth = `${actionBarColumnWidth - pageActionInBreadcrumbMinWidth}px`;
}
}
}
setPageHeaderStyles(prev => ({
...prev,
[`--${PageHeaderUtils.blockClass}--max-action-bar-width-px`]: newActionBarWidth,
[`--${PageHeaderUtils.blockClass}--button-set-in-breadcrumb-width-px`]: `${newPageActionInBreadcrumbWidth}`
}));
}, [actionBarColumnWidth, actionBarMaxWidth, actionBarMinWidth, pageActionInBreadcrumbMaxWidth, pageActionInBreadcrumbMinWidth, headerRef]);
React.useEffect(() => {
// Updates custom CSS props used to manage scroll behavior
/* istanbul ignore next */
setPageHeaderStyles(prev => ({
...prev,
[`--${PageHeaderUtils.blockClass}--height-px`]: `${metrics.headerHeight}px`,
[`--${PageHeaderUtils.blockClass}--width-px`]: `${metrics.headerWidth}px`,
[`--${PageHeaderUtils.blockClass}--header-top`]: `${(metrics?.headerTopValue || 0) + (metrics?.headerOffset || 0)}px`,
[`--${PageHeaderUtils.blockClass}--breadcrumb-title-visibility`]: scrollYValue > 0 ? 'visible' : 'hidden',
[`--${PageHeaderUtils.blockClass}--scroll`]: `${scrollYValue}`,
[`--${PageHeaderUtils.blockClass}--breadcrumb-title-top`]: `${metrics.breadcrumbTitleHeight && metrics.titleRowSpaceAbove && Math.max(0, metrics.breadcrumbTitleHeight + metrics.titleRowSpaceAbove - scrollYValue)}px`,
[`--${PageHeaderUtils.blockClass}--breadcrumb-title-opacity`]: `${Math.min(1, Math.max(0, (scrollYValue - (metrics.titleRowSpaceAbove || 0)) / (metrics.breadcrumbTitleHeight || 1) // don't want to divide by zero
))}`,
[`--${PageHeaderUtils.blockClass}--breadcrumb-row-width-px`]: `${metrics?.breadcrumbRowWidth}px`
}));
}, [headerRef, enableBreadcrumbScroll, metrics, metrics.breadcrumbRowHeight, metrics.breadcrumbRowSpaceBelow, metrics.breadcrumbTitleHeight, metrics.breadcrumbRowWidth, metrics.headerHeight, metrics.headerWidth, metrics.headerOffset, metrics.headerTopValue, metrics.navigationRowHeight, navigation, scrollYValue, tags]);
useWindowScroll.useNearestScroll(headerRef,
// on scroll or various layout changes check updates if needed
// istanbul ignore next
_ref5 => {
let {
current
} = _ref5;
setPageHeaderStyles(prev => ({
...prev,
[`--${PageHeaderUtils.blockClass}--breadcrumb-top`]: `${metrics.headerOffset}px`
}));
const fullyCollapsed = current.scrollY + metrics.headerTopValue + metrics.headerOffset >= 0;
setFullyCollapsed(fullyCollapsed);
// set offset for tagset tooltip
/* istanbul ignore next */
const tagsetTooltipOffset = fullyCollapsed && metrics?.headerHeight && metrics?.headerTopValue && metrics?.headerOffset ? metrics.headerHeight + metrics.headerTopValue + metrics.headerOffset : (metrics.headerHeight || 0) + (metrics.headerOffset || 0);
/* istanbul ignore next */
document.documentElement.style.setProperty(`--${PageHeaderUtils.blockClass}--tagset-tooltip-position`, fullyCollapsed ? 'fixed' : 'absolute');
document.documentElement.style.setProperty(`--${PageHeaderUtils.blockClass}--tagset-tooltip-offset`, `${tagsetTooltipOffset}px`);
setScrollYValue(current.scrollY);
}, [metrics.headerHeight, metrics.headerTopValue, metrics.headerOffset, enableBreadcrumbScroll]);
useWindowResize.useWindowResize(_ref6 => {
let {
current
} = _ref6;
// on window resize and other updates some values may have changed
checkUpdateVerticalSpace();
setWidthIsNarrow(current.innerWidth / 16 < parseInt(layout.breakpoints.md.width)); // small (below medium) media query
}, [actionBarItems, children, breadcrumbs, enableBreadcrumbScroll, navigation, pageActions, subtitle, tags, title]);
React.useEffect(() => {
checkUpdateVerticalSpace();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fullWidthGrid, narrowGrid]);
React.useEffect(() => {
// Determines the appropriate header background opacity based on the header config/height/scroll and the withoutBackground setting
let result = withoutBackground ? 0 : 1;
if (!result && metrics?.headerHeight && metrics.headerHeight > 0 && (breadcrumbs || actionBarItems || tags || navigation)) {
const startAddingAt = parseFloat(`${layout.spacing[9]}`) * parseInt(`${layout.baseFontSize}`);
const scrollRemaining = metrics.headerHeight - scrollYValue;
/* don't know how to test resize */
/* istanbul ignore if */
if (scrollRemaining < startAddingAt) {
const distanceAddingOver = startAddingAt - (metrics?.breadcrumbRowHeight || 0);
result = Math.min(1, (startAddingAt - scrollRemaining) / distanceAddingOver);
}
}
setPageHeaderStyles(prev => ({
...prev,
[`--${PageHeaderUtils.blockClass}--background-opacity`]: result
}));
}, [actionBarItems, withoutBackground, breadcrumbs, headerRef, metrics.breadcrumbRowHeight, metrics.headerHeight, navigation, scrollYValue, hasCollapseHeaderToggle, tags]);
React.useEffect(() => {
// only has toggle if requested and withoutBackground is unset/falsy
// NOTE: prop-types isRequired.if for the expand and collapse
// icon descriptions depends on the this.
setHasCollapseButton(hasCollapseHeaderToggle && !withoutBackground || false);
}, [withoutBackground, hasCollapseHeaderToggle]);
React.useEffect(() => {
// Determine if space is needed in the breadcrumb for a collapse button
if (hasCollapseButton && !(navigation || tags) && metrics?.headerHeight) {
setSpaceForCollapseButton(true);
} else {
setSpaceForCollapseButton(false);
}
}, [hasCollapseButton, navigation, tags, metrics.headerHeight]);
const nextToTabsCheck = () => {
/* istanbul ignore next */
return enableBreadcrumbScroll && !actionBarItems && metrics.headerTopValue && scrollYValue + metrics.headerTopValue >= 0;
};
React.useEffect(() => {
if (collapseHeader === true) {
PageHeaderUtils.utilSetCollapsed(collapseHeader, headerRef, metrics.headerOffset, metrics.headerTopValue);
}
}, [collapseHeader, metrics.headerOffset, metrics.headerTopValue, headerRef]);
useResizeObserver.useResizeObserver(sizingContainerRef, handleResizeActionBarColumn);
useResizeObserver.useResizeObserver(headerRef, handleResize);
// Determine what form of title to display in the breadcrumb
const breadcrumbItemForTitle = PageHeaderUtils.utilGetBreadcrumbItemForTitle(PageHeaderUtils.blockClass, collapseTitle, title);
const getBreadcrumbs = () => {
if (breadcrumbs && breadcrumbItemForTitle) {
return breadcrumbs.concat(breadcrumbItemForTitle);
} else {
if (breadcrumbItemForTitle) {
return [breadcrumbItemForTitle];
} else {
return breadcrumbs;
}
}
};
const displayedBreadcrumbs = getBreadcrumbs();
useIsomorphicEffect.useIsomorphicEffect(() => {
Object.keys(pageHeaderStyles).forEach(key => {
// check if style is a css var
if (key.startsWith('--')) {
headerRef.current.style.setProperty(key, pageHeaderStyles[key]);
} else {
headerRef.current.style[key] = pageHeaderStyles[key];
}
});
}, [headerRef, pageHeaderStyles]);
const subtitleRef = React.useRef(null);
const isOverflowing = useOverflowString.useOverflowStringHeight(subtitleRef);
const subtitleContent = /*#__PURE__*/React.createElement("span", {
ref: subtitleRef,
className: `${PageHeaderUtils.blockClass}__subtitle-text`
}, subtitle);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
className: `${PageHeaderUtils.blockClass}--offset-top-measuring-element`,
ref: offsetTopMeasuringRef
}), /*#__PURE__*/React.createElement("section", _rollupPluginBabelHelpers.extends({}, rest, {
className: cx([PageHeaderUtils.blockClass, `${PageHeaderUtils.blockClass}--no-margins-below-row`, className, {
[`${PageHeaderUtils.blockClass}--has-navigation`]: navigation || tags,
[`${PageHeaderUtils.blockClass}--has-navigation-tags-only`]: !navigation && tags,
[`${PageHeaderUtils.blockClass}--without-background`]: withoutBackground
}]),
ref: headerRef
}, devtools.getDevtoolsProps(componentName)), /*#__PURE__*/React.createElement(react.FlexGrid, {
fullWidth: fullWidthGrid === true || fullWidthGrid === 'xl',
narrow: narrowGrid,
className: cx({
[`${PageHeaderUtils.blockClass}--width--xl`]: fullWidthGrid === 'xl'
})
}, /*#__PURE__*/React.createElement("div", {
className: `${PageHeaderUtils.blockClass}__non-navigation-row-content`
}, hasBreadcrumbRow ? /*#__PURE__*/React.createElement(react.Row, {
className: cx(`${PageHeaderUtils.blockClass}__breadcrumb-row`, {
[`${PageHeaderUtils.blockClass}__breadcrumb-row--next-to-tabs`]: nextToTabsCheck(),
[`${PageHeaderUtils.blockClass}__breadcrumb-row--has-breadcrumbs`]: breadcrumbs || breadcrumbItemForTitle,
[`${PageHeaderUtils.blockClass}__breadcrumb-row--has-action-bar`]: hasActionBar || widthIsNarrow,
[`${PageHeaderUtils.blockClass}__has-page-actions-without-action-bar`]: !hasActionBar && !widthIsNarrow && pageActions,
[`${PageHeaderUtils.blockClass}__has-page-actions-with-title-collapsed`]: collapseTitle && pageActions
})
}, /*#__PURE__*/React.createElement("div", {
className: `${PageHeaderUtils.blockClass}__breadcrumb-row--container`
}, /*#__PURE__*/React.createElement(react.Column, {
className: cx(`${PageHeaderUtils.blockClass}__breadcrumb-column`, {
[`${PageHeaderUtils.blockClass}__breadcrumb-column--background`]: !!breadcrumbs || hasActionBar || widthIsNarrow
})
}, (breadcrumbs || breadcrumbItemForTitle) && /*#__PURE__*/React.createElement(BreadcrumbWithOverflow.BreadcrumbWithOverflow, {
className: `${PageHeaderUtils.blockClass}__breadcrumb`,
noTrailingSlash: !!title,
overflowAriaLabel: breadcrumbOverflowAriaLabel,
breadcrumbs: displayedBreadcrumbs,
overflowTooltipAlign: breadcrumbOverflowTooltipAlign,
maxVisible: undefined,
label: breadcrumbLabel
})), /*#__PURE__*/React.createElement(react.Column, {
className: cx([`${PageHeaderUtils.blockClass}__action-bar-column ${PageHeaderUtils.blockClass}__action-bar-column--background`, {
[`${PageHeaderUtils.blockClass}__action-bar-column--has-page-actions`]: pageActions,
[`${PageHeaderUtils.blockClass}__action-bar-column--influenced-by-collapse-button`]: spaceForCollapseButton
}])
}, /*#__PURE__*/React.createElement("div", {
className: `${PageHeaderUtils.blockClass}__action-bar-column-content`,
ref: sizingContainerRef
}, hasActionBar ?
/*#__PURE__*/
// Investigate the responsive behavior or this and the title also fix the ActionBar Item and PageAction story css
React.createElement(React.Fragment, null, thePageActions(true, pageActionsInBreadcrumbRow), /*#__PURE__*/React.createElement(ActionBar.ActionBar, {
actions: actionBarItems,
className: `${PageHeaderUtils.blockClass}__action-bar`,
menuOptionsClass: `${cx(actionBarMenuOptionsClass, `${PageHeaderUtils.blockClass}__action-bar-menu-options`)}`,
onWidthChange: handleActionBarWidthChange,
overflowAriaLabel: actionBarOverflowAriaLabel,
overflowMenuRef,
rightAlign: true
})) : (widthIsNarrow || pageActions) && thePageActions(true, pageActionsInBreadcrumbRow))))) : null, !collapseTitle && (title || pageActions) ? /*#__PURE__*/React.createElement(react.Row, {
className: cx(`${PageHeaderUtils.blockClass}__title-row`, {
[`${PageHeaderUtils.blockClass}__title-row--no-breadcrumb-row`]: !hasBreadcrumbRow,
[`${PageHeaderUtils.blockClass}__title-row--under-action-bar`]: hasActionBar || widthIsNarrow,
[`${PageHeaderUtils.blockClass}__title-row--has-page-actions`]: !!pageActions,
[`${PageHeaderUtils.blockClass}__title-row--sticky`]: !!pageActions && !actionBarItems && hasBreadcrumbRow
})
}, /*#__PURE__*/React.createElement(react.Column, {
className: `${PageHeaderUtils.blockClass}__title-column`
}, title ? /*#__PURE__*/React.createElement(PageHeaderTitle.PageHeaderTitle, {
blockClass: PageHeaderUtils.blockClass,
hasBreadcrumbRow: hasBreadcrumbRow,
title: title
}) : null), thePageActions(false, pageActionsInBreadcrumbRow)) : null, subtitle && /*#__PURE__*/React.createElement(react.Row, {
className: `${PageHeaderUtils.blockClass}__subtitle-row`
}, /*#__PURE__*/React.createElement(react.Column, {
className: `${PageHeaderUtils.blockClass}__subtitle`
}, isOverflowing ? /*#__PURE__*/React.createElement(react.DefinitionTooltip, {
definition: subtitle,
className: `${PageHeaderUtils.blockClass}__subtitle-tooltip`
}, subtitleContent) : subtitleContent)), children ? /*#__PURE__*/React.createElement(react.Row, {
className: `${PageHeaderUtils.blockClass}__available-row`
}, /*#__PURE__*/React.createElement(react.Column, {
className: `${PageHeaderUtils.blockClass}__available-column`
}, children)) : null, (breadcrumbs || actionBarItems || title || pageActions || children || subtitle) && /*#__PURE__*/React.createElement("div", {
className: cx([`${PageHeaderUtils.blockClass}__last-row-buffer`, {
[`${PageHeaderUtils.blockClass}__last-row-buffer--active`]: lastRowBufferActive
}])
}),
// this navigation row scrolls under the breadcrumb if there is one
tags && !navigation ? /*#__PURE__*/React.createElement(react.Row, {
className: cx(`${PageHeaderUtils.blockClass}__navigation-row`, {
[`${PageHeaderUtils.blockClass}__navigation-row--has-tags`]: tags
})
}, /*#__PURE__*/React.createElement(react.Column, {
className: cx(`${PageHeaderUtils.blockClass}__navigation-tags`, {
[`${PageHeaderUtils.blockClass}__navigation-tags--tags-only`]: !navigation
})
}, /*#__PURE__*/React.createElement(TagSet.TagSet, {
overflowAlign: "bottom-right",
allTagsModalSearchLabel,
allTagsModalSearchPlaceholderText,
allTagsModalTitle,
tags,
overflowClassName: `${PageHeaderUtils.blockClass}__navigation-tags-overflow`,
showAllTagsLabel: showAllTagsLabel || ''
}))) : null),
// this navigation pushes the breadcrumb off or settles underneath it depending on enableBreadcrumbScroll
navigation ? /*#__PURE__*/React.createElement(react.Row, {
className: cx(`${PageHeaderUtils.blockClass}__navigation-row`, {
[`${PageHeaderUtils.blockClass}__navigation-row--spacing-above-06`]: !!navigation,
[`${PageHeaderUtils.blockClass}__navigation-row--has-tags`]: tags
})
}, /*#__PURE__*/React.createElement(react.Column, {
className: `${PageHeaderUtils.blockClass}__navigation-tabs`
}, navigation), tags ? /*#__PURE__*/React.createElement(react.Column, {
className: cx(`${PageHeaderUtils.blockClass}__navigation-tags`, {
[`${PageHeaderUtils.blockClass}__navigation-tags--tags-only`]: !navigation
})
}, /*#__PURE__*/React.createElement(TagSet.TagSet, {
overflowAlign: "bottom-right",
allTagsModalSearchLabel,
allTagsModalSearchPlaceholderText,
allTagsModalTitle,
showAllTagsLabel: showAllTagsLabel || '',
tags,
overflowClassName: `${PageHeaderUtils.blockClass}__navigation-tags-overflow`
})) : null) : null), hasCollapseButton ? /*#__PURE__*/React.createElement("div", {
className: cx(`${PageHeaderUtils.blockClass}__collapse-expand-toggle`, {
[`${PageHeaderUtils.blockClass}__collapse-expand-toggle--collapsed`]: fullyCollapsed
})
}, /*#__PURE__*/React.createElement(react.Button, {
hasIconOnly: true,
iconDescription: /* istanbul ignore next */
fullyCollapsed ? expandHeaderIconDescription : collapseHeaderIconDescription,
kind: "ghost",
onClick: handleCollapseToggle,
renderIcon: props => /*#__PURE__*/React.createElement(icons.ChevronUp, _rollupPluginBabelHelpers.extends({
size: 16
}, props)),
size: "md",
tooltipPosition: "bottom",
tooltipAlignment: "end",
type: "button"
})) : null));
function thePageActions(isBreadcrumbRow, inBreadcrumbRow) {
if (pageActions) {
const Tag = isBreadcrumbRow ? 'div' : react.Column;
// Only report size change of version action bar is rendered as part of the breadcrumb row.
// and when there is an actionBar
const handleWidthChange = isBreadcrumbRow && hasBreadcrumbRow ? handlePageActionWidthChange : () => {};
return /*#__PURE__*/React.createElement(Tag, {
className: cx(`${PageHeaderUtils.blockClass}__page-actions`, {
[`${PageHeaderUtils.blockClass}__page-actions--in-breadcrumb`]: inBreadcrumbRow
})
}, /*#__PURE__*/React.createElement("div", {
className: cx(`${PageHeaderUtils.blockClass}__page-actions-content`)
}, pageActions?.content ?? /*#__PURE__*/React.createElement(ButtonSetWithOverflow.ButtonSetWithOverflow, {
className: `${PageHeaderUtils.blockClass}__button-set-with-overflow`,
menuOptionsClass: cx(pageActionsMenuOptionsClass, `${PageHeaderUtils.blockClass}__button-set-menu-options`),
onWidthChange: handleWidthChange,
buttons: pageActions,
buttonSetOverflowLabel: pageActionsOverflowLabel,
rightAlign: !widthIsNarrow
})));
}
}
});
// Return a placeholder if not released and not enabled by feature flag
exports.PageHeader = settings.pkg.checkComponentEnabled(exports.PageHeader, componentName);
// copied from carbon-components-react/src/components/Tag/Tag.js for DocGen
const TYPES = {
red: 'Red',
magenta: 'Magenta',
purple: 'Purple',
blue: 'Blue',
cyan: 'Cyan',
teal: 'Teal',
green: 'Green',
gray: 'Gray',
'cool-gray': 'Cool-Gray',
'warm-gray': 'Warm-Gray',
'high-contrast': 'High-Contrast',
outline: 'Outline'
};
const tagTypes = Object.keys(TYPES);
const deprecatedProps = {
/**
* **Deprecated** see property `enableBreadcrumbScroll`
*/
disableBreadcrumbScroll: propsHelper.deprecateProp(index.default.bool, 'Property replaced by `enableBreadcrumbScroll`'),
/**
* **Deprecated** see property `withoutBackground`
*/
hasBackgroundAlways: propsHelper.deprecateProp(index.default.bool, 'Property replaced by `withoutBackground`')
};
/**@ts-ignore */
exports.PageHeader.tagTypes = tagTypes;
exports.PageHeader.propTypes = {
/**
* Specifies the action bar items which are the final items in the row top of the PageHeader.
* Each item is specified as an object with the properties of a Carbon Button in icon only form.
* Button kind, size, tooltipPosition, tooltipAlignment and type are ignored.
*/
/**@ts-ignore */
actionBarItems: index.default.arrayOf(index.default.shape({
/**@ts-ignore*/
...propsHelper.prepareProps(react.Button.propTypes, ['kind', 'size', 'tooltipPosition', 'tooltipAlignment']),
iconDescription: index.default.string.isRequired,
/**@ts-ignore*/
onClick: react.Button.propTypes.onClick,
/**@ts-ignore*/
renderIcon: react.Button.propTypes.renderIcon.isRequired
})),
/**
* class name applied to the action bar overflow options
*/
actionBarMenuOptionsClass: index.default.string,
/**
* When there is insufficient space for all actionBarItems to be displayed this
* aria label is used for the action bar overflow menu
*
* NOTE: This prop is required if actionBarItems are supplied
*/
/**@ts-ignore */
actionBarOverflowAriaLabel: index.default.string.isRequired.if(_ref7 => {
let {
actionBarItems
} = _ref7;
return actionBarItems && actionBarItems.length > 0;
}),
/**
* When tags are supplied there may not be sufficient space to display all of the tags. This results in an overflow
* menu being shown. If in the overflow menu there is still insufficient space this label is used in a dialog showing
* all tags.
*
* **Note: Required if more than 10 tags**
*/
allTagsModalSearchLabel: TagSet.string_required_if_more_than_10_tags,
/**
* When tags are supplied there may not be sufficient space to display all of the tags. This results in an overflow
* menu being shown. If in the overflow menu there is still insufficient space this placeholder is used in a dialog
* showing all tags.
*
* **Note: Required if more than 10 tags**
*/
allTagsModalSearchPlaceholderText: TagSet.string_required_if_more_than_10_tags,
/**
* When tags are supplied there may not be sufficient space to display all of the tags. This results in an overflow
* menu being shown. If in the overflow menu there is still insufficient space this title is used in a dialog showing
* all tags.
*
* **Note: Required if more than 10 tags**
*/
allTagsModalTitle: TagSet.string_required_if_more_than_10_tags,
/**
* If the user supplies breadcrumbs then this property is required.
* It is used in an overflow menu when there is insufficient space to display all breadcrumbs inline.
*/
/**@ts-ignore */
breadcrumbOverflowAriaLabel: index.default.string.isRequired.if(_ref8 => {
let {
breadcrumbs
} = _ref8;
return breadcrumbs && breadcrumbs.length > 0;
}),
/**
* align breadcrumb overflow tooltip
*/
breadcrumbOverflowTooltipAlign: react.Tooltip.propTypes.align,
/**
* Specifies the breadcrumb components to be shown in the breadcrumb area of
* the page header. Each item is specified as an object with optional fields
* 'label' to supply the breadcrumb label, 'href' to supply the link location,
* and 'isCurrentPage' to specify whether this breadcrumb component represents
* the current page. Each item should also include a unique 'key' field to
* enable efficient rendering, and if the label is not a string then a 'title'
* field is required to provide a text alternative for display. Any other
* fields in the object will be passed through to the breadcrumb element as
* HTML attributes.
*/
/**@ts-ignore */
breadcrumbs: index.default.arrayOf(index.default.shape({
/**
* Optional string representing the link location for the BreadcrumbItem
*/
href: index.default.string,
/**
* Provide if this breadcrumb item represents the current page
*/
isCurrentPage: index.default.bool,
/**
* Key required to render array efficiently
*/
key: index.default.string.isRequired,
/**
* Pass in content that will be inside of the BreadcrumbItem
*/
label: index.default.node,
/**
* A text version of the `label` for display, required if `label` is not a string.
*/
/**@ts-ignore */
title: index.default.string.isRequired.if(_ref9 => {
let {
label
} = _ref9;
return typeof label !== 'string';
})
})),
/**
* A zone for placing high-level, client content above the page tabs.
* Accepts arbitrary renderable content as a React node. Optional.
*/
children: index.default.node,
/**
* Specifies class(es) to be applied to the top-level PageHeader node.
* Optional.
*/
className: index.default.string,
/**
* The header can as a whole be collapsed, expanded or somewhere in between.
* This setting controls the initial value, but also takes effect on change
*
* NOTE: The header is collapsed by setting the scroll position to hide part of the header.
* Collapsing has no effect if there is insufficient content to scroll.
*/
collapseHeader: index.default.bool,
/**
* If `hasCollapseHeaderToggle` is set and `withoutBackground` is unset/falsy then assistive text is
* required for both the expend and collapse states of the button component used.
*/
/**@ts-ignore */
collapseHeaderIconDescription: index.default.string.isRequired.if(_ref10 => {
let {
withoutBackground,
hasCollapseHeaderToggle
} = _ref10;
return !withoutBackground && hasCollapseHeaderToggle;
}),
/**
* The title row typically starts below the breadcrumb row. This option
* preCollapses it into the breadcrumb row.
*/
collapseTitle: index.default.bool,
/**
* Standard keeps the breadcrumb on the page. This option allows the breadcrumb
* to scroll off
*/
enableBreadcrumbScroll: index.default.bool,
/**
* If `hasCollapseHeaderToggle` is set and `withoutBackground` is unset/falsy then assistive text is
* required for both the expend and collapse states of the button component used.
*/
/**@ts-ignore */
expandHeaderIconDescription: index.default.string.isRequired.if(_ref11 => {
let {
withoutBackground,
hasCollapseHeaderToggle
} = _ref11;
return !withoutBackground && hasCollapseHeaderToggle;
}),
/**
* The PageHeader is hosted in a Carbon grid, this value is passed through to the Carbon grid fullWidth prop.
* 'xl' is used to override the grid width setting. Can be used with narrowGrid: true to get the largest size.
*/
/**@ts-ignore */
fullWidthGrid: index.default.oneOfType([index.default.bool, index.default.oneOf(['xl'])]),
/**
* Adds a button as the last element of the bottom row which collapses and expands the header.
*
* NOTE: The header is collapsed by setting the scroll position to hide part of the header.
* Collapsing has no effect if there is insufficient content to scroll.
*/
/**@ts-ignore */
hasCollapseHeaderToggle: index.default.bool,
/**
* The PageHeader is hosted in a Carbon grid, this value is passed through to the Carbon grid narrow prop
*/
narrowGrid: index.default.bool,
/**
* Content for the navigation area in the PageHeader. Should
* be a React element that is normally a Carbon Tabs component. Optional.
*/
navigation: index.default.element,
// Supports Tabs
/**
* Specifies the primary page actions which are placed at the same level in the page as the title.
*
* Either a set of actions, each specified as an object with the properties of a Carbon Button plus:
*
* - label: node
*
* Or a single object
*
* - content: content to be rendered. NOTE: must be capable of restricting itself to the space provided. This 2.5rem height ($spacing-08)
* and the width not used by action bar items when scrolled into toolbar.
* - minWidth: smallest number of pixel width the content would like. NOTE: This is not guaranteed and may be less on small viewports.
* - maxWidth: maximum number of pixels the content will grow to
* Carbon Button API https://react.carbondesignsystem.com/?path=/docs/components-button--default#component-api
*/
/**@ts-ignore */
pageActions: index.default.oneOfType([index.default.arrayOf(index.default.shape({
/**@ts-ignore*/
...react.Button.propTypes,
key: index.default.string.isRequired,
/**@ts-ignore*/
kind: react.Button.propTypes.kind,
label: index.default.node,
onClick: index.default.func
})), index.default.shape({
/**
* minWidth should not be more than 180
* The content is expected to adjust itself to fit in
*/
content: index.default.node.isRequired,
minWidth: index.default.number.isRequired,
maxWidth: index.default.number.isRequired
})]),
/**
* class name applied to the page actions overflow options
*/
pageActionsMenuOptionsClass: index.default.string,
/**
* When there is insufficient space to display all of hte page actions inline a dropdown button menu is shown,
* containing the page actions. This label is used as the display content of the dropdown button menu.
*
* NOTE: This prop is required if pageActions are supplied
*/
/**@ts-ignore */
pageActionsOverflowLabel: index.default.node.isRequired.if(_ref12 => {
let {
pageActions
} = _ref12;
return pageActions && pageActions.length > 0 && !pageActions.content;
}),
/**
* When tags are supplied there may not be sufficient space to display all of the tags. This results in an overflow
* menu being shown. If in the overflow menu there is still insufficient space this label is used to offer a
* "View all tags" option.
*
* **Note: Required if more than 10 tags**
*/
showAllTagsLabel: TagSet.string_required_if_more_than_10_tags,
/**
* Sitting just below the title is this optional subtitle that provides additional context to
* identify the current page.
*/
subtitle: index.default.node,
/**
* An array of tags to be shown as the final content in the PageHeader.
*
* Each tag is specified as an object with the following properties
* **label**\* (required) to supply the tag content, and properties of the the Carbon Tag component,
* such as **type**, **disabled**, **ref**, **className** , and any other Tag props.
*
* NOTE: **filter** is not supported. Any remaining fields in the object will be passed through to the HTML element
* as HTML attributes.
*
* See https://react.carbondesignsystem.com/?path=/docs/components-tag--default
*/
/**@ts-ignore */
tags: index.default.arrayOf(index.default.shape({
...propsHelper.prepareProps(react.Tag.propTypes, 'filter'),
label: index.default.string.isRequired,
// we duplicate this prop to improve the DocGen
type: index.default.oneOf(tagTypes)
})),
/**
* An optional page title supplied as a string or object with the following attributes: text, icon, loading
*
* Can be supplied either as:
* - String
* - Object containing
* - text: title string
* - shortTitle: alternative title for exceptionally long titles
* - icon: optional icon
* - loading: boolean shows loading indicator if true
* - onChange: function to process the live value (React change === HTML Input)
* - onSave: function to process a confirmed change
* - editableLabel: label for edit required if onChange supplied
* - cancelDescription: label for edit cancel button
* - saveDescription: label for edit save button
* - tooltipAlignment: position for tooltip displayed for large titles. Default to "bottom".
* - Object containing user defined contents. These must fit within the area defined for the title in both main part of the header and the breadcrumb.
* - content: title or name of current location shown in main part of page header
* - breadcrumbContent: version of content used in the breadcrumb on scroll. If not supplied
* - asText: String based representation of the title
*/
/**@ts-ignore */
title: index.default.oneOfType([index.default.shape({
// Update docgen if changed
text: index.default.string.isRequired,
shortTitle: index.default.string,
icon: index.default.oneOfType([index.default.func, index.default.object]),
loading: index.default.bool,
// inline edit version properties
editableLabel: index.default.string,
// .isRequired.if(editInPlaceRequired),
id: index.default.string,
// .isRequired.if(editInPlaceRequired),
onCancel: index.default.func,
onChange: index.default.func,
onSave: index.default.func,
cancelDescription: index.default.string,
//.isRequired.if(editInPlaceRequired),
editDescription: index.default.string,
// .isRequired.if(editInPlaceRequired),
saveDescription: index.default.string,
//.isRequired.if(editInPlaceRequired),
tooltipAlignment: index.default.oneOf(['top', 'top-left', 'top-right', 'bottom', 'bottom-left', 'bottom-right', 'left', 'right'])
// Update docgen if changed
}), index.default.string, index.default.shape({
content: index.default.node.isRequired,
breadcrumbContent: index.default.node,
asText: index.default.string.isRequired
})]),
/**
* Specifies if the PageHeader should appear without a background color, and defaults to the preferred `false` (a background color is shown).
* Note that when `true` some parts of the header still gain a background if and when they stick to the top of the PageHeader on scroll.
*/
/**@ts-ignore */
withoutBackground: index.default.bool,
...deprecatedProps
};
exports.PageHeader.displayName = componentName;
exports.deprecatedProps = deprecatedProps;