All files / Tag Tag.tsx

100% Statements 17/17
100% Branches 4/4
100% Functions 2/2
100% Lines 17/17

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186                      2x   2x                                                                     2x   2x               2x                   151x   151x 1x     151x 151x 151x 151x   151x                                                                           2x 2x   2x                                           2x                                                                                      
import React from 'react';
import PropTypes from 'prop-types';
import _ from 'lodash';
import { lucidClassNames } from '../../util/style-helpers';
import CloseIcon from '../Icon/CloseIcon/CloseIcon';
import {
	StandardProps,
	filterTypes,
	rejectTypes,
} from '../../util/component-types';
 
const cx = lucidClassNames.bind('&-Tag');
 
const { bool, func, node, string, oneOf } = PropTypes;
 
export interface ITagProps
	extends StandardProps,
		React.DetailedHTMLProps<
			React.HTMLAttributes<HTMLDivElement>,
			HTMLDivElement
		> {
	/** Set this prop if you're using three levels of tags so it can be styled
		appropriately. This is required because we aren't able to know if your
		Tags have grand children efficiently. */
	isTop: boolean;
 
	/** Use the light background when your tags are on a white page background.
		Use a dark background when your tags need to be placed on a darker
		background (e.g. in a page header). */
	hasLightBackground: boolean;
 
	/** Shows or hides the little "x" for a given tag. */
	isRemovable: boolean;
 
	/** Style variations of the `Tag`. */
	kind?: 'primary' | 'success' | 'warning' | 'danger' | 'info' | 'default';
 
	/** Called when the user clicks to remove a tag. */
	onRemove: ({
		props,
		event,
	}: {
		props: ITagProps;
		event: React.MouseEvent;
	}) => void;
}
 
/** TODO: Remove the nonPassThroughs when the component is converted to a functional component */
const nonPassThroughs = ['callbackId', 'initialState'];
 
const defaultProps = {
	isTop: false,
	hasLightBackground: true,
	isRemovable: false,
	kind: 'default' as const,
	onRemove: _.noop,
};
 
export const Tag = (props: ITagProps): React.ReactElement => {
	const {
		isTop,
		hasLightBackground,
		isRemovable,
		kind,
		onRemove,
		children,
		className,
		...passThroughs
	} = props;
 
	const handleRemove = ({ event }: { event: React.MouseEvent }): void => {
		onRemove({ props, event });
	};
 
	const subTags = filterTypes(children, Tag);
	const otherChildren = rejectTypes(children, Tag);
	const hasOtherChildren = !_.isEmpty(otherChildren);
	const isLeaf = _.isEmpty(subTags);
 
	return (
		<div
			{..._.omit(passThroughs, nonPassThroughs)}
			className={cx(
				'&',
				{
					'&-is-top': isTop,
					'&-is-leaf': isLeaf,
					'&-is-removable': isRemovable,
					'&-has-light-background': hasLightBackground,
					'&-default': kind === 'default',
					'&-primary': kind === 'primary',
					'&-success': kind === 'success',
					'&-warning': kind === 'warning',
					'&-danger': kind === 'danger',
					'&-info': kind === 'info',
				},
				className
			)}
		>
			{hasOtherChildren && (
				<span className={cx('&-other-children')}>
					{otherChildren}
					{isRemovable && (
						<CloseIcon
							onClick={handleRemove}
							className={cx('&-remove-button')}
							size={7}
							isClickable
						/>
					)}
				</span>
			)}
			{subTags}
		</div>
	);
};
 
Tag.defaultProps = defaultProps;
Tag.displayName = 'Tag';
 
Tag.peek = {
	description: `\`Tag\` is a visualization for selected settings.`,
	notes: {
		overview: `
					A visualization for items. Tags can be removable and can be nested into groups.
					`,
		intendedUse: `
					Tags are typically used to display filter selections. Tags can be interactive or display-only. They can also be grouped into a parent container.
											
					**Styling notes**
					
					- The default style is not interactive, it does not have a \`CloseIcon\`.
					- Use \`isRemovable='true'\` for interactive tags.
					- Styling is optimized for 40 or fewer characters.
					`,
		technicalRecommendations: `
					None
			`,
	},
	categories: ['communication'],
};
 
Tag.propTypes = {
	/**
		Set this prop if you're using three levels of tags so it can be styled
		appropriately. This is required because we aren't able to know if your
		Tags have grand children efficiently.
	*/
	isTop: bool,
 
	/**
		Use the light background when your tags are on a white page background.
		Use a dark background when your tags need to be placed on a darker
		background (e.g. in a page header).
	*/
	hasLightBackground: bool,
 
	/**
		Shows or hides the little "x" for a given tag.
	*/
	isRemovable: bool,
 
	/**
		Style variations of the \`Tag\`.
	*/
	kind: oneOf(['primary', 'success', 'warning', 'danger', 'info', 'default']),
 
	/**
		Called when the user clicks to remove a tag.  Signature:
		\`({props, event}) => {}\`
	*/
	onRemove: func,
 
	/**
		Can contain elements or nested \`Tag\` components.
	*/
	children: node,
 
	/**
		Appended to the component-specific class names set on the root element.
	*/
	className: string,
};
 
export default Tag;