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 */