detailpanel

This commit is contained in:
2025-11-01 19:39:51 +01:00
parent 366f716b1c
commit 03bc311403
5 changed files with 142 additions and 52 deletions
+40 -16
View File
@@ -26,8 +26,18 @@ const ELLIPSIS = { id: '__breadcrumbs_ellipsis__', label: '…', onClick: null,
const WIDTH_TOLERANCE = 1;
const WIDTH_BUFFER_RATIO = 0.99;
const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
const BreadcrumbTrail = ({
entries = [],
className = '',
separator = '/',
truncateFromStart = true,
}) => {
const normalized = useMemo(() => normalizeEntries(entries), [entries]);
const shouldTruncateFromStart = truncateFromStart !== false;
const measurementEntries = useMemo(
() => (shouldTruncateFromStart ? normalized : normalized.slice().reverse()),
[normalized, shouldTruncateFromStart],
);
const containerRef = useRef(null);
const measurementRef = useRef(null);
const ellipsisButtonRef = useRef(null);
@@ -50,7 +60,7 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
useEffect(() => {
setStartIndex(0);
closeEllipsisMenu();
}, [normalized, closeEllipsisMenu]);
}, [normalized, shouldTruncateFromStart, closeEllipsisMenu]);
useEffect(() => {
const container = containerRef.current;
@@ -58,10 +68,12 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
return undefined;
}
const target = container.parentElement || container;
const updateWidth = () => {
const nextWidth = target.getBoundingClientRect().width;
const host = containerRef.current;
if (!host) {
return;
}
const nextWidth = host.getBoundingClientRect().width;
if (!nextWidth) {
return;
}
@@ -71,12 +83,12 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
updateWidth();
const observer = new ResizeObserver(updateWidth);
observer.observe(target);
observer.observe(container.parentElement || container);
return () => observer.disconnect();
}, []);
useLayoutEffect(() => {
if (!normalized.length) {
if (!measurementEntries.length) {
return;
}
@@ -136,7 +148,7 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
ellipsisNode.style.display = originalEllipsisDisplay ?? 'none';
}
const available = availableWidth ?? container.clientWidth;
const available = availableWidth ?? container.getBoundingClientRect().width;
if (!available || !widths.length) {
return;
}
@@ -156,13 +168,24 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
if (nextStart !== startIndex) {
setStartIndex(nextStart);
}
}, [normalized, separator, availableWidth, startIndex]);
}, [measurementEntries, separator, availableWidth, startIndex]);
const sliceStart = Math.min(startIndex, Math.max(0, normalized.length - 1));
const slice = normalized.slice(sliceStart);
const visibleEntries = sliceStart > 0 ? [ELLIPSIS, ...slice] : slice;
const hiddenEntries = sliceStart > 0 ? normalized.slice(0, sliceStart) : [];
const trimmedCount = Math.min(startIndex, Math.max(0, normalized.length - 1));
const visibleEntries = shouldTruncateFromStart
? normalized.slice(trimmedCount)
: normalized.slice(0, Math.max(normalized.length - trimmedCount, 1));
const hiddenEntries = trimmedCount === 0
? []
: shouldTruncateFromStart
? normalized.slice(0, trimmedCount)
: normalized.slice(-trimmedCount);
const hasHiddenEntries = hiddenEntries.length > 0;
const ellipsisPlacement = shouldTruncateFromStart ? 'start' : 'end';
const displayEntries = hasHiddenEntries
? ellipsisPlacement === 'start'
? [ELLIPSIS, ...visibleEntries]
: [...visibleEntries, ELLIPSIS]
: visibleEntries;
useEffect(() => {
if (!hasHiddenEntries) {
@@ -191,9 +214,9 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
return (
<>
<span ref={containerRef} className={wrapperClassName}>
{visibleEntries.map((entry, index) => {
{displayEntries.map((entry, index) => {
const isEllipsis = entry.id === ELLIPSIS.id;
const isLast = index === visibleEntries.length - 1;
const isLast = index === displayEntries.length - 1;
if (isEllipsis) {
return (
@@ -273,7 +296,7 @@ const BreadcrumbTrail = ({ entries = [], className = '', separator = '/' }) => {
>
{ELLIPSIS.label}
</span>
{normalized.map((entry, index) => (
{measurementEntries.map((entry, index) => (
<React.Fragment key={`measure-${entry.id || index}`}>
{index > 0 ? (
<span
@@ -342,6 +365,7 @@ BreadcrumbTrail.propTypes = {
entries: PropTypes.arrayOf(breadcrumbEntryShape),
className: PropTypes.string,
separator: PropTypes.string,
truncateFromStart: PropTypes.bool,
};
export default BreadcrumbTrail;