From 2d66b51c7dc5b44cba51e92730276de83c24abf0 Mon Sep 17 00:00:00 2001 From: mnajdova Date: Fri, 13 Oct 2023 13:22:14 +0200 Subject: [PATCH] Convert to TS + fixes --- packages/mui-material-next/src/Menu/Menu.d.ts | 123 ----------------- .../src/Menu/{Menu.js => Menu.tsx} | 87 ++++++++---- .../mui-material-next/src/Menu/Menu.types.ts | 126 ++++++++++++++++++ 3 files changed, 184 insertions(+), 152 deletions(-) delete mode 100644 packages/mui-material-next/src/Menu/Menu.d.ts rename packages/mui-material-next/src/Menu/{Menu.js => Menu.tsx} (83%) create mode 100644 packages/mui-material-next/src/Menu/Menu.types.ts diff --git a/packages/mui-material-next/src/Menu/Menu.d.ts b/packages/mui-material-next/src/Menu/Menu.d.ts deleted file mode 100644 index 83af53d6d20312..00000000000000 --- a/packages/mui-material-next/src/Menu/Menu.d.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* eslint-disable no-restricted-imports */ -import * as React from 'react'; -import { SxProps } from '@mui/system'; -import { MenuOwnerState, SlotComponentProps, MenuActions } from '@mui/base'; -import { InternalStandardProps as StandardProps } from '@mui/material'; -import Paper, { PaperProps } from '@mui/material/Paper'; -import { PopoverProps } from '@mui/material/Popover'; -import { MenuListProps } from '@mui/material/MenuList'; -import { Theme } from '@mui/material/styles'; -import { TransitionProps } from '@mui/material/transitions/transition'; -import { MenuClasses } from './menuClasses'; - -export interface MenuProps - extends StandardProps, 'children'> { - /** - * A ref with imperative actions that can be performed on the menu. - */ - actions?: React.Ref; - /** - * An HTML element, or a function that returns one. - * It's used to set the position of the menu. - */ - anchorEl?: PopoverProps['anchorEl']; - /** - * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled - * children are not focusable. If you set this prop to `false` focus will be placed - * on the parent modal container. This has severe accessibility implications - * and should only be considered if you manage focus otherwise. - * @default true - */ - autoFocus?: boolean; - /** - * Menu contents, normally `MenuItem`s. - */ - children?: React.ReactNode; - /** - * Override or extend the styles applied to the component. - */ - classes?: Partial; - /** - * When opening the menu will not focus the active item but the `[role="menu"]` - * unless `autoFocus` is also set to `false`. Not using the default means not - * following WAI-ARIA authoring practices. Please be considerate about possible - * accessibility implications. - * @default false - */ - disableAutoFocusItem?: boolean; - /** - * Props applied to the [`MenuList`](/material-ui/api/menu-list/) element. - * @default {} - */ - MenuListProps?: Partial; - /** - * Callback fired when the component requests to be closed. - * - * @param {object} event The event source of the callback. - * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`. - */ - onClose?: PopoverProps['onClose']; - /** - * If `true`, the component is shown. - */ - open?: boolean; - /** - * `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element. - */ - PopoverClasses?: PopoverProps['classes']; - /** - * The components used for each slot inside. - * - * @default {} - */ - slots?: { - root?: React.ElementType; - paper?: React.ElementType; - }; - /** - * The extra props for the slot components. - * You can override the existing props or add new ones. - * - * @default {} - */ - slotProps?: { - root?: SlotComponentProps; - paper?: SlotComponentProps; - }; - /** - * The system prop that allows defining system overrides as well as additional CSS styles. - */ - sx?: SxProps; - /** - * The length of the transition in `ms`, or 'auto' - * @default 'auto' - */ - transitionDuration?: TransitionProps['timeout'] | 'auto'; - /** - * Props applied to the transition element. - * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. - * @default {} - */ - TransitionProps?: TransitionProps; - /** - * The variant to use. Use `menu` to prevent selected items from impacting the initial focus. - * @default 'selectedMenu' - */ - variant?: 'menu' | 'selectedMenu'; -} - -export declare const MenuPaper: React.FC; - -/** - * - * Demos: - * - * - [App Bar](https://mui.com/material-ui/react-app-bar/) - * - [Menu](https://mui.com/material-ui/react-menu/) - * - * API: - * - * - [Menu API](https://mui.com/material-ui/api/menu/) - * - inherits [Popover API](https://mui.com/material-ui/api/popover/) - */ -export default function Menu(props: MenuProps): JSX.Element; diff --git a/packages/mui-material-next/src/Menu/Menu.js b/packages/mui-material-next/src/Menu/Menu.tsx similarity index 83% rename from packages/mui-material-next/src/Menu/Menu.js rename to packages/mui-material-next/src/Menu/Menu.tsx index 620e3e2470c27d..6f2128e8318f62 100644 --- a/packages/mui-material-next/src/Menu/Menu.js +++ b/packages/mui-material-next/src/Menu/Menu.tsx @@ -3,28 +3,34 @@ import * as React from 'react'; import { isFragment } from 'react-is'; import PropTypes from 'prop-types'; import clsx from 'clsx'; +import { OverridableComponent } from '@mui/types'; import { unstable_composeClasses as composeClasses } from '@mui/base/composeClasses'; import { useMenu, MenuProvider } from '@mui/base/useMenu'; import { useDropdown, DropdownContext } from '@mui/base/useDropdown'; import { useSlotProps } from '@mui/base/utils'; import { ListActionTypes } from '@mui/base/useList'; -import { HTMLElementType } from '@mui/utils'; -import Popover, { PopoverPaper } from '@mui/material/Popover'; +import { + HTMLElementType, + unstable_getScrollbarSize as getScrollbarSize, + unstable_ownerDocument as ownerDocument, +} from '@mui/utils'; +import Popover, { PopoverPaper, PopoverOrigin } from '@mui/material/Popover'; import { styled, useTheme, useThemeProps } from '@mui/material/styles'; import { rootShouldForwardProp } from '@mui/material/styles/styled'; +import { MenuTypeMap, MenuOwnerState } from './Menu.types'; import { getMenuUtilityClass } from './menuClasses'; -const RTL_ORIGIN = { +const RTL_ORIGIN: PopoverOrigin = { vertical: 'top', horizontal: 'right', }; -const LTR_ORIGIN = { +const LTR_ORIGIN: PopoverOrigin = { vertical: 'top', horizontal: 'left', }; -const useUtilityClasses = (ownerState) => { +const useUtilityClasses = (ownerState: MenuOwnerState) => { const { classes } = ownerState; const slots = { @@ -37,7 +43,7 @@ const useUtilityClasses = (ownerState) => { }; const MenuRoot = styled(Popover, { - shouldForwardProp: (prop) => rootShouldForwardProp(prop) || prop === 'classes', + shouldForwardProp: (prop: string) => rootShouldForwardProp(prop) || prop === 'classes', name: 'MuiMenu', slot: 'Root', overridesResolver: (props, styles) => styles.root, @@ -92,8 +98,16 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { const theme = useTheme(); const isRtl = theme.direction === 'rtl'; + const listRef = React.useRef(null); + + const { contextValue, getListboxProps, dispatch, open, triggerElement } = useMenu({ + // onItemsChange, + disabledItemsFocusable: Boolean(MenuListProps.disabledItemsFocusable), + }); + const ownerState = { ...props, + open, autoFocus, disableAutoFocusItem, MenuListProps, @@ -104,11 +118,6 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { variant, }; - const { contextValue, getListboxProps, dispatch, open, triggerElement } = useMenu({ - // onItemsChange, - disabledItemsFocusable: Boolean(MenuListProps.disabledItemsFocusable), - }); - React.useImperativeHandle( actions, () => ({ @@ -122,11 +131,19 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { const autoFocusItem = autoFocus && !disableAutoFocusItem && open; - const menuListActionsRef = React.useRef(null); - - const handleEntering = (element, isAppearing) => { - if (menuListActionsRef.current) { - menuListActionsRef.current.adjustStyleForScrollbar(element, theme); + const handleEntering = (element: HTMLElement, isAppearing: boolean) => { + // adjust styles for scrollbar + if (element && listRef.current) { + // Let's ignore that piece of logic if users are already overriding the width + // of the menu. + const containerElement = element; + const noExplicitWidth = !listRef.current.style.width; + if (containerElement.clientHeight < listRef?.current?.clientHeight && noExplicitWidth) { + const scrollbarSize = `${getScrollbarSize(ownerDocument(containerElement))}px`; + listRef.current.style[theme.direction === 'rtl' ? 'paddingLeft' : 'paddingRight'] = + scrollbarSize; + listRef.current.style.width = `calc(100% + ${scrollbarSize})`; + } } if (onEntering) { @@ -134,7 +151,7 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { } }; - const handleListKeyDown = (event) => { + const handleListKeyDown = (event: React.KeyboardEvent) => { if (event.key === 'Tab') { event.preventDefault(); @@ -211,10 +228,11 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { }, externalSlotProps: (args) => ({ ...(typeof slotProps.listbox === 'function' ? slotProps.listbox(args) : slotProps.listbox), - // TOD: Make sure all previous support props still work + // TODO: Make sure all previous support props still work ...MenuListProps, }), additionalProps: { + ref: listRef, variant, autoFocusItem, autoFocus: autoFocus && (activeItemIndex === -1 || disableAutoFocusItem), @@ -243,6 +261,7 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { ref={ref} transitionDuration={transitionDuration} TransitionProps={{ onEntering: handleEntering, ...TransitionProps }} + // @ts-ignore internal usage ownerState={ownerState} anchorEl={anchorEl ?? triggerElement} {...other} @@ -253,26 +272,36 @@ const MenuInner = React.forwardRef(function Menu(inProps, ref) { ); -}); - +}) as OverridableComponent; + +/** + * + * Demos: + * + * - [App Bar](https://mui.com/material-ui/react-app-bar/) + * - [Menu](https://mui.com/material-ui/react-menu/) + * + * API: + * + * - [Menu API](https://mui.com/material-ui/api/menu/) + * - inherits [Popover API](https://mui.com/material-ui/api/popover/) + */ const Menu = React.forwardRef(function Menu(inProps, ref) { - const { open, anchorEl, ...other } = inProps; + const { open } = inProps; const upperDropdownContext = React.useContext(DropdownContext); const { contextValue: dropdownContextValue } = useDropdown({ open, - anchorEl, }); - const Wrapper = !upperDropdownContext ? DropdownContext.Provider : React.Fragment; - const wrapperProps = !upperDropdownContext ? { value: dropdownContextValue } : {}; - - return ( - + return !upperDropdownContext ? ( + - + + ) : ( + ); -}); +}) as OverridableComponent; Menu.propTypes /* remove-proptypes */ = { // ----------------------------- Warning -------------------------------- diff --git a/packages/mui-material-next/src/Menu/Menu.types.ts b/packages/mui-material-next/src/Menu/Menu.types.ts new file mode 100644 index 00000000000000..4049d6d60f23f1 --- /dev/null +++ b/packages/mui-material-next/src/Menu/Menu.types.ts @@ -0,0 +1,126 @@ +/* eslint-disable no-restricted-imports */ +import * as React from 'react'; +import { SxProps } from '@mui/system'; +import { OverrideProps } from '@mui/types'; +import { SlotComponentProps, MenuActions } from '@mui/base'; +import { InternalStandardProps as StandardProps } from '@mui/material'; +import Paper from '@mui/material/Paper'; +import Popover, { PopoverProps } from '@mui/material/Popover'; +import { MenuListProps } from '@mui/material/MenuList'; +import { Theme } from '@mui/material/styles'; +import { TransitionProps } from '@mui/material/transitions/transition'; +import { MenuClasses } from './menuClasses'; + +export interface MenuTypeMap { + props: AdditionalProps & + StandardProps, 'children'> & { + /** + * A ref with imperative actions that can be performed on the menu. + */ + actions?: React.Ref; + /** + * An HTML element, or a function that returns one. + * It's used to set the position of the menu. + */ + anchorEl?: PopoverProps['anchorEl']; + /** + * If `true` (Default) will focus the `[role="menu"]` if no focusable child is found. Disabled + * children are not focusable. If you set this prop to `false` focus will be placed + * on the parent modal container. This has severe accessibility implications + * and should only be considered if you manage focus otherwise. + * @default true + */ + autoFocus?: boolean; + /** + * Menu contents, normally `MenuItem`s. + */ + children?: React.ReactNode; + /** + * Override or extend the styles applied to the component. + */ + classes?: Partial; + /** + * When opening the menu will not focus the active item but the `[role="menu"]` + * unless `autoFocus` is also set to `false`. Not using the default means not + * following WAI-ARIA authoring practices. Please be considerate about possible + * accessibility implications. + * @default false + */ + disableAutoFocusItem?: boolean; + /** + * Props applied to the [`MenuList`](/material-ui/api/menu-list/) element. + * @default {} + */ + MenuListProps?: Partial; + /** + * Callback fired when the component requests to be closed. + * + * @param {object} event The event source of the callback. + * @param {string} reason Can be: `"escapeKeyDown"`, `"backdropClick"`, `"tabKeyDown"`. + */ + onClose?: { + bivarianceHack(event: {}, reason: 'backdropClick' | 'escapeKeyDown' | 'tabKeyDown'): void; + }['bivarianceHack']; + /** + * If `true`, the component is shown. + */ + open?: boolean; + /** + * `classes` prop applied to the [`Popover`](/material-ui/api/popover/) element. + */ + PopoverClasses?: PopoverProps['classes']; + /** + * The components used for each slot inside. + * + * @default {} + */ + slots?: { + root?: React.ElementType; + listbox?: React.ElementType; + paper?: React.ElementType; + }; + /** + * The extra props for the slot components. + * You can override the existing props or add new ones. + * + * @default {} + */ + slotProps?: { + root?: SlotComponentProps; + listbox?: SlotComponentProps<'ul', {}, MenuOwnerState>; + paper?: SlotComponentProps; + }; + /** + * The system prop that allows defining system overrides as well as additional CSS styles. + */ + sx?: SxProps; + /** + * The length of the transition in `ms`, or 'auto' + * @default 'auto' + */ + transitionDuration?: TransitionProps['timeout'] | 'auto'; + /** + * Props applied to the transition element. + * By default, the element is based on this [`Transition`](http://reactcommunity.org/react-transition-group/transition/) component. + * @default {} + */ + TransitionProps?: TransitionProps; + /** + * The variant to use. Use `menu` to prevent selected items from impacting the initial focus. + * @default 'selectedMenu' + */ + variant?: 'menu' | 'selectedMenu'; + }; + defaultComponent: RootComponent; +} + +export type MenuProps< + RootComponent extends React.ElementType = MenuTypeMap['defaultComponent'], + AdditionalProps = {}, +> = OverrideProps, RootComponent> & { + component?: React.ElementType; +}; + +export interface MenuOwnerState extends Omit { + open: boolean; +}