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, '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 ( {icon && {icon}} {children} ); } if (href) { return ( {icon && {icon}} {children} ); } return ( {icon && {icon}} {children} ); }; let renderChildren: React.ReactNode | null = null; if (noAnchor) { renderChildren = children; } else if (header) { renderChildren = children; } else if (!divider) { renderChildren = renderAnchor(); } return ( {renderChildren} ); }; interface MenuListItemProps extends React.HTMLAttributes { 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})` ${getListItemStyles} `; const MenuListItem = styled('li')` 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')` ${getListItemStyles} display: flex; align-items: center; `; const MenuIcon = styled('div')` display: flex; align-items: center; margin-right: ${space(1)}; `; const MenuLink = styled(Link, {shouldForwardProp})` ${getListItemStyles} `; export default MenuItem;