@carbon/ibm-products
Version:
Carbon for IBM Products
192 lines (176 loc) • 7.66 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.
*/
import React__default from 'react';
import cx from 'classnames';
import { pkg } from '../../settings.js';
import { scrollableAncestor } from '../../global/js/utils/scrollableAncestor.js';
import { SkeletonText } from '@carbon/react';
var _SkeletonText;
const blockClass = `${pkg.prefix}--page-header`;
/**
* Assesses the vertical height of various elements and calls setMetrics with update
* @param {{}} headerRef
* @param {{}} offsetTopMeasuringRef
* @param {object} navigation
* @param {boolean} enableBreadcrumbScroll
* @param {boolean} hasActionBar
* @param {boolean} widthIsNarrow
* @param {()} setMetrics
*/
const utilCheckUpdateVerticalSpace = (headerRef, offsetTopMeasuringRef, navigation, enableBreadcrumbScroll, hasActionBar, widthIsNarrow, setMetrics) => {
const dynamicRefs = {};
const getDynamicRef = selector => {
// would love to do this differently but digging in the dom seems easier
// than getting a ref to a conditionally rendered item
/* don't know how to test resize */
/* istanbul ignore if */
if (!headerRef.current) {
return undefined;
} else {
let dRef = dynamicRefs[selector];
/* istanbul ignore else */
if (!dRef || /* istanbul ignore next */dRef.parentNode === null) {
dynamicRefs[selector] = headerRef.current.querySelector(selector);
}
}
return dynamicRefs[selector];
};
setMetrics(previous => {
// Utility function that checks the heights of various elements which are used to determine layout
const update = {};
const breadcrumbTitleEl = getDynamicRef(`.${blockClass}__breadcrumb-title`);
const breadcrumbRowEl = getDynamicRef(`.${blockClass}__breadcrumb-row`);
const titleRowEl = getDynamicRef(`.${blockClass}__title-row`);
const subtitleRowEl = getDynamicRef(`.${blockClass}__subtitle-row`);
const availableRowEl = getDynamicRef(`.${blockClass}__available-row`);
const navigationRowEl = getDynamicRef(`.${blockClass}__navigation-row`);
const pageActionsEl = getDynamicRef(`.${blockClass}__page-actions`);
/* istanbul ignore next */
update.headerHeight = headerRef.current ? headerRef.current.clientHeight : 0;
/* istanbul ignore next */
update.headerWidth = headerRef.current ? headerRef.current.offsetWidth : 0;
// The header offset is the vertical distance from the top of the document to
// the page header, which we obtain using getBoundingClientRect() for robust
// behavior. We use this offset as the scroll/fixed threshold.
const scrollableContainer = scrollableAncestor(headerRef.current);
/* istanbul ignore next */
const scrollableContainerTop = scrollableContainer ? scrollableContainer.getBoundingClientRect().top : 0;
/* istanbul ignore next */
const offsetMeasuringTop = offsetTopMeasuringRef.current ? offsetTopMeasuringRef.current.getBoundingClientRect().top : 0;
// The header offset calculation is either going to work out at 0 if we have no gap between scrolling container
// top and the measuring ref top, or the difference between.
update.headerOffset = offsetMeasuringTop !== 0 ? offsetMeasuringTop - scrollableContainerTop : 0;
/* istanbul ignore next */
update.breadcrumbRowHeight = breadcrumbRowEl ? breadcrumbRowEl.clientHeight : 0;
/* istanbul ignore next */
update.breadcrumbRowWidth = breadcrumbRowEl ? breadcrumbRowEl.offsetWidth : 0;
/* istanbul ignore next */
update.breadcrumbTitleHeight = breadcrumbTitleEl ? breadcrumbTitleEl.offsetHeight // clientHeight returns 0 when window small
: 1;
/* istanbul ignore next */
update.titleRowHeight = titleRowEl ? titleRowEl.clientHeight : 0;
/* istanbul ignore next */
update.subtitleRowHeight = subtitleRowEl ? subtitleRowEl.clientHeight : 0;
/* istanbul ignore next */
update.availableRowHeight = availableRowEl ? availableRowEl.clientHeight : 0;
/* istanbul ignore next */
update.navigationRowHeight = navigationRowEl ? navigationRowEl.clientHeight : 1;
// Base for calculating sticky top
update.headerTopValue = -update.headerHeight;
if (navigation) {
// adjust top for sticky with navigation
update.headerTopValue += update.navigationRowHeight;
}
if (!enableBreadcrumbScroll || !navigation) {
// adjust sticky top if no navigation or breadcrumb is to stay on screen
update.headerTopValue += update.breadcrumbRowHeight;
}
if (enableBreadcrumbScroll) {
// adjust header top value when scroll enabled for breadcrumb
update.headerTopValue -= navigation ? hasActionBar ? 0 : 10 : update.headerHeight;
}
// if (window) {
let val;
/* don't know how to test resize */
/* istanbul ignore if */
if (breadcrumbRowEl) {
val = parseFloat(window.getComputedStyle(breadcrumbRowEl).getPropertyValue('margin-bottom'), 10);
update.breadcrumbRowSpaceBelow = isNaN(val) ? 0 : val;
}
/* don't know how to test resize */
/* istanbul ignore if */
if (titleRowEl) {
val = parseFloat(window.getComputedStyle(titleRowEl).getPropertyValue('margin-top'), 10);
update.titleRowSpaceAbove = isNaN(val) ? 0 : val;
if (pageActionsEl) {
val = parseFloat(window.getComputedStyle(pageActionsEl).getPropertyValue('margin-top'), 10);
update.pageActionsSpaceAbove = titleRowEl.clientHeight - pageActionsEl.clientHeight + update.titleRowSpaceAbove - (isNaN(val) ? 0 : val);
}
}
if (!hasActionBar && pageActionsEl) {
// adjust headerTopValue when there are no page actions or action bar items (margin above title row)
update.headerTopValue -= update.titleRowSpaceAbove;
}
return {
...previous,
...update
};
});
};
// Trigger a window scroll, if necessary, to set the collapsed state.
const utilSetCollapsed = (collapse, headerRef, headerOffset, headerTopValue) => {
/* don't know how to test resize */
/* istanbul ignore else */
let scrollableTarget = scrollableAncestor(headerRef.current);
if (collapse) {
scrollableTarget.scrollTo({
top: headerOffset - headerTopValue,
behavior: 'smooth'
});
} else {
scrollableTarget.scrollTo({
top: 0,
behavior: 'smooth'
});
}
};
const utilGetBreadcrumbItemForTitle = (blockClass, collapseTitle, title) => {
let breadcrumbTitle;
if (title) {
if (title.text !== undefined) {
// Shape title provided
breadcrumbTitle = {
label: title.loading ? _SkeletonText || (_SkeletonText = /*#__PURE__*/React__default.createElement(SkeletonText, null)) : title.text,
title: title.text
};
} else if (title.content !== undefined) {
// user defined content
breadcrumbTitle = {
label: title.breadcrumbContent ?? title.content ?? title.asText,
title: title.asText
};
} else {
breadcrumbTitle = {
label: title,
title
};
}
if (breadcrumbTitle) {
breadcrumbTitle.key = 'breadcrumb-title';
breadcrumbTitle.isCurrentPage = true;
breadcrumbTitle.href = '#';
breadcrumbTitle.className = cx([`${blockClass}__breadcrumb-title`, {
[`${blockClass}__breadcrumb-title--pre-collapsed`]: collapseTitle
}]);
}
if (title.shortTitle) {
breadcrumbTitle.shortTitle = title.shortTitle;
}
return breadcrumbTitle;
}
};
export { blockClass, utilCheckUpdateVerticalSpace, utilGetBreadcrumbItemForTitle, utilSetCollapsed };