@carbon/ibm-products
Version:
Carbon for IBM Products
228 lines (226 loc) • 10.9 kB
JavaScript
/**
* 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.
*/
const require_runtime = require("../../_virtual/_rolldown/runtime.js");
const require_index = require("../../node_modules/classnames/index.js");
const require_settings = require("../../settings.js");
require("../../global/js/utils/props-helper.js");
const require_uuidv4 = require("../../global/js/utils/uuidv4.js");
const require_useResizeObserver = require("../../global/js/hooks/useResizeObserver.js");
const require_TooltipTrigger = require("../TooltipTrigger/TooltipTrigger.js");
let react = require("react");
react = require_runtime.__toESM(react);
let prop_types = require("prop-types");
prop_types = require_runtime.__toESM(prop_types);
let _carbon_react = require("@carbon/react");
let _carbon_react_icons = require("@carbon/react/icons");
//#region src/components/BreadcrumbWithOverflow/BreadcrumbWithOverflow.jsx
var import_classnames = /* @__PURE__ */ require_runtime.__toESM(require_index.default);
const blockClass = `${require_settings.pkg.prefix}--breadcrumb-with-overflow`;
const componentName = "BreadcrumbWithOverflow";
/**
* The BreadcrumbWithOverflow is used internally by the PageHeader to wrap BreadcrumbItems.
*/
let BreadcrumbWithOverflow = ({ breadcrumbs, className, label, maxVisible, noTrailingSlash, overflowAriaLabel, overflowTooltipAlign, ...other }) => {
const carbonPrefix = (0, _carbon_react.usePrefix)();
const [displayCount, setDisplayCount] = (0, react.useState)(3);
const [displayedBreadcrumbItems, setDisplayedBreadcrumbItems] = (0, react.useState)([]);
const breadcrumbItemWithOverflow = (0, react.useRef)(null);
const sizingContainerRef = (0, react.useRef)(null);
const internalId = (0, react.useRef)(require_uuidv4.default());
const [hiddenSizingItems, setHiddenSizingItems] = (0, react.useState)([]);
const BreadcrumbOverflowMenu = ({ overflowItems }) => {
const handleClick = (evt, item) => {
if (item?.props?.href) window.location.href = item.props.href;
item?.props?.onClick?.(evt);
};
return /* @__PURE__ */ react.default.createElement(_carbon_react.BreadcrumbItem, { key: `breadcrumb-overflow-${internalId.current}` }, /* @__PURE__ */ react.default.createElement(_carbon_react.unstable_FeatureFlags, { enableV12Overflowmenu: true }, /* @__PURE__ */ react.default.createElement(_carbon_react.OverflowMenu, {
role: "navigation",
"aria-label": overflowAriaLabel,
label: overflowAriaLabel,
renderIcon: (props) => /* @__PURE__ */ react.default.createElement(_carbon_react_icons.OverflowMenuHorizontal, {
size: 32,
...props
}),
className: `${blockClass}__overflow-menu`,
tooltipAlignment: overflowTooltipAlign
}, overflowItems.map((item, index) => /* @__PURE__ */ react.default.createElement(_carbon_react.MenuItem, {
key: `breadcrumb-overflow-menu-item-${internalId.current}-${index}`,
onClick: (evt) => handleClick(evt, item),
label: item.props.children
})))));
};
(0, react.useEffect)(() => {
setHiddenSizingItems(/* @__PURE__ */ react.default.createElement("div", {
className: `${blockClass}__breadcrumb-container ${blockClass}__breadcrumb-container--hidden`,
"aria-hidden": true,
ref: sizingContainerRef
}, /* @__PURE__ */ react.default.createElement(_carbon_react.Breadcrumb, { "aria-label": `${label}-hidden` }, /* @__PURE__ */ react.default.createElement(_carbon_react.BreadcrumbItem, { key: `${blockClass}-hidden-overflow-${internalId}` }, /* @__PURE__ */ react.default.createElement(_carbon_react.OverflowMenu, {
role: "navigation",
"aria-label": overflowAriaLabel,
renderIcon: (props) => /* @__PURE__ */ react.default.createElement(_carbon_react_icons.OverflowMenuHorizontal, {
size: 32,
...props
})
})), breadcrumbs.map(({ label, key, title, id, shortTitle, ...rest }) => /* @__PURE__ */ react.default.createElement(_carbon_react.BreadcrumbItem, {
key,
...rest,
"data-original-id": id,
title: title ?? label
}, label)))));
}, [
breadcrumbs,
label,
overflowAriaLabel
]);
(0, react.useEffect)(() => {
/* istanbul ignore if */
if (breadcrumbs.length === 0) {
setDisplayedBreadcrumbItems([]);
return;
}
const newDisplayedBreadcrumbItems = breadcrumbs.map(({ className, key, label, shortTitle, title, ...rest }, index) => /* @__PURE__ */ react.default.createElement(_carbon_react.BreadcrumbItem, {
key,
className: index > 0 || displayCount > 1 ? (0, import_classnames.default)([className, `${blockClass}__displayed-breadcrumb`]) : className,
title: index + 1 === breadcrumbs.length ? title : null,
...rest
}, shortTitle || label));
const overflowPosition = displayCount > 1 ? 1 : 0;
let newOverflowBreadcrumbItems = newDisplayedBreadcrumbItems.splice(overflowPosition, breadcrumbs.length - displayCount);
if (newOverflowBreadcrumbItems.length) newDisplayedBreadcrumbItems.splice(overflowPosition, 0, /* @__PURE__ */ react.default.createElement(BreadcrumbOverflowMenu, {
overflowItems: newOverflowBreadcrumbItems,
key: `displayed-breadcrumb-${internalId}-overflow`
}));
setDisplayedBreadcrumbItems(newDisplayedBreadcrumbItems);
}, [breadcrumbs, displayCount]);
const checkFullyVisibleBreadcrumbItems = () => {
if (!breadcrumbItemWithOverflow.current) return;
const displayItemIndex = (itemCount, index) => {
if (index === 0) return itemCount - 1;
else if (index === 1) return 1;
else return itemCount - index;
};
if (maxVisible <= 1) setDisplayCount(1);
else {
let willFit = 0;
let spaceAvailable = breadcrumbItemWithOverflow.current.offsetWidth;
/* istanbul ignore next */
if (sizingContainerRef.current) {
const sizingBreadcrumbItems = sizingContainerRef.current.querySelectorAll(`.${carbonPrefix}--breadcrumb-item`);
const breadcrumbWidthsIncludingMargin = [];
for (let item of sizingBreadcrumbItems) {
const computedStyle = window ? window.getComputedStyle(sizingBreadcrumbItems[0]) : null;
const marginWidths = computedStyle ? parseFloat(computedStyle.marginLeft || 0, 10) + parseFloat(computedStyle.marginRight || 0, 10) : 0;
breadcrumbWidthsIncludingMargin.push(item.offsetWidth + marginWidths);
}
let overflowWidth = breadcrumbWidthsIncludingMargin[0];
for (let i = 0; i < breadcrumbWidthsIncludingMargin.length - 1; i++) {
const index = displayItemIndex(breadcrumbWidthsIncludingMargin.length, i);
if (spaceAvailable >= breadcrumbWidthsIncludingMargin[index]) {
spaceAvailable -= breadcrumbWidthsIncludingMargin[index];
willFit += 1;
} else break;
}
if (willFit < breadcrumbWidthsIncludingMargin.length - 1) {
if (spaceAvailable < overflowWidth) while (willFit > 0 && spaceAvailable < overflowWidth) {
willFit -= 1;
const itemToRemove = displayItemIndex(breadcrumbWidthsIncludingMargin.length, willFit);
spaceAvailable += breadcrumbWidthsIncludingMargin[itemToRemove];
}
}
}
if (willFit <= 1) setDisplayCount(1);
else setDisplayCount(maxVisible ? Math.min(willFit, maxVisible) : willFit);
}
};
(0, react.useEffect)(() => {
checkFullyVisibleBreadcrumbItems();
}, [hiddenSizingItems, maxVisible]);
/* istanbul ignore next */ const handleResize = () => {
/* istanbul ignore next */ checkFullyVisibleBreadcrumbItems();
};
let backItem = breadcrumbs[breadcrumbs.length - 1];
/* istanbul ignore if */ if (backItem.isCurrentPage) backItem = breadcrumbs[breadcrumbs.length - 2];
require_useResizeObserver.useResizeObserver(sizingContainerRef, handleResize);
require_useResizeObserver.useResizeObserver(breadcrumbItemWithOverflow, handleResize);
return /* @__PURE__ */ react.default.createElement("div", {
className: (0, import_classnames.default)(blockClass, className, { [`${blockClass}__with-items`]: displayedBreadcrumbItems.length > 1 }),
ref: breadcrumbItemWithOverflow
}, /* @__PURE__ */ react.default.createElement("div", { className: (0, import_classnames.default)([`${blockClass}__space`]) }, hiddenSizingItems, /* @__PURE__ */ react.default.createElement(_carbon_react.Breadcrumb, {
"aria-label": label,
className: (0, import_classnames.default)(`${blockClass}__breadcrumb-container`, { [`${blockClass}__breadcrumb-container-with-items`]: displayedBreadcrumbItems.length > 1 }),
noTrailingSlash,
...other
}, backItem?.href && (backItem?.label || backItem?.title) && /* @__PURE__ */ react.default.createElement(_carbon_react.BreadcrumbItem, { className: (0, import_classnames.default)(`${blockClass}__breadcrumb-back`) }, /* @__PURE__ */ react.default.createElement(_carbon_react.Link, {
href: backItem.href,
renderIcon: () => /* @__PURE__ */ react.default.createElement(_carbon_react.Tooltip, {
align: "right",
label: backItem.title || backItem.label,
className: `${blockClass}__back__button ${carbonPrefix}--icon-tooltip`
}, /* @__PURE__ */ react.default.createElement(require_TooltipTrigger.TooltipTrigger, null, /* @__PURE__ */ react.default.createElement(_carbon_react_icons.ArrowLeft, { size: 16 })))
})), displayedBreadcrumbItems)));
};
BreadcrumbWithOverflow = require_settings.pkg.checkComponentEnabled(BreadcrumbWithOverflow, componentName);
BreadcrumbWithOverflow.propTypes = {
breadcrumbs: prop_types.default.arrayOf(prop_types.default.shape({
/**
* Optional string representing the link location for the BreadcrumbItem
*/
href: prop_types.default.string,
/**
* Provide if this breadcrumb item represents the current page
*/
isCurrentPage: prop_types.default.bool,
/**
* Key required to render array efficiently
*/
key: prop_types.default.string.isRequired,
/**
* Pass in content that will be inside of the BreadcrumbItem
*/
label: prop_types.default.node,
/**
* An optional title label for extra long breadcrumb
*/
shortTitle: prop_types.default.string,
/**
* A string based alternative to the children, required only if children is not of type string
*/
title: prop_types.default.string
})),
/**
* className
*/
className: prop_types.default.string,
/**
* Label for the Breadcrumb component
*/
label: prop_types.default.string,
/**
* maxVisible: maximum visible breadcrumb-items before overflow is used (values less than 1 are treated as 1)
*/
maxVisible: prop_types.default.number,
/**
* noTrailing slash - same as for Carbon
*/
noTrailingSlash: prop_types.default.bool,
/**
* overflowAriaLabel label for open close button overflow used for breadcrumb items that do not fit.
*/
overflowAriaLabel: prop_types.default.string,
/**
* overflowTooltipAlign: align tooltip position
*/
overflowTooltipAlign: _carbon_react.Tooltip.propTypes.align
};
BreadcrumbWithOverflow.displayName = componentName;
//#endregion
Object.defineProperty(exports, "BreadcrumbWithOverflow", {
enumerable: true,
get: function() {
return BreadcrumbWithOverflow;
}
});