123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- import styled from '@emotion/styled';
- import omit from 'lodash/omit';
- import Link, {LinkProps} from 'sentry/components/links/link';
- import space from 'sentry/styles/space';
- import {Theme} from 'sentry/utils/theme';
- type MenuItemProps = {
- /**
- * Enable to allow default event on click
- */
- allowDefaultEvent?: boolean;
- 'aria-label'?: string;
- className?: string;
- /**
- * Is the item disabled?
- */
- disabled?: boolean;
- /**
- * Should this item act as a divider
- */
- divider?: boolean;
- /**
- * Provided to the onSelect callback when this item is selected
- */
- eventKey?: any;
- /**
- * Should this item act as a header
- */
- header?: boolean;
- /**
- * A server rendered URL.
- */
- href?: string;
- /**
- * Renders an icon next to the item
- */
- icon?: React.ReactNode;
- /**
- * Is the item actively selected?
- */
- isActive?: boolean;
- /**
- * Enable to provide custom button/contents via children
- */
- noAnchor?: boolean;
- /**
- * Triggered when the item is clicked
- */
- onSelect?: (eventKey: any) => void;
- /**
- * Enable to stop event propagation on click
- */
- stopPropagation?: boolean;
- /**
- * The title/tooltip of the item
- */
- title?: string;
- /**
- * A router target destination
- */
- to?: LinkProps['to'];
- /**
- * Renders a bottom border (excludes the last item)
- */
- withBorder?: boolean;
- };
- interface Props
- extends MenuItemProps,
- Omit<React.HTMLAttributes<HTMLLIElement>, 'onSelect'> {}
- const MenuItem = ({
- header,
- icon,
- divider,
- isActive,
- noAnchor,
- className,
- children,
- ...props
- }: Props) => {
- const {
- to,
- href,
- title,
- withBorder,
- disabled,
- onSelect,
- eventKey,
- allowDefaultEvent,
- stopPropagation,
- } = props;
- const handleClick = (e: React.MouseEvent): void => {
- if (disabled) {
- return;
- }
- if (onSelect) {
- if (allowDefaultEvent !== true) {
- e.preventDefault();
- }
- if (stopPropagation) {
- e.stopPropagation();
- }
- onSelect?.(eventKey);
- }
- };
- const renderAnchor = (): React.ReactNode => {
- const linkProps = {
- onClick: handleClick,
- tabIndex: -1,
- isActive,
- disabled,
- withBorder,
- };
- if (to) {
- return (
- <MenuLink to={to} {...linkProps} title={title} data-test-id="menu-item">
- {icon && <MenuIcon>{icon}</MenuIcon>}
- {children}
- </MenuLink>
- );
- }
- if (href) {
- return (
- <MenuAnchor {...linkProps} href={href} data-test-id="menu-item">
- {icon && <MenuIcon>{icon}</MenuIcon>}
- {children}
- </MenuAnchor>
- );
- }
- return (
- <MenuTarget role="button" {...linkProps} title={title} data-test-id="menu-item">
- {icon && <MenuIcon>{icon}</MenuIcon>}
- {children}
- </MenuTarget>
- );
- };
- let renderChildren: React.ReactNode | null = null;
- if (noAnchor) {
- renderChildren = children;
- } else if (header) {
- renderChildren = children;
- } else if (!divider) {
- renderChildren = renderAnchor();
- }
- return (
- <MenuListItem
- className={className}
- role="presentation"
- isActive={isActive}
- divider={divider}
- noAnchor={noAnchor}
- header={header}
- {...omit(props, ['href', 'title', 'onSelect', 'eventKey', 'to', 'as'])}
- >
- {renderChildren}
- </MenuListItem>
- );
- };
- interface MenuListItemProps extends React.HTMLAttributes<HTMLLIElement> {
- disabled?: boolean;
- divider?: boolean;
- header?: boolean;
- isActive?: boolean;
- noAnchor?: boolean;
- withBorder?: boolean;
- }
- function getListItemStyles(props: MenuListItemProps & {theme: Theme}) {
- const common = `
- display: block;
- padding: ${space(0.5)} ${space(2)};
- &:focus {
- outline: none;
- }
- `;
- if (props.disabled) {
- return `
- ${common}
- color: ${props.theme.disabled};
- background: transparent;
- cursor: not-allowed;
- `;
- }
- if (props.isActive) {
- return `
- ${common}
- color: ${props.theme.white};
- background: ${props.theme.active};
- &:hover {
- background: ${props.theme.activeHover};
- }
- `;
- }
- return `
- ${common}
- &:hover {
- background: ${props.theme.hover};
- }
- `;
- }
- function getChildStyles(props: MenuListItemProps & {theme: Theme}) {
- if (!props.noAnchor) {
- return '';
- }
- return `
- & a {
- ${getListItemStyles(props)}
- }
- `;
- }
- const shouldForwardProp = (p: PropertyKey) =>
- typeof p === 'string' && ['isActive', 'disabled', 'withBorder'].includes(p) === false;
- const MenuAnchor = styled('a', {shouldForwardProp})<MenuListItemProps>`
- ${getListItemStyles}
- `;
- const MenuListItem = styled('li')<MenuListItemProps>`
- display: block;
- ${p =>
- p.withBorder &&
- `
- border-bottom: 1px solid ${p.theme.innerBorder};
- &:last-child {
- border-bottom: none;
- }
- `};
- ${p =>
- p.divider &&
- `
- height: 1px;
- margin: ${space(0.5)} 0;
- overflow: hidden;
- background-color: ${p.theme.innerBorder};
- `}
- ${p =>
- p.header &&
- `
- padding: ${space(0.25)} ${space(0.5)};
- font-size: ${p.theme.fontSizeSmall};
- line-height: 1.4;
- color: ${p.theme.gray300};
- `}
- ${getChildStyles}
- `;
- const MenuTarget = styled('span')<MenuListItemProps>`
- ${getListItemStyles}
- display: flex;
- align-items: center;
- `;
- const MenuIcon = styled('div')`
- display: flex;
- align-items: center;
- margin-right: ${space(1)};
- `;
- const MenuLink = styled(Link, {shouldForwardProp})<MenuListItemProps>`
- ${getListItemStyles}
- `;
- export default MenuItem;
|