@carbon/react
Version:
React components for the Carbon Design System
209 lines (204 loc) • 8.06 kB
JavaScript
/**
* Copyright IBM Corp. 2016, 2023
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { extends as _extends } from '../../_virtual/_rollupPluginBabelHelpers.js';
import PropTypes from 'prop-types';
import React, { useRef, useState, useLayoutEffect, cloneElement } from 'react';
import cx from 'classnames';
import { Close } from '@carbon/icons-react';
import { useId } from '../../internal/useId.js';
import { usePrefix } from '../../internal/usePrefix.js';
import '../Text/index.js';
import { deprecate } from '../../prop-types/deprecate.js';
import { DefinitionTooltip } from '../Tooltip/DefinitionTooltip.js';
import '../Tooltip/Tooltip.js';
import { isEllipsisActive } from './isEllipsisActive.js';
import { useMergedRefs } from '../../internal/useMergedRefs.js';
import { AILabel } from '../AILabel/index.js';
import { isComponentElement } from '../../internal/utils.js';
import { Text } from '../Text/Text.js';
var _Close;
const TYPES = {
red: 'Red',
magenta: 'Magenta',
purple: 'Purple',
blue: 'Blue',
cyan: 'Cyan',
teal: 'Teal',
green: 'Green',
gray: 'Gray',
'cool-gray': 'Cool-Gray',
'warm-gray': 'Warm-Gray',
'high-contrast': 'High-Contrast',
outline: 'Outline'
};
const SIZES = {
sm: 'sm',
md: 'md',
lg: 'lg'
};
const TagBase = /*#__PURE__*/React.forwardRef(({
children,
className,
decorator,
id,
type,
filter,
// remove filter in next major release - V12
renderIcon: CustomIconElement,
title = 'Clear filter',
// remove title in next major release - V12
disabled,
onClose,
// remove onClose in next major release - V12
size,
as: BaseComponent,
slug,
...other
}, forwardRef) => {
const prefix = usePrefix();
const tagRef = useRef(null);
if (filter) {
console.warn('The `filter` prop for Tag has been deprecated and will be removed in the next major version. Use DismissibleTag instead.');
}
if (onClose) {
console.warn('The `onClose` prop for Tag has been deprecated and will be removed in the next major version. Use DismissibleTag instead.');
}
const ref = useMergedRefs([forwardRef, tagRef]);
const tagId = id || `tag-${useId()}`;
const [isEllipsisApplied, setIsEllipsisApplied] = useState(false);
useLayoutEffect(() => {
const newElement = tagRef.current?.getElementsByClassName(`${prefix}--tag__label`)[0];
setIsEllipsisApplied(isEllipsisActive(newElement));
}, [prefix, tagRef]);
const conditions = [`${prefix}--tag--selectable`, `${prefix}--tag--filter`, `${prefix}--tag--operational`];
const isInteractiveTag = conditions.some(el => className?.includes(el));
const tagClasses = cx(`${prefix}--tag`, className, {
[`${prefix}--tag--disabled`]: disabled,
[`${prefix}--tag--filter`]: filter,
[`${prefix}--tag--${size}`]: size,
// TODO: V12 - Remove this class
[`${prefix}--layout--size-${size}`]: size,
[`${prefix}--tag--${type}`]: type,
[`${prefix}--tag--interactive`]: other.onClick && !isInteractiveTag && isEllipsisApplied
});
const typeText = type !== undefined && type in Object.keys(TYPES) ? TYPES[type] : '';
const handleClose = event => {
if (onClose) {
event.stopPropagation();
onClose(event);
}
};
// AILabel is always size `sm` and `inline`
const candidate = slug ?? decorator;
const candidateIsAILabel = isComponentElement(candidate, AILabel);
const normalizedDecorator = candidateIsAILabel && !isInteractiveTag ? /*#__PURE__*/cloneElement(candidate, {
size: 'sm',
kind: 'inline'
}) : null;
if (filter) {
const ComponentTag = BaseComponent ?? 'div';
return /*#__PURE__*/React.createElement(ComponentTag, _extends({
className: tagClasses,
id: tagId
}, other), CustomIconElement && size !== 'sm' ? /*#__PURE__*/React.createElement("div", {
className: `${prefix}--tag__custom-icon`
}, /*#__PURE__*/React.createElement(CustomIconElement, null)) : '', /*#__PURE__*/React.createElement(Text, {
title: typeof children === 'string' ? children : undefined,
className: `${prefix}--tag__label`
}, children !== null && children !== undefined ? children : typeText), normalizedDecorator, /*#__PURE__*/React.createElement("button", {
type: "button",
className: `${prefix}--tag__close-icon`,
onClick: handleClose,
disabled: disabled,
"aria-label": title,
title: title
}, _Close || (_Close = /*#__PURE__*/React.createElement(Close, null))));
}
const ComponentTag = BaseComponent ?? (other.onClick || className?.includes(`${prefix}--tag--operational`) ? 'button' : 'div');
const labelClasses = cx({
[`${prefix}--tag__label`]: !isInteractiveTag
});
return /*#__PURE__*/React.createElement(ComponentTag, _extends({
ref: ref,
disabled: disabled,
className: tagClasses,
id: tagId,
type: ComponentTag === 'button' ? 'button' : undefined
}, other), CustomIconElement && size !== 'sm' ? /*#__PURE__*/React.createElement("div", {
className: `${prefix}--tag__custom-icon`
}, /*#__PURE__*/React.createElement(CustomIconElement, null)) : '', isEllipsisApplied && !isInteractiveTag ? /*#__PURE__*/React.createElement(DefinitionTooltip, {
openOnHover: false,
definition: children !== null && children !== undefined ? children : typeText,
className: `${prefix}--definition--tooltip--tag`
}, /*#__PURE__*/React.createElement(Text, {
title: children !== null && children !== undefined && typeof children === 'string' ? children : typeText,
className: labelClasses
}, children !== null && children !== undefined ? children : typeText)) : /*#__PURE__*/React.createElement(Text, {
title: children !== null && children !== undefined && typeof children === 'string' ? children : typeText,
className: labelClasses
}, children !== null && children !== undefined ? children : typeText), slug ? normalizedDecorator : decorator ? /*#__PURE__*/React.createElement("div", {
className: `${prefix}--tag__decorator`
}, normalizedDecorator) : '');
});
const Tag = TagBase;
Tag.propTypes = {
/**
* Provide an alternative tag or component to use instead of the default
* wrapping element
*/
as: PropTypes.elementType,
/**
* Provide content to be rendered inside of a `Tag`
*/
children: PropTypes.node,
/**
* Provide a custom className that is applied to the containing <span>
*/
className: PropTypes.string,
/**
* **Experimental:** Provide a `decorator` component to be rendered inside the `Tag` component
*/
decorator: PropTypes.node,
/**
* Specify if the `Tag` is disabled
*/
disabled: PropTypes.bool,
/**
* Determine if `Tag` is a filter/chip
*/
filter: deprecate(PropTypes.bool, 'The `filter` prop has been deprecated and will be removed in the next major version. Use DismissibleTag instead.'),
/**
* Specify the id for the tag.
*/
id: PropTypes.string,
/**
* Click handler for filter tag close button.
*/
onClose: deprecate(PropTypes.func, 'The `onClose` prop has been deprecated and will be removed in the next major version. Use DismissibleTag instead.'),
/**
* A component used to render an icon.
*/
renderIcon: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
/**
* Specify the size of the Tag. Currently supports either `sm`,
* `md` (default) or `lg` sizes.
*/
size: PropTypes.oneOf(Object.keys(SIZES)),
/**
* **Experimental:** Provide a `Slug` component to be rendered inside the `Tag` component
*/
slug: deprecate(PropTypes.node, 'The `slug` prop has been deprecated and will be removed in the next major version. Use the decorator prop instead.'),
/**
* Text to show on clear filters
*/
title: deprecate(PropTypes.string, 'The `title` prop has been deprecated and will be removed in the next major version. Use DismissibleTag instead.'),
/**
* Specify the type of the `Tag`
*/
type: PropTypes.oneOf(Object.keys(TYPES))
};
export { SIZES, TYPES, Tag as default };