You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ContextMenuItem.tsx 6.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import React, { ReactNode, useCallback } from 'react';
  2. import { useSelector } from 'react-redux';
  3. import { makeStyles } from 'tss-react/mui';
  4. import { showOverflowDrawer } from '../../../../toolbox/functions.web';
  5. import Icon from '../../../icons/components/Icon';
  6. import { withPixelLineHeight } from '../../../styles/functions.web';
  7. import { TEXT_OVERFLOW_TYPES } from '../../constants.any';
  8. import TextWithOverflow from './TextWithOverflow';
  9. export interface IProps {
  10. /**
  11. * Label used for accessibility.
  12. */
  13. accessibilityLabel: string;
  14. /**
  15. * Component children.
  16. */
  17. children?: ReactNode;
  18. /**
  19. * CSS class name used for custom styles.
  20. */
  21. className?: string;
  22. /**
  23. * Id of dom element controlled by this item. Matches aria-controls.
  24. * Useful if you need this item as a tab element.
  25. *
  26. */
  27. controls?: string;
  28. /**
  29. * Custom icon. If used, the icon prop is ignored.
  30. * Used to allow custom children instead of just the default icons.
  31. */
  32. customIcon?: ReactNode;
  33. /**
  34. * Whether or not the action is disabled.
  35. */
  36. disabled?: boolean;
  37. /**
  38. * Default icon for action.
  39. */
  40. icon?: Function;
  41. /**
  42. * Id of the action container.
  43. */
  44. id?: string;
  45. /**
  46. * Click handler.
  47. */
  48. onClick?: (e?: React.MouseEvent<any>) => void;
  49. /**
  50. * Keydown handler.
  51. */
  52. onKeyDown?: (e: React.KeyboardEvent<HTMLDivElement>) => void;
  53. /**
  54. * Keypress handler.
  55. */
  56. onKeyPress?: (e?: React.KeyboardEvent) => void;
  57. /**
  58. * Overflow type for item text.
  59. */
  60. overflowType?: TEXT_OVERFLOW_TYPES;
  61. /**
  62. * You can use this item as a tab. Defaults to button if not set.
  63. *
  64. * If no onClick handler is provided, we assume the context menu item is
  65. * not interactive and no role will be set.
  66. */
  67. role?: 'tab' | 'button';
  68. /**
  69. * Whether the item is marked as selected.
  70. */
  71. selected?: boolean;
  72. /**
  73. * TestId of the element, if any.
  74. */
  75. testId?: string;
  76. /**
  77. * Action text.
  78. */
  79. text?: string;
  80. /**
  81. * Class name for the text.
  82. */
  83. textClassName?: string;
  84. }
  85. const useStyles = makeStyles()(theme => {
  86. return {
  87. contextMenuItem: {
  88. alignItems: 'center',
  89. cursor: 'pointer',
  90. display: 'flex',
  91. minHeight: '40px',
  92. padding: '10px 16px',
  93. boxSizing: 'border-box',
  94. '& > *:not(:last-child)': {
  95. marginRight: theme.spacing(3)
  96. },
  97. '&:hover': {
  98. backgroundColor: theme.palette.ui02
  99. },
  100. '&:active': {
  101. backgroundColor: theme.palette.ui03
  102. },
  103. '&.focus-visible': {
  104. boxShadow: `inset 0 0 0 2px ${theme.palette.action01Hover}`
  105. }
  106. },
  107. selected: {
  108. borderLeft: `3px solid ${theme.palette.action01Hover}`,
  109. paddingLeft: '13px',
  110. backgroundColor: theme.palette.ui02
  111. },
  112. contextMenuItemDisabled: {
  113. pointerEvents: 'none'
  114. },
  115. contextMenuItemDrawer: {
  116. padding: '13px 16px'
  117. },
  118. contextMenuItemIcon: {
  119. '& svg': {
  120. fill: theme.palette.icon01
  121. }
  122. },
  123. text: {
  124. ...withPixelLineHeight(theme.typography.bodyShortRegular),
  125. color: theme.palette.text01
  126. },
  127. drawerText: {
  128. ...withPixelLineHeight(theme.typography.bodyShortRegularLarge)
  129. }
  130. };
  131. });
  132. const ContextMenuItem = ({
  133. accessibilityLabel,
  134. children,
  135. className,
  136. controls,
  137. customIcon,
  138. disabled,
  139. id,
  140. icon,
  141. onClick,
  142. onKeyDown,
  143. onKeyPress,
  144. overflowType,
  145. role = 'button',
  146. selected,
  147. testId,
  148. text,
  149. textClassName }: IProps) => {
  150. const { classes: styles, cx } = useStyles();
  151. const _overflowDrawer: boolean = useSelector(showOverflowDrawer);
  152. const onKeyPressHandler = useCallback(e => {
  153. // only trigger the fallback behavior (onClick) if we dont have any explicit keyboard event handler
  154. if (onClick && !onKeyPress && !onKeyDown && (e.key === 'Enter' || e.key === ' ')) {
  155. e.preventDefault();
  156. onClick(e);
  157. }
  158. if (onKeyPress) {
  159. onKeyPress(e);
  160. }
  161. }, [ onClick, onKeyPress, onKeyDown ]);
  162. let tabIndex: undefined | 0 | -1;
  163. if (role === 'tab') {
  164. tabIndex = selected ? 0 : -1;
  165. }
  166. if (role === 'button' && !disabled) {
  167. tabIndex = 0;
  168. }
  169. return (
  170. <div
  171. aria-controls = { controls }
  172. aria-disabled = { disabled }
  173. aria-label = { accessibilityLabel }
  174. aria-selected = { role === 'tab' ? selected : undefined }
  175. className = { cx(styles.contextMenuItem,
  176. _overflowDrawer && styles.contextMenuItemDrawer,
  177. disabled && styles.contextMenuItemDisabled,
  178. selected && styles.selected,
  179. className
  180. ) }
  181. data-testid = { testId }
  182. id = { id }
  183. key = { text }
  184. onClick = { disabled ? undefined : onClick }
  185. onKeyDown = { disabled ? undefined : onKeyDown }
  186. onKeyPress = { disabled ? undefined : onKeyPressHandler }
  187. role = { onClick ? role : undefined }
  188. tabIndex = { onClick ? tabIndex : undefined }>
  189. {customIcon ? customIcon
  190. : icon && <Icon
  191. className = { styles.contextMenuItemIcon }
  192. size = { 20 }
  193. src = { icon } />}
  194. {text && (
  195. <TextWithOverflow
  196. className = { cx(styles.text,
  197. _overflowDrawer && styles.drawerText,
  198. textClassName) }
  199. overflowType = { overflowType } >
  200. {text}
  201. </TextWithOverflow>
  202. )}
  203. {children}
  204. </div>
  205. );
  206. };
  207. export default ContextMenuItem;