From 29e960c858d2c58d46c86e9ea638a351d5fddd76 Mon Sep 17 00:00:00 2001
From: Jeeson Johnson <44068823+jeesonjohnson@users.noreply.github.com>
Date: Fri, 11 Oct 2024 12:51:23 +0100
Subject: [PATCH] feat: adding the ability to disable modals for TagSet
component (#5753)
* feat: adding the ability to disable modals for TagSet component
* feat: improving logic to remove additional uneeded values
* refactor: clean up code a little
* feat: addressed requested cahnges
* refactor: cleaned up code
* fix: spelling mistakes
* feat: addressed comments
* refactor: cleaned up code
* refactor: cleaned up code part 2
---------
Co-authored-by: Nandan Devadula <47176249+devadula-nandan@users.noreply.github.com>
---
.../src/components/TagSet/TagSet.stories.jsx | 5 ++
.../src/components/TagSet/TagSet.test.js | 30 ++++++++++-
.../src/components/TagSet/TagSet.tsx | 33 ++++++++----
.../src/components/TagSet/TagSetOverflow.tsx | 54 ++++++++++++++++---
4 files changed, 105 insertions(+), 17 deletions(-)
diff --git a/packages/ibm-products/src/components/TagSet/TagSet.stories.jsx b/packages/ibm-products/src/components/TagSet/TagSet.stories.jsx
index efd7f043e3..b70cf2a849 100644
--- a/packages/ibm-products/src/components/TagSet/TagSet.stories.jsx
+++ b/packages/ibm-products/src/components/TagSet/TagSet.stories.jsx
@@ -149,6 +149,11 @@ export default {
description:
'This prop is only for storybook representation, and does not belong to `tagset` component, the size can be passed to each tag{} in tags[], the overflow tag takes the size of last tag{} in tags[]',
},
+ onOverflowClick: {
+ control: { type: 'function' },
+ description:
+ 'An optional click handler that overrides the default functionality of displaying all tags in a modal',
+ },
allTagsModalTargetCustomDomNode: {
control: { type: 'boolean' },
description: 'Optional DOM node: Modal target defaults to document.body',
diff --git a/packages/ibm-products/src/components/TagSet/TagSet.test.js b/packages/ibm-products/src/components/TagSet/TagSet.test.js
index 4a93f48d8a..be6158f58f 100644
--- a/packages/ibm-products/src/components/TagSet/TagSet.test.js
+++ b/packages/ibm-products/src/components/TagSet/TagSet.test.js
@@ -177,7 +177,6 @@ describe(TagSet.displayName, () => {
const visibleTags = 5;
window.innerWidth = tagWidth * (visibleTags + 1) + 1; // + 1 for overflow
- // const { container } =
render();
const overflow = screen.getByText(`+${tags.length - visibleTags}`);
@@ -193,6 +192,35 @@ describe(TagSet.displayName, () => {
expect(modal).not.toHaveClass('is-visible');
});
+ it('Tags set overflow trigger can be overridden, and does not show TagSetModal or overflow popup', async () => {
+ const visibleTags = 5;
+ window.innerWidth = tagWidth * (visibleTags + 1) + 1; // + 1 for overflow
+
+ const overflowClickSpy = jest.fn();
+
+ const { queryByText } = render(
+
+ );
+
+ const overFlowButton = queryByText(`+${tags.length - visibleTags}`);
+ // Ensure the number of visible elements are rendered on the screen
+ expect(overFlowButton).toBeInTheDocument();
+ // Clicking the overflow button causes the spyFunction to be called
+ await act(() => userEvent.click(overFlowButton));
+ expect(overflowClickSpy).toHaveBeenCalledTimes(1);
+
+ // Ensure the overflow popup is not rendered onto the screen
+ expect(queryByText('View all tags')).toBeNull();
+
+ // Ensure the modal is not rendered onto the screen
+ const modal = screen.queryByRole('presentation');
+ expect(modal).not.toBeInTheDocument();
+ });
+
it('Obeys max visible', async () => {
window.innerWidth = tagWidth * 10 + 1;
diff --git a/packages/ibm-products/src/components/TagSet/TagSet.tsx b/packages/ibm-products/src/components/TagSet/TagSet.tsx
index 7ab4084aa7..8e54b7b07d 100644
--- a/packages/ibm-products/src/components/TagSet/TagSet.tsx
+++ b/packages/ibm-products/src/components/TagSet/TagSet.tsx
@@ -106,6 +106,10 @@ export interface TagSetProps extends PropsWithChildren {
* display tags in multiple lines
*/
multiline?: boolean;
+ /**
+ * An optional click handler that overrides the default functionality of displaying all tags in a modal
+ */
+ onOverflowClick?: ((overFlowTags: ReactNode[]) => void) | undefined;
/**
* Handler to get overflow tags
*/
@@ -160,6 +164,7 @@ export let TagSet = React.forwardRef(
allTagsModalSearchLabel = 'Search all tags',
allTagsModalSearchPlaceholderText = 'Search all tags',
showAllTagsLabel = 'View all tags',
+ onOverflowClick,
tags,
containingElementRef,
measurementOffset = defaults.measurementOffset,
@@ -281,6 +286,7 @@ export let TagSet = React.forwardRef(
key="displayed-tag-overflow"
ref={overflowTag}
popoverOpen={popoverOpen}
+ onOverflowClick={onOverflowClick}
setPopoverOpen={setPopoverOpen}
/>
);
@@ -293,6 +299,7 @@ export let TagSet = React.forwardRef(
overflowClassName,
overflowType,
showAllTagsLabel,
+ onOverflowClick,
tags,
onOverflowTagChange,
popoverOpen,
@@ -415,16 +422,18 @@ export let TagSet = React.forwardRef(
{displayedTags}
-
+ {!onOverflowClick && (
+
+ )}
);
}
@@ -509,6 +518,10 @@ TagSet.propTypes = {
* display tags in multiple lines
*/
multiline: PropTypes.bool,
+ /**
+ * An optional click handler that overrides the default functionality of displaying all tags in a modal
+ */
+ onOverflowClick: PropTypes.func,
/**
* Handler to get overflow tags
*/
diff --git a/packages/ibm-products/src/components/TagSet/TagSetOverflow.tsx b/packages/ibm-products/src/components/TagSet/TagSetOverflow.tsx
index efa1fe18bc..07dafedd1d 100644
--- a/packages/ibm-products/src/components/TagSet/TagSetOverflow.tsx
+++ b/packages/ibm-products/src/components/TagSet/TagSetOverflow.tsx
@@ -51,6 +51,10 @@ interface TagSetOverflowProps {
* className
*/
className?: string;
+ /**
+ * An optional click handler that overrides the default functionality of displaying all tags in a modal
+ */
+ onOverflowClick?: ((overFlowTags: ReactNode[]) => void) | undefined;
/**
* function to execute on clicking show all
*/
@@ -78,7 +82,7 @@ interface TagSetOverflowProps {
/**
* Setter function for the popoverOpen state value
*/
- setPopoverOpen?: ((value: boolean) => void) | undefined;
+ setPopoverOpen: (value: boolean) => void;
/**
* label for the overflow show all tags link
*/
@@ -95,6 +99,7 @@ export const TagSetOverflow = React.forwardRef(
// The component props, in alphabetical order (for consistency).
allTagsModalSearchThreshold = defaults.allTagsModalSearchThreshold,
+ onOverflowClick,
className,
onShowAllClick,
overflowAlign = 'bottom',
@@ -115,24 +120,57 @@ export const TagSetOverflow = React.forwardRef(
useClickOutside(ref || localRef, () => {
if (popoverOpen) {
- setPopoverOpen?.(false);
+ setPopoverOpen(false);
}
});
const handleShowAllTagsClick = (ev) => {
ev.stopPropagation();
ev.preventDefault();
- setPopoverOpen?.(false);
+ setPopoverOpen(false);
onShowAllClick();
};
const handleEscKeyPress = (event) => {
const { key } = event;
if (key === 'Escape') {
- setPopoverOpen?.(false);
+ setPopoverOpen(false);
}
};
+ const handleOverflowClick = () => {
+ // If a custom overflow function is provided then trigger that function
+ // on clicking the overflow
+ if (onOverflowClick) {
+ onOverflowClick(overflowTags);
+ } else {
+ setPopoverOpen(!popoverOpen);
+ }
+ };
+
+ if (onOverflowClick) {
+ return (
+
+ handleOverflowClick()}
+ className={`${blockClass}__popover-trigger`}
+ size={size}
+ text={`+${overflowTags.length}`}
+ />
+
+ );
+ }
+
return (
setPopoverOpen?.(!popoverOpen)}
+ onClick={() => setPopoverOpen(!popoverOpen)}
className={cx(`${blockClass}__popover-trigger`)}
size={size}
text={`+${overflowTags.length}`}
@@ -224,6 +262,10 @@ TagSetOverflow.propTypes = {
* className
*/
className: PropTypes.string,
+ /**
+ * An optional click handler that overrides the default functionality of displaying all tags in a modal
+ */
+ onOverflowClick: PropTypes.func,
/**
* function to execute on clicking show all
*/
@@ -265,7 +307,7 @@ TagSetOverflow.propTypes = {
/**
* Setter function for the popoverOpen state value
*/
- setPopoverOpen: PropTypes.func,
+ setPopoverOpen: PropTypes.func.isRequired,
/**
* label for the overflow show all tags link
*/