From 1697f3bb6a156648ad3d28101c53523a9fb96e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-David=20St=C3=BCtz?= <39377488+J-x-D@users.noreply.github.com> Date: Thu, 12 Feb 2026 13:23:38 +0100 Subject: [PATCH 1/2] Implement overflow handling for TagList component --- src/components/Tag/TagList.tsx | 184 +++++++++++++++++- .../Tag/stories/TagList.stories.tsx | 16 ++ 2 files changed, 190 insertions(+), 10 deletions(-) diff --git a/src/components/Tag/TagList.tsx b/src/components/Tag/TagList.tsx index 6aed6706c..d8f02b7bb 100644 --- a/src/components/Tag/TagList.tsx +++ b/src/components/Tag/TagList.tsx @@ -1,21 +1,177 @@ import React from "react"; import { CLASSPREFIX as eccgui } from "../../configuration/constants"; +import Tag from "./Tag"; +import Tooltip from "../Tooltip/Tooltip"; export interface TagListProps extends React.HTMLAttributes { label?: string; } function TagList({ children, className = "", label = "", ...otherProps }: TagListProps) { + const containerRef = React.useRef(null); + const measurementRef = React.useRef(null); + const moreTagRef = React.useRef(null); + const [visibleCount, setVisibleCount] = React.useState(null); + + const childArray = React.Children.toArray(children).filter(Boolean); + + React.useEffect(() => { + const checkOverflow = () => { + if (!containerRef.current || !measurementRef.current || !moreTagRef.current || childArray.length === 0) { + return; + } + + const container = containerRef.current; + const measurement = measurementRef.current; + const containerWidth = container.clientWidth; + + // If no size constraints, show all tags + if (containerWidth === 0) { + setVisibleCount(null); + return; + } + + const items = Array.from(measurement.children).filter( + (child) => !(child as HTMLElement).dataset.moreTag + ) as HTMLLIElement[]; + + if (items.length === 0) { + setVisibleCount(null); + return; + } + + // Get the actual width of the "+X more" tag + const moreTagWidth = moreTagRef.current.offsetWidth; + + let totalWidth = 0; + let count = 0; + + // Calculate how many items fit in one row + for (let i = 0; i < items.length; i++) { + const item = items[i]; + const itemWidth = item.offsetWidth; + + if (totalWidth + itemWidth <= containerWidth) { + totalWidth += itemWidth; + count++; + } else { + // This item doesn't fit + break; + } + } + + // If not all items fit, adjust count to leave room for "+X more" tag + if (count < childArray.length) { + let adjustedWidth = 0; + let adjustedCount = 0; + + for (let i = 0; i < count; i++) { + const item = items[i]; + const itemWidth = item.offsetWidth; + + if (adjustedWidth + itemWidth + moreTagWidth <= containerWidth) { + adjustedWidth += itemWidth; + adjustedCount++; + } else { + break; + } + } + + // Ensure at least one tag is visible before the "+X more" tag + // Only show overflow if we have at least 1 visible tag + if (adjustedCount > 0) { + setVisibleCount(adjustedCount); + } else { + // If no tags fit with the "+X more" tag, show all tags instead + setVisibleCount(null); + } + } else { + // All items fit + setVisibleCount(null); + } + }; + + // Use RAF to ensure DOM is ready + requestAnimationFrame(() => { + checkOverflow(); + }); + + // Watch for size changes + const resizeObserver = new ResizeObserver(() => { + requestAnimationFrame(checkOverflow); + }); + if (containerRef.current) { + resizeObserver.observe(containerRef.current); + } + + return () => { + resizeObserver.disconnect(); + }; + }, [childArray.length]); + + const showOverflowTag = visibleCount !== null && visibleCount < childArray.length; + const visibleChildren = showOverflowTag ? childArray.slice(0, visibleCount) : childArray; + const hiddenCount = childArray.length - (visibleCount ?? childArray.length); + const tagList = ( -
    - {React.Children.map(children, (child, i) => { - return child ? ( -
  • - {child} -
  • - ) : null; - })} +
      + {visibleChildren.map((child, i) => ( +
    • + {child} +
    • + ))} + {showOverflowTag && ( +
    • + + {childArray.map((child, i) => ( + {child} + ))} + + } + size="large" + > + +{hiddenCount} more + +
    • + )} +
    + ); + + // Hidden measurement list - always rendered for measurements + const measurementList = ( + ); @@ -23,12 +179,20 @@ function TagList({ children, className = "", label = "", ...otherProps }: TagLis return (
    {label} - {tagList} + + {tagList} + {measurementList} +
    ); } - return tagList; + return ( +
    + {tagList} + {measurementList} +
    + ); } export default TagList; diff --git a/src/components/Tag/stories/TagList.stories.tsx b/src/components/Tag/stories/TagList.stories.tsx index 27221c360..a4bddda92 100644 --- a/src/components/Tag/stories/TagList.stories.tsx +++ b/src/components/Tag/stories/TagList.stories.tsx @@ -20,3 +20,19 @@ List.args = { label: "Tag list", children: [Short, List, Of, Tags], }; + +export const ListWithOverflow = Template.bind({}); +ListWithOverflow.args = { + label: "Tag list with overflow", + style: { width: '300px' }, + children: [ + First Tag, + Second Tag, + Third Tag, + Fourth Tag, + Fifth Tag, + Sixth Tag, + Seventh Tag, + Eighth Tag, + ], +}; From eb48d86cc27ab1eabfd69a81e1ff23d7d9340607 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-David=20St=C3=BCtz?= <39377488+J-x-D@users.noreply.github.com> Date: Thu, 12 Feb 2026 15:19:57 +0100 Subject: [PATCH 2/2] Remove ListWithOverflow story from TagList Removed the ListWithOverflow story from TagList stories. --- src/components/Tag/stories/TagList.stories.tsx | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/components/Tag/stories/TagList.stories.tsx b/src/components/Tag/stories/TagList.stories.tsx index a4bddda92..27221c360 100644 --- a/src/components/Tag/stories/TagList.stories.tsx +++ b/src/components/Tag/stories/TagList.stories.tsx @@ -20,19 +20,3 @@ List.args = { label: "Tag list", children: [Short, List, Of, Tags], }; - -export const ListWithOverflow = Template.bind({}); -ListWithOverflow.args = { - label: "Tag list with overflow", - style: { width: '300px' }, - children: [ - First Tag, - Second Tag, - Third Tag, - Fourth Tag, - Fifth Tag, - Sixth Tag, - Seventh Tag, - Eighth Tag, - ], -};