chore: Migrating dashboard/components/menu from jsx to tsx (#13361)

* fix: constant icon size

* clean up

* dashboard/components/menu jsx -> tsx

* more types

* package-lock.json sync

* package-lock fix

* fix

* lint fix

* added comment about event type
diff --git a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.jsx b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
similarity index 81%
rename from superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.jsx
rename to superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
index 93b5de2..34dcdf9 100644
--- a/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.jsx
+++ b/superset-frontend/src/dashboard/components/menu/BackgroundStyleDropdown.tsx
@@ -17,19 +17,21 @@
  * under the License.
  */
 import React from 'react';
-import PropTypes from 'prop-types';
 import cx from 'classnames';
 
 import backgroundStyleOptions from '../../util/backgroundStyleOptions';
-import PopoverDropdown from './PopoverDropdown';
+import PopoverDropdown, {
+  OptionProps,
+  OnChangeHandler,
+} from './PopoverDropdown';
 
-const propTypes = {
-  id: PropTypes.string.isRequired,
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};
+interface BackgroundStyleDropdownProps {
+  id: string;
+  value: string;
+  onChange: OnChangeHandler;
+}
 
-function renderButton(option) {
+function renderButton(option: OptionProps) {
   return (
     <div className={cx('background-style-option', option.className)}>
       {`${option.label} background`}
@@ -37,7 +39,7 @@
   );
 }
 
-function renderOption(option) {
+function renderOption(option: OptionProps) {
   return (
     <div className={cx('background-style-option', option.className)}>
       {option.label}
@@ -45,7 +47,7 @@
   );
 }
 
-export default class BackgroundStyleDropdown extends React.PureComponent {
+export default class BackgroundStyleDropdown extends React.PureComponent<BackgroundStyleDropdownProps> {
   render() {
     const { id, value, onChange } = this.props;
     return (
@@ -60,5 +62,3 @@
     );
   }
 }
-
-BackgroundStyleDropdown.propTypes = propTypes;
diff --git a/superset-frontend/src/dashboard/components/menu/HoverMenu.jsx b/superset-frontend/src/dashboard/components/menu/HoverMenu.tsx
similarity index 74%
rename from superset-frontend/src/dashboard/components/menu/HoverMenu.jsx
rename to superset-frontend/src/dashboard/components/menu/HoverMenu.tsx
index ef46b58..6fa21ec 100644
--- a/superset-frontend/src/dashboard/components/menu/HoverMenu.jsx
+++ b/superset-frontend/src/dashboard/components/menu/HoverMenu.tsx
@@ -16,23 +16,22 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-import React from 'react';
-import PropTypes from 'prop-types';
+import React, { RefObject } from 'react';
 import cx from 'classnames';
 
-const propTypes = {
-  position: PropTypes.oneOf(['left', 'top']),
-  innerRef: PropTypes.func,
-  children: PropTypes.node,
-};
+interface HoverMenuProps {
+  position: 'left' | 'top';
+  innerRef: RefObject<HTMLDivElement>;
+  children: React.ReactNode;
+}
 
-const defaultProps = {
-  position: 'left',
-  innerRef: null,
-  children: null,
-};
+export default class HoverMenu extends React.PureComponent<HoverMenuProps> {
+  static defaultProps = {
+    position: 'left',
+    innerRef: null,
+    children: null,
+  };
 
-export default class HoverMenu extends React.PureComponent {
   render() {
     const { innerRef, position, children } = this.props;
     return (
@@ -49,6 +48,3 @@
     );
   }
 }
-
-HoverMenu.propTypes = propTypes;
-HoverMenu.defaultProps = defaultProps;
diff --git a/superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.jsx b/superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.tsx
similarity index 82%
rename from superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.jsx
rename to superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.tsx
index 62a289a..c9fc042 100644
--- a/superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.jsx
+++ b/superset-frontend/src/dashboard/components/menu/MarkdownModeDropdown.tsx
@@ -17,16 +17,15 @@
  * under the License.
  */
 import React from 'react';
-import PropTypes from 'prop-types';
 import { t } from '@superset-ui/core';
 
-import PopoverDropdown from './PopoverDropdown';
+import PopoverDropdown, { OnChangeHandler } from './PopoverDropdown';
 
-const propTypes = {
-  id: PropTypes.string.isRequired,
-  value: PropTypes.string.isRequired,
-  onChange: PropTypes.func.isRequired,
-};
+interface MarkdownModeDropdownProps {
+  id: string;
+  value: string;
+  onChange: OnChangeHandler;
+}
 
 const dropdownOptions = [
   {
@@ -39,7 +38,7 @@
   },
 ];
 
-export default class MarkdownModeDropdown extends React.PureComponent {
+export default class MarkdownModeDropdown extends React.PureComponent<MarkdownModeDropdownProps> {
   render() {
     const { id, value, onChange } = this.props;
 
@@ -53,5 +52,3 @@
     );
   }
 }
-
-MarkdownModeDropdown.propTypes = propTypes;
diff --git a/superset-frontend/src/dashboard/components/menu/PopoverDropdown.jsx b/superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx
similarity index 69%
rename from superset-frontend/src/dashboard/components/menu/PopoverDropdown.jsx
rename to superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx
index d67cd38..15f56ed 100644
--- a/superset-frontend/src/dashboard/components/menu/PopoverDropdown.jsx
+++ b/superset-frontend/src/dashboard/components/menu/PopoverDropdown.tsx
@@ -17,33 +17,29 @@
  * under the License.
  */
 import React from 'react';
-import PropTypes from 'prop-types';
 import cx from 'classnames';
-import { styled, withTheme } from '@superset-ui/core';
+import { styled, withTheme, SupersetThemeProps } from '@superset-ui/core';
 import { Dropdown, Menu } from 'src/common/components';
 import Icon from 'src/components/Icon';
 
-const propTypes = {
-  id: PropTypes.string.isRequired,
-  options: PropTypes.arrayOf(
-    PropTypes.shape({
-      value: PropTypes.string.isRequired,
-      label: PropTypes.string.isRequired,
-      className: PropTypes.string,
-    }),
-  ).isRequired,
-  onChange: PropTypes.func.isRequired,
-  value: PropTypes.string.isRequired,
-  renderButton: PropTypes.func,
-  renderOption: PropTypes.func,
-};
+export interface OptionProps {
+  value: string;
+  label: string;
+  className?: string;
+}
 
-const defaultProps = {
-  renderButton: option => option.label,
-  renderOption: option => (
-    <div className={option.className}>{option.label}</div>
-  ),
-};
+export type OnChangeHandler = (key: React.Key) => void;
+export type RenderElementHandler = (option: OptionProps) => JSX.Element;
+
+interface PopoverDropdownProps {
+  id: string;
+  options: OptionProps[];
+  onChange: OnChangeHandler;
+  value: string;
+  theme: SupersetThemeProps['theme'];
+  renderButton: RenderElementHandler;
+  renderOption: RenderElementHandler;
+}
 
 const MenuItem = styled(Menu.Item)`
   &.ant-menu-item {
@@ -75,31 +71,34 @@
   }
 `;
 
-class PopoverDropdown extends React.PureComponent {
-  constructor(props) {
+interface HandleSelectProps {
+  key: React.Key;
+}
+
+class PopoverDropdown extends React.PureComponent<PopoverDropdownProps> {
+  constructor(props: PopoverDropdownProps) {
     super(props);
 
     this.handleSelect = this.handleSelect.bind(this);
   }
 
-  handleSelect({ key }) {
+  handleSelect({ key }: HandleSelectProps) {
     this.props.onChange(key);
   }
 
+  static defaultProps = {
+    renderButton: (option: OptionProps) => option.label,
+    renderOption: (option: OptionProps) => (
+      <div className={option.className}>{option.label}</div>
+    ),
+  };
+
   render() {
-    const {
-      id,
-      value,
-      options,
-      renderButton,
-      renderOption,
-      theme,
-    } = this.props;
+    const { value, options, renderButton, renderOption, theme } = this.props;
     const selected = options.find(opt => opt.value === value);
     return (
       <Dropdown
-        id={id}
-        trigger="click"
+        trigger={['click']}
         overlayStyle={{ zIndex: theme.zIndex.max }}
         overlay={
           <Menu onClick={this.handleSelect}>
@@ -118,7 +117,7 @@
         }
       >
         <div role="button" css={{ display: 'flex', alignItems: 'center' }}>
-          {renderButton(selected)}
+          {selected && renderButton(selected)}
           <Icon name="caret-down" css={{ marginTop: 4 }} />
         </div>
       </Dropdown>
@@ -126,7 +125,4 @@
   }
 }
 
-PopoverDropdown.propTypes = propTypes;
-PopoverDropdown.defaultProps = defaultProps;
-
 export default withTheme(PopoverDropdown);
diff --git a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx
similarity index 67%
rename from superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx
rename to superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx
index dcd8dcf..df44b4e 100644
--- a/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.jsx
+++ b/superset-frontend/src/dashboard/components/menu/WithPopoverMenu.tsx
@@ -17,44 +17,59 @@
  * under the License.
  */
 import React from 'react';
-import PropTypes from 'prop-types';
 import cx from 'classnames';
 
-const propTypes = {
-  children: PropTypes.node,
-  disableClick: PropTypes.bool,
-  menuItems: PropTypes.arrayOf(PropTypes.node),
-  onChangeFocus: PropTypes.func,
-  isFocused: PropTypes.bool,
-  shouldFocus: PropTypes.func,
-  editMode: PropTypes.bool.isRequired,
-  style: PropTypes.object,
+type ShouldFocusContainer = HTMLDivElement & {
+  contains: (event_target: EventTarget & HTMLElement) => Boolean;
 };
 
-const defaultProps = {
-  children: null,
-  disableClick: false,
-  onChangeFocus: null,
-  menuItems: [],
-  isFocused: false,
-  shouldFocus: (event, container) =>
-    container?.contains(event.target) ||
-    event.target.id === 'menu-item' ||
-    event.target.parentNode?.id === 'menu-item',
-  style: null,
-};
+interface WithPopoverMenuProps {
+  children: React.ReactNode;
+  disableClick: Boolean;
+  menuItems: React.ReactNode[];
+  onChangeFocus: (focus: Boolean) => void;
+  isFocused: Boolean;
+  // Event argument is left as "any" because of the clash. In defaultProps it seems
+  // like it should be React.FocusEvent<>, however from handleClick() we can also
+  // derive that type is EventListenerOrEventListenerObject.
+  shouldFocus: (event: any, container: ShouldFocusContainer) => Boolean;
+  editMode: Boolean;
+  style: React.CSSProperties;
+}
 
-class WithPopoverMenu extends React.PureComponent {
-  constructor(props) {
+interface WithPopoverMenuState {
+  isFocused: Boolean;
+}
+
+export default class WithPopoverMenu extends React.PureComponent<
+  WithPopoverMenuProps,
+  WithPopoverMenuState
+> {
+  container: ShouldFocusContainer;
+
+  static defaultProps = {
+    children: null,
+    disableClick: false,
+    onChangeFocus: null,
+    menuItems: [],
+    isFocused: false,
+    shouldFocus: (event: any, container: ShouldFocusContainer) =>
+      container?.contains(event.target) ||
+      event.target.id === 'menu-item' ||
+      event.target.parentNode?.id === 'menu-item',
+    style: null,
+  };
+
+  constructor(props: WithPopoverMenuProps) {
     super(props);
     this.state = {
-      isFocused: props.isFocused,
+      isFocused: props.isFocused!,
     };
     this.setRef = this.setRef.bind(this);
     this.handleClick = this.handleClick.bind(this);
   }
 
-  UNSAFE_componentWillReceiveProps(nextProps) {
+  UNSAFE_componentWillReceiveProps(nextProps: WithPopoverMenuProps) {
     if (nextProps.editMode && nextProps.isFocused && !this.state.isFocused) {
       document.addEventListener('click', this.handleClick);
       document.addEventListener('drag', this.handleClick);
@@ -71,11 +86,11 @@
     document.removeEventListener('drag', this.handleClick);
   }
 
-  setRef(ref) {
+  setRef(ref: ShouldFocusContainer) {
     this.container = ref;
   }
 
-  handleClick(event) {
+  handleClick(event: any) {
     if (!this.props.editMode) {
       return;
     }
@@ -84,6 +99,7 @@
       shouldFocus: shouldFocusFunc,
       disableClick,
     } = this.props;
+
     const shouldFocus = shouldFocusFunc(event, this.container);
 
     if (!disableClick && shouldFocus && !this.state.isFocused) {
@@ -121,9 +137,9 @@
         style={style}
       >
         {children}
-        {editMode && isFocused && menuItems.length > 0 && (
+        {editMode && isFocused && (menuItems?.length ?? 0) > 0 && (
           <div className="popover-menu">
-            {menuItems.map((node, i) => (
+            {menuItems.map((node: React.ReactNode, i: Number) => (
               <div className="menu-item" key={`menu-item-${i}`}>
                 {node}
               </div>
@@ -134,8 +150,3 @@
     );
   }
 }
-
-WithPopoverMenu.propTypes = propTypes;
-WithPopoverMenu.defaultProps = defaultProps;
-
-export default WithPopoverMenu;