Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

A TagSet in need of review, It had some prep to do, Refinements and tests, React.forwardRef Dave it's over to you. #688

Merged
merged 12 commits into from
Apr 30, 2021
324 changes: 166 additions & 158 deletions packages/cloud-cognitive/src/components/TagSet/TagSet.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,189 +18,197 @@ import { pkg } from '../../settings';
const componentName = 'TagSet';
const blockClass = `${pkg.prefix}--tag-set`;

export let TagSet = ({
children,
className,
maxVisible,
rightAlign,
overflowAlign,
overflowDirection,
showAllModalHeading,
showAllSearchLabel,
showAllSearchPlaceHolderText,
showAllTagsLabel,
}) => {
const [displayCount, setDisplayCount] = useState(3);
const [displayedTags, setDisplayedTags] = useState([]);
const [allTags, setAllTags] = useState([]);
const [hiddenSizingTags, setHiddenSizingTags] = useState([]);
const [showAllModalOpen, setShowAllModalOpen] = useState(false);
const tagSet = useRef(null);
const displayedArea = useRef(null);
const [sizingTags, setSizingTags] = useState([]);
const overflowTag = useRef(null);
export let TagSet = React.forwardRef(
(
{
children,
className,
maxVisible,
rightAlign,
overflowAlign,
overflowDirection,
showAllModalHeading,
showAllSearchLabel,
showAllSearchPlaceHolderText,
showAllTagsLabel,
// Collect any other property values passed in.
...rest
},
ref
) => {
const [displayCount, setDisplayCount] = useState(3);
const [displayedTags, setDisplayedTags] = useState([]);
const [allTags, setAllTags] = useState([]);
const [hiddenSizingTags, setHiddenSizingTags] = useState([]);
const [showAllModalOpen, setShowAllModalOpen] = useState(false);
const localTagSetRef = useRef(null);
const tagSetRef = ref || localTagSetRef;
const displayedArea = useRef(null);
const [sizingTags, setSizingTags] = useState([]);
const overflowTag = useRef(null);

const handleShowAllClick = () => {
setShowAllModalOpen(true);
};
const handleShowAllClick = () => {
setShowAllModalOpen(true);
};

useEffect(() => {
// clone children for use in modal
setAllTags(
children && children.length > 0
? children.map((child) => React.cloneElement(child))
: []
);
useEffect(() => {
// clone children for use in modal
setAllTags(
children && children.length > 0
? children.map((child) => React.cloneElement(child))
: []
);

const newSizingTags = [];
// use children as sizing tags
setHiddenSizingTags(
/* istanbul ignore next */
children && children.length > 0
? children.map((child, index) => {
return (
<div
key={index}
className={`${blockClass}__sizing-tag`}
ref={(el) => (newSizingTags[index] = el)}>
{child}
</div>
);
})
: []
);
setSizingTags(newSizingTags);
}, [children]);
const newSizingTags = [];
// use children as sizing tags
setHiddenSizingTags(
/* istanbul ignore next */
children && children.length > 0
? children.map((child, index) => {
return (
<div
key={index}
className={`${blockClass}__sizing-tag`}
ref={(el) => (newSizingTags[index] = el)}>
{child}
</div>
);
})
: []
);
setSizingTags(newSizingTags);
}, [children]);

useEffect(() => {
// clone children for use as visible and overflow tags
let newDisplayedTags =
children && children.length > 0
? children.map((child) => React.cloneElement(child))
: [];
useEffect(() => {
// clone children for use as visible and overflow tags
let newDisplayedTags =
children && children.length > 0
? children.map((child) => React.cloneElement(child))
: [];

// separate out tags for the overflow
const newOverflowTags = newDisplayedTags.splice(
displayCount,
newDisplayedTags.length - displayCount
);
// separate out tags for the overflow
const newOverflowTags = newDisplayedTags.splice(
displayCount,
newDisplayedTags.length - displayCount
);

// add wrapper around displayed tags
newDisplayedTags = newDisplayedTags.map((tag, index) => (
<div key={index} className={`${blockClass}__displayed-tag`}>
{tag}
</div>
));
// add wrapper around displayed tags
newDisplayedTags = newDisplayedTags.map((tag, index) => (
<div key={index} className={`${blockClass}__displayed-tag`}>
{tag}
</div>
));

newDisplayedTags.push(
<TagSetOverflow
onShowAllClick={handleShowAllClick}
overflowTags={newOverflowTags}
overflowAlign={overflowAlign}
overflowDirection={overflowDirection}
showAllTagsLabel={showAllTagsLabel}
key="displayed-tag-overflow"
ref={overflowTag}
/>
);
newDisplayedTags.push(
<TagSetOverflow
onShowAllClick={handleShowAllClick}
overflowTags={newOverflowTags}
overflowAlign={overflowAlign}
overflowDirection={overflowDirection}
showAllTagsLabel={showAllTagsLabel}
key="displayed-tag-overflow"
ref={overflowTag}
/>
);

setDisplayedTags(newDisplayedTags);
}, [
children,
displayCount,
overflowAlign,
overflowDirection,
showAllTagsLabel,
]);
setDisplayedTags(newDisplayedTags);
}, [
children,
displayCount,
overflowAlign,
overflowDirection,
showAllTagsLabel,
]);

const checkFullyVisibleTags = useCallback(() => {
// how many will fit?
let willFit = 0;
const checkFullyVisibleTags = useCallback(() => {
// how many will fit?
let willFit = 0;

if (sizingTags.length > 0) {
let spaceAvailable = tagSet.current.offsetWidth;
if (sizingTags.length > 0) {
let spaceAvailable = tagSetRef.current.offsetWidth;

for (let i in sizingTags) {
const tagWidth = sizingTags[i].offsetWidth;
for (let i in sizingTags) {
const tagWidth = sizingTags[i].offsetWidth;

if (spaceAvailable >= tagWidth) {
spaceAvailable -= tagWidth;
willFit += 1;
} else {
break;
if (spaceAvailable >= tagWidth) {
spaceAvailable -= tagWidth;
willFit += 1;
} else {
break;
}
}
}

if (willFit < sizingTags.length) {
while (
willFit > 0 &&
spaceAvailable < overflowTag.current.offsetWidth
) {
// Highly unlikely any useful tag is smaller
willFit -= 1; // remove one tag
spaceAvailable += sizingTags[willFit].offsetWidth;
if (willFit < sizingTags.length) {
while (
willFit > 0 &&
spaceAvailable < overflowTag.current.offsetWidth
) {
// Highly unlikely any useful tag is smaller
willFit -= 1; // remove one tag
spaceAvailable += sizingTags[willFit].offsetWidth;
}
}
}
}

if (willFit < 1) {
setDisplayCount(0);
} else {
setDisplayCount(maxVisible ? Math.min(willFit, maxVisible) : willFit);
}
}, [maxVisible, sizingTags]);
if (willFit < 1) {
setDisplayCount(0);
} else {
setDisplayCount(maxVisible ? Math.min(willFit, maxVisible) : willFit);
}
}, [maxVisible, sizingTags, tagSetRef]);

useEffect(() => {
checkFullyVisibleTags();
}, [checkFullyVisibleTags, maxVisible, sizingTags]);
useEffect(() => {
checkFullyVisibleTags();
}, [checkFullyVisibleTags, maxVisible, sizingTags]);

const handleResize = () => {
/* istanbul ignore next */ // not sure how to test resize
checkFullyVisibleTags();
};
const handleResize = () => {
/* istanbul ignore next */ // not sure how to test resize
checkFullyVisibleTags();
};

const handleSizerTagsResize = () => {
/* istanbul ignore next */ // not sure how to test resize
checkFullyVisibleTags();
};
const handleSizerTagsResize = () => {
/* istanbul ignore next */ // not sure how to test resize
checkFullyVisibleTags();
};

const handleModalClose = () => {
setShowAllModalOpen(false);
};
const handleModalClose = () => {
setShowAllModalOpen(false);
};

return (
<ReactResizeDetector onResize={handleResize}>
<div className={cx([blockClass, className])} ref={tagSet}>
<div
className={cx([
`${blockClass}__space`,
{ [`${blockClass}__space--right`]: rightAlign },
])}>
<ReactResizeDetector onResize={handleSizerTagsResize}>
<div
className={`${blockClass}__tag-container ${blockClass}__tag-container--hidden`}
aria-hidden={true}>
{hiddenSizingTags}
</div>
</ReactResizeDetector>
return (
<ReactResizeDetector onResize={handleResize}>
<div className={cx([blockClass, className])} ref={tagSetRef} {...rest}>
lee-chase marked this conversation as resolved.
Show resolved Hide resolved
<div
className={cx([
`${blockClass}__space`,
{ [`${blockClass}__space--right`]: rightAlign },
])}>
<ReactResizeDetector onResize={handleSizerTagsResize}>
<div
className={`${blockClass}__tag-container ${blockClass}__tag-container--hidden`}
aria-hidden={true}>
{hiddenSizingTags}
</div>
</ReactResizeDetector>

<div className={`${blockClass}__tag-container`} ref={displayedArea}>
{displayedTags}
<div className={`${blockClass}__tag-container`} ref={displayedArea}>
{displayedTags}
</div>
</div>
</div>

<TagSetModal
allTags={allTags}
open={showAllModalOpen}
heading={showAllModalHeading}
onClose={handleModalClose}
searchLabel={showAllSearchLabel}
searchPlaceholder={showAllSearchPlaceHolderText}
/>
</div>
</ReactResizeDetector>
);
};
<TagSetModal
allTags={allTags}
open={showAllModalOpen}
heading={showAllModalHeading}
onClose={handleModalClose}
searchLabel={showAllSearchLabel}
searchPlaceholder={showAllSearchPlaceHolderText}
/>
</div>
</ReactResizeDetector>
);
}
);

// Return a placeholder if not released and not enabled by feature flag
TagSet = pkg.checkComponentEnabled(TagSet, componentName);
Expand Down
Loading