Skip to content

Commit

Permalink
New Tag Overflow (#16437)
Browse files Browse the repository at this point in the history
* feat: added ellipses to tag

* feat: added tooltip

* feat: added text prop to intereactive tags storybook

* fix: removed children prop from interactive tags

* test: snapshots updated

* fix: fixed button propagation

* fix: added text to operational tags

* fix: removed Tooltip from operational tag example

* test: fixed tests

* fix: added tooltip style

* fix: added a class to manipulate the onMouseEnter in Tooltip

* chore: an updated to interactive tag story

* chore: update interactive story

* fix: fixed dismissible tag issue and playground story

* fix: added new overflow spec to InteractiveTag

* test: updated snapshot

* fix: removed commented code

* fix: fixed errors

* fix: yarn format

* fix: fixed operational tag disabled with toggletip

* fix: fixed title in text

* fix: fixed flicker in tooltip onclick
  • Loading branch information
guidari authored May 21, 2024
1 parent ba18b14 commit 96524a7
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 197 deletions.
13 changes: 12 additions & 1 deletion e2e/components/InteractiveTag/InteractiveTag-test.avt.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ test.describe('@avt InteractiveTag', () => {
theme: 'white',
},
});
const tooltip = page.getByRole('tooltip');
const button = page.getByRole('button').first();
await expect(button).toBeVisible();
await page.keyboard.press('Tab');
await expect(button).toBeFocused();
await expect(tooltip).toHaveAttribute('aria-hidden', 'false');
});

test('@avt-keyboard-nav OperationalTag', async ({ page }) => {
Expand All @@ -71,6 +73,10 @@ test.describe('@avt InteractiveTag', () => {
await expect(button).toBeVisible();
await page.keyboard.press('Tab');
await expect(button).toBeFocused();
await expect(page.getByRole('tooltip')).toHaveAttribute(
'aria-hidden',
'false'
);
await expect(button).toHaveClass(/cds--tag--red/);

await page.keyboard.press('Tab');
Expand All @@ -87,7 +93,7 @@ test.describe('@avt InteractiveTag', () => {
// Expecte the OperationalTag with tooltip be focusable and visible
await expect(page.getByRole('button').nth(10)).toBeFocused();
await page.keyboard.press('Enter');
await expect(page.getByText('View More')).toBeVisible();
await expect(page.getByText('Tag 1 name').first()).toBeVisible();
});

test('@avt-keyboard-nav SelectableTag', async ({ page }) => {
Expand All @@ -103,6 +109,11 @@ test.describe('@avt InteractiveTag', () => {
await page.keyboard.press('Tab');
await expect(tag).toBeFocused();
await page.keyboard.press('Enter');
await expect(page.getByRole('tooltip')).toHaveAttribute(
'aria-hidden',
'false'
);
await expect(tag).toHaveClass(/cds--tag--selectable-selected/);
await page.keyboard.press('Tab');
});
});
19 changes: 19 additions & 0 deletions e2e/components/Tag/Tag-test.avt.e2e.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,23 @@ test.describe('@avt Tag', () => {
});
await expect(page).toHaveNoACViolations('Tag-skeleton');
});

test('@avt-keyboard-nav Tag', async ({ page }) => {
await visitStory(page, {
component: 'Tag',
id: 'components-tag--read-only',
globals: {
theme: 'white',
},
});
const button = page.getByRole('button').first();
await expect(button).toBeVisible();
await page.keyboard.press('Tab');
await expect(button).toBeFocused();
// Expect DefinitionTooltip to be visible
await expect(page.getByRole('button')).toHaveAttribute(
'aria-expanded',
'true'
);
});
});
18 changes: 9 additions & 9 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -2729,9 +2729,6 @@ Map {
},
"DismissibleTag" => Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"className": Object {
"type": "string",
},
Expand Down Expand Up @@ -2770,6 +2767,9 @@ Map {
"slug": Object {
"type": "node",
},
"text": Object {
"type": "string",
},
"title": Object {
"type": "string",
},
Expand Down Expand Up @@ -5626,9 +5626,6 @@ Map {
},
"OperationalTag" => Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"className": Object {
"type": "string",
},
Expand Down Expand Up @@ -5664,6 +5661,9 @@ Map {
"slug": Object {
"type": "node",
},
"text": Object {
"type": "string",
},
"type": Object {
"args": Array [
Array [
Expand Down Expand Up @@ -6742,9 +6742,6 @@ Map {
},
"SelectableTag" => Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"className": Object {
"type": "string",
},
Expand Down Expand Up @@ -6783,6 +6780,9 @@ Map {
"slug": Object {
"type": "node",
},
"text": Object {
"type": "string",
},
},
},
"SelectableTile" => Object {
Expand Down
77 changes: 54 additions & 23 deletions packages/react/src/components/Tag/DismissibleTag.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,19 @@
*/

import PropTypes from 'prop-types';
import React, { ReactNode } from 'react';
import React, { useLayoutEffect, useState, ReactNode } from 'react';
import classNames from 'classnames';
import setupGetInstanceId from '../../tools/setupGetInstanceId';
import { usePrefix } from '../../internal/usePrefix';
import { PolymorphicProps } from '../../types/common';
import Tag, { SIZES, TYPES } from './Tag';
import { Close } from '@carbon/icons-react';
import { Tooltip } from '../Tooltip';
import { Text } from '../Text';

const getInstanceId = setupGetInstanceId();

export interface DismissibleTagBaseProps {
/**
* Provide content to be rendered inside of a `DismissibleTag`
*/
children?: React.ReactNode;

/**
* Provide a custom className that is applied to the containing <span>
*/
Expand Down Expand Up @@ -59,6 +56,11 @@ export interface DismissibleTagBaseProps {
*/
slug?: ReactNode;

/**
* Provide text to be rendered inside of a the tag.
*/
text?: string;

/**
* Text to show on clear filters
*/
Expand All @@ -76,22 +78,35 @@ export type DismissibleTagProps<T extends React.ElementType> = PolymorphicProps<
>;

const DismissibleTag = <T extends React.ElementType>({
children,
className,
disabled,
id,
renderIcon,
title = 'Clear filter',
title = 'Dismiss',
onClose,
slug,
size,
text,
type,
...other
}: DismissibleTagProps<T>) => {
const prefix = usePrefix();
const tagId = id || `tag-${getInstanceId()}`;
const tagClasses = classNames(`${prefix}--tag--filter`, className);
const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);

const isEllipsisActive = (element: any) => {
setIsEllipsisApplied(element.offsetWidth < element.scrollWidth);
return element.offsetWidth < element.scrollWidth;
};

useLayoutEffect(() => {
const elementTagId = document.querySelector(`#${tagId}`);
const newElement = elementTagId?.getElementsByClassName(
`${prefix}--tag__label`
)[0];
isEllipsisActive(newElement);
}, [prefix, tagId]);
const handleClose = (event: React.MouseEvent<HTMLButtonElement>) => {
if (onClose) {
event.stopPropagation();
Expand All @@ -107,10 +122,17 @@ const DismissibleTag = <T extends React.ElementType>({
});
}

const tooltipClasses = classNames(
`${prefix}--icon-tooltip`,
`${prefix}--tag-label-tooltip`
);

// Removing onClick from the spread operator
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { onClick, ...otherProps } = other;

const dismissLabel = `Dismiss "${text}"`;

return (
<Tag<any>
type={type}
Expand All @@ -121,27 +143,31 @@ const DismissibleTag = <T extends React.ElementType>({
id={tagId}
{...otherProps}>
<div className={`${prefix}--interactive--tag-children`}>
{children}
<Text title={text} className={`${prefix}--tag__label`}>
{text}
</Text>
<Tooltip
label={isEllipsisApplied ? dismissLabel : title}
align="bottom"
className={tooltipClasses}
leaveDelayMs={0}
closeOnActivation>
<button
type="button"
className={`${prefix}--tag__close-icon`}
onClick={handleClose}
disabled={disabled}
aria-label={title}
title={title}>
<Close />
</button>
</Tooltip>
{normalizedSlug}
<button
type="button"
className={`${prefix}--tag__close-icon`}
onClick={handleClose}
disabled={disabled}
aria-label={title}
title={title}>
<Close />
</button>
</div>
</Tag>
);
};
DismissibleTag.propTypes = {
/**
* Provide content to be rendered inside of a `DismissibleTag`
*/
children: PropTypes.node,

/**
* Provide a custom className that is applied to the containing <span>
*/
Expand Down Expand Up @@ -179,6 +205,11 @@ DismissibleTag.propTypes = {
*/
slug: PropTypes.node,

/**
* Provide text to be rendered inside of a the tag.
*/
text: PropTypes.string,

/**
* Text to show on clear filters
*/
Expand Down
Loading

0 comments on commit 96524a7

Please sign in to comment.