Skip to content

Commit

Permalink
A TagSet in need of review, It had some prep to do, Refinements and t…
Browse files Browse the repository at this point in the history
…ests, React.forwardRef Dave it's over to you. (#688)

* feat: updates for code review

* chore: remove focus from show all

* fix: back out show all blur

* chore: simplify tag usage in overflow

* fix: tests for tag set

* chore: change tagset ref

* fix: ci-check

* chore: review update

Co-authored-by: Lee Chase <[email protected]>
Co-authored-by: Dave Clark <[email protected]>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people authored Apr 30, 2021
1 parent 42a4f39 commit 0cc4ce2
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 163 deletions.
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 {...rest} className={cx([blockClass, className])} ref={tagSetRef}>
<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

0 comments on commit 0cc4ce2

Please sign in to comment.