123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- import {useCallback} from 'react';
- import type {Theme} from '@emotion/react';
- import styled from '@emotion/styled';
- import {Button} from 'sentry/components/button';
- import ExternalLink from 'sentry/components/links/externalLink';
- import type {LinkProps} from 'sentry/components/links/link';
- import Link from 'sentry/components/links/link';
- import type {TooltipProps} from 'sentry/components/tooltip';
- import {Tooltip} from 'sentry/components/tooltip';
- import {IconClose, IconOpen} from 'sentry/icons';
- import type {SVGIconProps} from 'sentry/icons/svgIcon';
- import {IconDefaultsProvider} from 'sentry/icons/useIconDefaults';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import {trackAnalytics} from 'sentry/utils/analytics';
- import type {Color} from 'sentry/utils/theme';
- import theme from 'sentry/utils/theme';
- interface TagProps extends React.HTMLAttributes<HTMLSpanElement> {
- /**
- * Makes the tag clickable. Use for external links.
- * If no icon is passed, it defaults to IconOpen (can be removed by passing icon={null})
- */
- href?: string;
- /**
- * Icon on the left side.
- */
- icon?: React.ReactNode;
- /**
- * Triggered when the item is clicked
- */
- onClick?: (eventKey: any) => void;
- /**
- * Shows clickable IconClose on the right side.
- */
- onDismiss?: () => void;
- /**
- * Max width of the tag's text
- */
- textMaxWidth?: number;
- /**
- * Makes the tag clickable. Use for internal links handled by react router.
- * If no icon is passed, it defaults to IconOpen (can be removed by passing icon={null})
- */
- to?: LinkProps['to'];
- /**
- * Additional properites for the Tooltip when `tooltipText` is set.
- */
- tooltipProps?: Omit<TooltipProps, 'children' | 'title' | 'skipWrapper'>;
- /**
- * Text to show up on a hover.
- */
- tooltipText?: TooltipProps['title'];
- /**
- * Dictates color scheme of the tag.
- */
- type?: keyof Theme['tag'];
- }
- function BaseTag({
- type = 'default',
- icon,
- tooltipText,
- tooltipProps,
- to,
- onClick,
- href,
- onDismiss,
- children,
- textMaxWidth = 150,
- ...props
- }: TagProps) {
- const iconsProps: SVGIconProps = {
- size: 'xs',
- color: theme.tag[type].color as Color,
- };
- const isLink = href !== undefined || to !== undefined;
- // Links use the IconOpen by default
- const linkIcon = isLink ? <IconOpen /> : null;
- const tagIcon = icon || icon === null ? icon : linkIcon;
- const handleDismiss = useCallback<React.MouseEventHandler>(
- event => {
- event.preventDefault();
- onDismiss?.();
- },
- [onDismiss]
- );
- const trackClickEvent = useCallback(() => {
- trackAnalytics('tag.clicked', {
- is_clickable: onClick !== undefined || isLink,
- organization: null,
- });
- }, [isLink, onClick]);
- const tag = (
- <Tooltip title={tooltipText} containerDisplayMode="inline-flex" {...tooltipProps}>
- <Background type={type}>
- {tagIcon && (
- <IconWrapper>
- <IconDefaultsProvider {...iconsProps}>{tagIcon}</IconDefaultsProvider>
- </IconWrapper>
- )}
- <Text type={type} maxWidth={textMaxWidth}>
- {children}
- </Text>
- {onDismiss !== undefined && (
- <DismissButton
- onClick={handleDismiss}
- size="zero"
- priority="link"
- aria-label={t('Dismiss')}
- icon={<IconClose isCircled {...iconsProps} />}
- />
- )}
- </Background>
- </Tooltip>
- );
- const tagWithParent =
- href !== undefined ? (
- <ExternalLink href={href}>{tag}</ExternalLink>
- ) : to !== undefined ? (
- <Link to={to} onClick={onClick}>
- {tag}
- </Link>
- ) : (
- tag
- );
- return (
- <span {...props} onClick={trackClickEvent}>
- {tagWithParent}
- </span>
- );
- }
- const Tag = styled(BaseTag)`
- font-size: ${p => p.theme.fontSizeSmall};
- `;
- const TAG_HEIGHT = '20px';
- export const Background = styled('div')<{type: keyof Theme['tag']}>`
- display: inline-flex;
- align-items: center;
- height: ${TAG_HEIGHT};
- border-radius: ${TAG_HEIGHT};
- background-color: ${p => p.theme.tag[p.type].background};
- border: solid 1px ${p => p.theme.tag[p.type].border};
- padding: 0 ${space(1)};
- `;
- const IconWrapper = styled('span')`
- margin-right: ${space(0.5)};
- display: inline-flex;
- `;
- const Text = styled('span')<{maxWidth: number; type: keyof Theme['tag']}>`
- color: ${p => p.theme.tag[p.type].color};
- max-width: ${p => p.maxWidth}px;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
- line-height: ${TAG_HEIGHT};
- `;
- const DismissButton = styled(Button)`
- margin-left: ${space(0.5)};
- margin-right: -${space(0.5)};
- border: none;
- `;
- export {Tag, type TagProps};
- const DO_NOT_USE_TAG = Tag;
- export default DO_NOT_USE_TAG;
|