From cabfa29b600a542b7a7bcfd48020fe7a7ef534d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Tue, 19 Nov 2024 14:39:44 +0100 Subject: [PATCH 01/11] [Dialog] Use internal backdrop element --- packages/mui-base/src/AlertDialog/Popup/AlertDialogPopup.tsx | 3 ++- packages/mui-base/src/Dialog/Popup/DialogPopup.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/mui-base/src/AlertDialog/Popup/AlertDialogPopup.tsx b/packages/mui-base/src/AlertDialog/Popup/AlertDialogPopup.tsx index f311895fe8..f98d69120d 100644 --- a/packages/mui-base/src/AlertDialog/Popup/AlertDialogPopup.tsx +++ b/packages/mui-base/src/AlertDialog/Popup/AlertDialogPopup.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; +import { FloatingFocusManager, FloatingOverlay, FloatingPortal } from '@floating-ui/react'; import { useDialogPopup } from '../../Dialog/Popup/useDialogPopup'; import { useAlertDialogRootContext } from '../Root/AlertDialogRootContext'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; @@ -115,6 +115,7 @@ const AlertDialogPopup = React.forwardRef(function AlertDialogPopup( return ( + {mounted && } + {modal && mounted && } Date: Wed, 20 Nov 2024 11:21:14 +0100 Subject: [PATCH 02/11] Simplify and unify backdrops --- .../Backdrop/AlertDialogBackdrop.tsx | 37 +++++----- .../src/Dialog/Backdrop/DialogBackdrop.tsx | 36 +++++----- .../src/Dialog/Backdrop/useDialogBackdrop.ts | 69 ------------------- .../src/Popover/Backdrop/PopoverBackdrop.tsx | 11 +-- .../Popover/Backdrop/usePopoverBackdrop.ts | 29 -------- .../Backdrop/PreviewCardBackdrop.tsx | 5 +- .../Backdrop/usePreviewCardBackdrop.ts | 29 -------- .../src/Select/Backdrop/SelectBackdrop.tsx | 6 +- .../src/Select/Backdrop/useSelectBackdrop.ts | 30 -------- .../mui-base/src/utils/useAnimatedElement.ts | 36 ---------- 10 files changed, 41 insertions(+), 247 deletions(-) delete mode 100644 packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts delete mode 100644 packages/mui-base/src/Popover/Backdrop/usePopoverBackdrop.ts delete mode 100644 packages/mui-base/src/PreviewCard/Backdrop/usePreviewCardBackdrop.ts delete mode 100644 packages/mui-base/src/Select/Backdrop/useSelectBackdrop.ts delete mode 100644 packages/mui-base/src/utils/useAnimatedElement.ts diff --git a/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx b/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx index 863ae02e9c..4169feda7e 100644 --- a/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx +++ b/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx @@ -3,7 +3,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { FloatingPortal } from '@floating-ui/react'; import { useAlertDialogRootContext } from '../Root/AlertDialogRootContext'; -import { useDialogBackdrop } from '../../Dialog/Backdrop/useDialogBackdrop'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; import type { TransitionStatus } from '../../utils/useTransitionStatus'; import type { BaseUIComponentProps } from '../../utils/types'; @@ -37,32 +36,29 @@ const AlertDialogBackdrop = React.forwardRef(function AlertDialogBackdrop( props: AlertDialogBackdrop.Props, forwardedRef: React.ForwardedRef, ) { - const { render, className, keepMounted = false, ...other } = props; - const { open, hasParentDialog, animated } = useAlertDialogRootContext(); + const { render, className, keepMounted = false, container, ...other } = props; + const { open, hasParentDialog, mounted, transitionStatus } = useAlertDialogRootContext(); - const { getRootProps, mounted, transitionStatus } = useDialogBackdrop({ - animated, - open, - ref: forwardedRef, - }); - - const ownerState: AlertDialogBackdrop.OwnerState = { open, transitionStatus }; + const ownerState: AlertDialogBackdrop.OwnerState = React.useMemo( + () => ({ + open, + transitionStatus, + }), + [open, transitionStatus], + ); const { renderElement } = useComponentRenderer({ render: render ?? 'div', className, ownerState, - propGetter: getRootProps, - extraProps: other, + ref: forwardedRef, + extraProps: { role: 'presentation', ...other }, customStyleHookMapping, }); - if (!mounted && !keepMounted) { - return null; - } - - if (hasParentDialog) { - // no need to render nested backdrops + // no need to render nested backdrops + const shouldRender = (keepMounted || mounted) && !hasParentDialog; + if (!shouldRender) { return null; } @@ -77,6 +73,11 @@ namespace AlertDialogBackdrop { * @default false */ keepMounted?: boolean; + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container?: HTMLElement | null | React.MutableRefObject; } export interface OwnerState { diff --git a/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx b/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx index 0ce6ed2c48..4627ae340b 100644 --- a/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx +++ b/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; import { FloatingPortal } from '@floating-ui/react'; -import { useDialogBackdrop } from './useDialogBackdrop'; import { useDialogRootContext } from '../Root/DialogRootContext'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; import { type TransitionStatus } from '../../utils/useTransitionStatus'; @@ -38,17 +37,14 @@ const DialogBackdrop = React.forwardRef(function DialogBackdrop( props: DialogBackdrop.Props, forwardedRef: React.ForwardedRef, ) { - const { render, className, keepMounted = false, ...other } = props; - const { open, hasParentDialog, animated } = useDialogRootContext(); - - const { getRootProps, mounted, transitionStatus } = useDialogBackdrop({ - animated, - open, - ref: forwardedRef, - }); + const { render, className, keepMounted = false, container, ...other } = props; + const { open, hasParentDialog, mounted, transitionStatus } = useDialogRootContext(); const ownerState: DialogBackdrop.OwnerState = React.useMemo( - () => ({ open, transitionStatus }), + () => ({ + open, + transitionStatus, + }), [open, transitionStatus], ); @@ -56,21 +52,18 @@ const DialogBackdrop = React.forwardRef(function DialogBackdrop( render: render ?? 'div', className, ownerState, - propGetter: getRootProps, - extraProps: other, + ref: forwardedRef, + extraProps: { role: 'presentation', ...other }, customStyleHookMapping, }); - if (!mounted && !keepMounted) { - return null; - } - - if (hasParentDialog) { - // no need to render nested backdrops + // no need to render nested backdrops + const shouldRender = (keepMounted || mounted) && !hasParentDialog; + if (!shouldRender) { return null; } - return {renderElement()}; + return {renderElement()}; }); namespace DialogBackdrop { @@ -81,6 +74,11 @@ namespace DialogBackdrop { * @default false */ keepMounted?: boolean; + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container?: HTMLElement | null | React.MutableRefObject; } export interface OwnerState { diff --git a/packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts b/packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts deleted file mode 100644 index b3b6bb2bdf..0000000000 --- a/packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts +++ /dev/null @@ -1,69 +0,0 @@ -'use client'; -import * as React from 'react'; -import { mergeReactProps } from '../../utils/mergeReactProps'; -import { useAnimatedElement } from '../../utils/useAnimatedElement'; -import { useForkRef } from '../../utils/useForkRef'; -import { type TransitionStatus } from '../../utils/useTransitionStatus'; - -export function useDialogBackdrop( - params: useDialogBackdrop.Parameters, -): useDialogBackdrop.ReturnValue { - const { animated, open, ref } = params; - - const backdropRef = React.useRef(null); - const handleRef = useForkRef(ref, backdropRef); - - const { mounted, transitionStatus } = useAnimatedElement({ - open, - ref: backdropRef, - enabled: animated, - }); - - const getRootProps = React.useCallback( - (externalProps: React.ComponentPropsWithRef) => - mergeReactProps(externalProps, { - role: 'presentation', - ref: handleRef, - }), - [handleRef], - ); - - return { - getRootProps, - mounted, - transitionStatus, - }; -} - -export namespace useDialogBackdrop { - export interface Parameters { - /** - * If `true`, the dialog supports CSS-based animations and transitions. - * It is kept in the DOM until the animation completes. - */ - animated: boolean; - /** - * Determines if the dialog is open. - */ - open: boolean; - /** - * The ref to the background element. - */ - ref: React.Ref; - } - - export interface ReturnValue { - /** - * Resolver for the root element props. - */ - getRootProps: (externalProps?: Record) => Record; - /** - * Determines if the dialog should be mounted even if closed (as the exit animation is still in progress). - */ - mounted: boolean; - /** - * The current transition status of the dialog. - */ - transitionStatus: TransitionStatus; - } -} diff --git a/packages/mui-base/src/Popover/Backdrop/PopoverBackdrop.tsx b/packages/mui-base/src/Popover/Backdrop/PopoverBackdrop.tsx index 429d6be4f5..0f6c2ccf79 100644 --- a/packages/mui-base/src/Popover/Backdrop/PopoverBackdrop.tsx +++ b/packages/mui-base/src/Popover/Backdrop/PopoverBackdrop.tsx @@ -5,7 +5,6 @@ import { FloatingPortal } from '@floating-ui/react'; import { usePopoverRootContext } from '../Root/PopoverRootContext'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; import { HTMLElementType } from '../../utils/proptypes'; -import { usePopoverBackdrop } from './usePopoverBackdrop'; import type { BaseUIComponentProps } from '../../utils/types'; import { type CustomStyleHookMapping } from '../../utils/getStyleHookProps'; import { popupOpenStateMapping as baseMapping } from '../../utils/popupOpenStateMapping'; @@ -39,13 +38,10 @@ const PopoverBackdrop = React.forwardRef(function PopoverBackdrop( props: PopoverBackdrop.Props, forwardedRef: React.ForwardedRef, ) { - const { className, render, keepMounted = false, container, ...otherProps } = props; - + const { className, render, keepMounted = false, container, ...other } = props; const { open, mounted, transitionStatus } = usePopoverRootContext(); - const { getBackdropProps } = usePopoverBackdrop(); - - const ownerState = React.useMemo( + const ownerState: PopoverBackdrop.OwnerState = React.useMemo( () => ({ open, transitionStatus, @@ -54,12 +50,11 @@ const PopoverBackdrop = React.forwardRef(function PopoverBackdrop( ); const { renderElement } = useComponentRenderer({ - propGetter: getBackdropProps, render: render ?? 'div', className, ownerState, ref: forwardedRef, - extraProps: otherProps, + extraProps: { role: 'presentation', ...other }, customStyleHookMapping, }); diff --git a/packages/mui-base/src/Popover/Backdrop/usePopoverBackdrop.ts b/packages/mui-base/src/Popover/Backdrop/usePopoverBackdrop.ts deleted file mode 100644 index e8cbfebd1b..0000000000 --- a/packages/mui-base/src/Popover/Backdrop/usePopoverBackdrop.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { mergeReactProps } from '../../utils/mergeReactProps'; -import type { GenericHTMLProps } from '../../utils/types'; - -export function usePopoverBackdrop(): usePopoverBackdrop.ReturnValue { - const getBackdropProps = React.useCallback((externalProps = {}) => { - return mergeReactProps<'div'>(externalProps, { - role: 'presentation', - style: { - overflow: 'auto', - position: 'fixed', - inset: 0, - }, - }); - }, []); - - return React.useMemo( - () => ({ - getBackdropProps, - }), - [getBackdropProps], - ); -} - -namespace usePopoverBackdrop { - export interface ReturnValue { - getBackdropProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps; - } -} diff --git a/packages/mui-base/src/PreviewCard/Backdrop/PreviewCardBackdrop.tsx b/packages/mui-base/src/PreviewCard/Backdrop/PreviewCardBackdrop.tsx index fea035ea55..0307006fa5 100644 --- a/packages/mui-base/src/PreviewCard/Backdrop/PreviewCardBackdrop.tsx +++ b/packages/mui-base/src/PreviewCard/Backdrop/PreviewCardBackdrop.tsx @@ -4,7 +4,6 @@ import PropTypes from 'prop-types'; import { FloatingPortal } from '@floating-ui/react'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; import { usePreviewCardRootContext } from '../Root/PreviewCardContext'; -import { usePreviewCardBackdrop } from './usePreviewCardBackdrop'; import { HTMLElementType } from '../../utils/proptypes'; import type { BaseUIComponentProps } from '../../utils/types'; import { type CustomStyleHookMapping } from '../../utils/getStyleHookProps'; @@ -41,7 +40,6 @@ const PreviewCardBackdrop = React.forwardRef(function PreviewCardBackdrop( const { render, className, keepMounted = false, container, ...otherProps } = props; const { open, mounted, transitionStatus } = usePreviewCardRootContext(); - const { getBackdropProps } = usePreviewCardBackdrop(); const ownerState: PreviewCardBackdrop.OwnerState = React.useMemo( () => ({ @@ -52,12 +50,11 @@ const PreviewCardBackdrop = React.forwardRef(function PreviewCardBackdrop( ); const { renderElement } = useComponentRenderer({ - propGetter: getBackdropProps, render: render ?? 'div', className, ownerState, ref: forwardedRef, - extraProps: otherProps, + extraProps: { role: 'presentation', ...otherProps }, customStyleHookMapping, }); diff --git a/packages/mui-base/src/PreviewCard/Backdrop/usePreviewCardBackdrop.ts b/packages/mui-base/src/PreviewCard/Backdrop/usePreviewCardBackdrop.ts deleted file mode 100644 index c779055338..0000000000 --- a/packages/mui-base/src/PreviewCard/Backdrop/usePreviewCardBackdrop.ts +++ /dev/null @@ -1,29 +0,0 @@ -import * as React from 'react'; -import { mergeReactProps } from '../../utils/mergeReactProps'; -import type { GenericHTMLProps } from '../../utils/types'; - -export function usePreviewCardBackdrop(): usePreviewCardBackdrop.ReturnValue { - const getBackdropProps = React.useCallback((externalProps = {}) => { - return mergeReactProps<'div'>(externalProps, { - style: { - overflow: 'auto', - position: 'fixed', - inset: 0, - pointerEvents: 'none', - }, - }); - }, []); - - return React.useMemo( - () => ({ - getBackdropProps, - }), - [getBackdropProps], - ); -} - -namespace usePreviewCardBackdrop { - export interface ReturnValue { - getBackdropProps: (externalProps?: GenericHTMLProps) => GenericHTMLProps; - } -} diff --git a/packages/mui-base/src/Select/Backdrop/SelectBackdrop.tsx b/packages/mui-base/src/Select/Backdrop/SelectBackdrop.tsx index 049583aae3..d9adc9d9e1 100644 --- a/packages/mui-base/src/Select/Backdrop/SelectBackdrop.tsx +++ b/packages/mui-base/src/Select/Backdrop/SelectBackdrop.tsx @@ -6,7 +6,6 @@ import type { BaseUIComponentProps } from '../../utils/types'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; import { HTMLElementType } from '../../utils/proptypes'; import { useSelectRootContext } from '../Root/SelectRootContext'; -import { useSelectBackdrop } from './useSelectBackdrop'; import { popupOpenStateMapping } from '../../utils/popupOpenStateMapping'; import type { CustomStyleHookMapping } from '../../utils/getStyleHookProps'; import type { TransitionStatus } from '../../utils/useTransitionStatus'; @@ -44,20 +43,17 @@ const SelectBackdrop = React.forwardRef(function SelectBackdrop( const { open, mounted, transitionStatus } = useSelectRootContext(); - const { getBackdropProps } = useSelectBackdrop(); - const ownerState: SelectBackdrop.OwnerState = React.useMemo( () => ({ open, transitionStatus }), [open, transitionStatus], ); const { renderElement } = useComponentRenderer({ - propGetter: getBackdropProps, render: render ?? 'div', className, ownerState, ref: forwardedRef, - extraProps: otherProps, + extraProps: { role: 'presentation', ...otherProps }, customStyleHookMapping, }); diff --git a/packages/mui-base/src/Select/Backdrop/useSelectBackdrop.ts b/packages/mui-base/src/Select/Backdrop/useSelectBackdrop.ts deleted file mode 100644 index a4dcb26993..0000000000 --- a/packages/mui-base/src/Select/Backdrop/useSelectBackdrop.ts +++ /dev/null @@ -1,30 +0,0 @@ -'use client'; -import * as React from 'react'; -import { mergeReactProps } from '../../utils/mergeReactProps'; - -/** - * - * API: - * - * - [useSelectBackdrop API](https://mui.com/base-ui/api/use-select-backdrop/) - */ -export function useSelectBackdrop() { - const getBackdropProps = React.useCallback((externalProps = {}) => { - return mergeReactProps<'div'>(externalProps, { - role: 'presentation', - style: { - overflow: 'auto', - position: 'fixed', - inset: 0, - pointerEvents: 'none', - }, - }); - }, []); - - return React.useMemo( - () => ({ - getBackdropProps, - }), - [getBackdropProps], - ); -} diff --git a/packages/mui-base/src/utils/useAnimatedElement.ts b/packages/mui-base/src/utils/useAnimatedElement.ts deleted file mode 100644 index 3a947023b9..0000000000 --- a/packages/mui-base/src/utils/useAnimatedElement.ts +++ /dev/null @@ -1,36 +0,0 @@ -import * as React from 'react'; -import { useAnimationsFinished } from './useAnimationsFinished'; -import { useTransitionStatus } from './useTransitionStatus'; - -interface UseAnimatedElementParameters { - open: boolean; - ref: React.RefObject; - enabled: boolean; -} - -/** - * @ignore - internal hook. - */ -export function useAnimatedElement(params: UseAnimatedElementParameters) { - const { open, ref, enabled } = params; - - const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, enabled); - const runOnceAnimationsFinish = useAnimationsFinished(ref); - - React.useEffect(() => { - if (!open) { - if (enabled) { - runOnceAnimationsFinish(() => { - setMounted(false); - }); - } else { - setMounted(false); - } - } - }, [enabled, open, runOnceAnimationsFinish, setMounted]); - - return { - mounted, - transitionStatus, - }; -} From 432d9313fc3983208e2ff0158b511fa97139143a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Wed, 20 Nov 2024 11:31:42 +0100 Subject: [PATCH 03/11] docs --- docs/data/api/alert-dialog-backdrop.json | 7 +++++ docs/data/api/dialog-backdrop.json | 7 +++++ .../alert-dialog-backdrop.json | 1 + .../dialog-backdrop/dialog-backdrop.json | 1 + .../generated/alert-dialog-backdrop.json | 5 ++++ docs/reference/generated/dialog-backdrop.json | 5 ++++ .../Backdrop/AlertDialogBackdrop.tsx | 26 +++++++++++++++++++ .../src/Dialog/Backdrop/DialogBackdrop.tsx | 26 +++++++++++++++++++ 8 files changed, 78 insertions(+) diff --git a/docs/data/api/alert-dialog-backdrop.json b/docs/data/api/alert-dialog-backdrop.json index 4a4cadeae5..6d3a943a8c 100644 --- a/docs/data/api/alert-dialog-backdrop.json +++ b/docs/data/api/alert-dialog-backdrop.json @@ -1,6 +1,13 @@ { "props": { "className": { "type": { "name": "union", "description": "func
| string" } }, + "container": { + "type": { + "name": "union", + "description": "(props, propName) => {\n if (props[propName] == null) {\n return new Error(`Prop '${propName}' is required but wasn't specified`);\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n}
| { current?: (props, propName) => {\n if (props[propName] == null) {\n return null;\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n} }" + }, + "default": "false" + }, "keepMounted": { "type": { "name": "bool" }, "default": "false" }, "render": { "type": { "name": "union", "description": "element
| func" } } }, diff --git a/docs/data/api/dialog-backdrop.json b/docs/data/api/dialog-backdrop.json index 9a48a406a5..03c0312d40 100644 --- a/docs/data/api/dialog-backdrop.json +++ b/docs/data/api/dialog-backdrop.json @@ -1,6 +1,13 @@ { "props": { "className": { "type": { "name": "union", "description": "func
| string" } }, + "container": { + "type": { + "name": "union", + "description": "(props, propName) => {\n if (props[propName] == null) {\n return new Error(`Prop '${propName}' is required but wasn't specified`);\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n}
| { current?: (props, propName) => {\n if (props[propName] == null) {\n return null;\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n} }" + }, + "default": "false" + }, "keepMounted": { "type": { "name": "bool" }, "default": "false" }, "render": { "type": { "name": "union", "description": "element
| func" } } }, diff --git a/docs/data/translations/api-docs/alert-dialog-backdrop/alert-dialog-backdrop.json b/docs/data/translations/api-docs/alert-dialog-backdrop/alert-dialog-backdrop.json index 0b0d36e82d..1c6cf7319a 100644 --- a/docs/data/translations/api-docs/alert-dialog-backdrop/alert-dialog-backdrop.json +++ b/docs/data/translations/api-docs/alert-dialog-backdrop/alert-dialog-backdrop.json @@ -4,6 +4,7 @@ "className": { "description": "Class names applied to the element or a function that returns them based on the component's state." }, + "container": { "description": "The container element to which the backdrop is appended to." }, "keepMounted": { "description": "If true, the backdrop element is kept in the DOM when closed." }, diff --git a/docs/data/translations/api-docs/dialog-backdrop/dialog-backdrop.json b/docs/data/translations/api-docs/dialog-backdrop/dialog-backdrop.json index 0bbcc578ea..cc9aeff3d4 100644 --- a/docs/data/translations/api-docs/dialog-backdrop/dialog-backdrop.json +++ b/docs/data/translations/api-docs/dialog-backdrop/dialog-backdrop.json @@ -4,6 +4,7 @@ "className": { "description": "Class names applied to the element or a function that returns them based on the component's state." }, + "container": { "description": "The container element to which the backdrop is appended to." }, "keepMounted": { "description": "If true, the backdrop element is kept in the DOM when closed." }, diff --git a/docs/reference/generated/alert-dialog-backdrop.json b/docs/reference/generated/alert-dialog-backdrop.json index a09e5f6699..a179969a3d 100644 --- a/docs/reference/generated/alert-dialog-backdrop.json +++ b/docs/reference/generated/alert-dialog-backdrop.json @@ -6,6 +6,11 @@ "type": "string | (state) => string", "description": "Class names applied to the element or a function that returns them based on the component's state." }, + "container": { + "type": "React.Ref | HTMLElement | null", + "default": "false", + "description": "The container element to which the backdrop is appended to." + }, "keepMounted": { "type": "boolean", "default": "false", diff --git a/docs/reference/generated/dialog-backdrop.json b/docs/reference/generated/dialog-backdrop.json index 7ac90105e9..16b6e2ba8f 100644 --- a/docs/reference/generated/dialog-backdrop.json +++ b/docs/reference/generated/dialog-backdrop.json @@ -6,6 +6,11 @@ "type": "string | (state) => string", "description": "Class names applied to the element or a function that returns them based on the component's state." }, + "container": { + "type": "React.Ref | HTMLElement | null", + "default": "false", + "description": "The container element to which the backdrop is appended to." + }, "keepMounted": { "type": "boolean", "default": "false", diff --git a/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx b/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx index 4169feda7e..783dcc8357 100644 --- a/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx +++ b/packages/mui-base/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx @@ -99,6 +99,32 @@ AlertDialogBackdrop.propTypes /* remove-proptypes */ = { * Class names applied to the element or a function that returns them based on the component's state. */ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container: PropTypes.oneOfType([ + (props, propName) => { + if (props[propName] == null) { + return new Error(`Prop '${propName}' is required but wasn't specified`); + } + if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { + return new Error(`Expected prop '${propName}' to be of type Element`); + } + return null; + }, + PropTypes.shape({ + current: (props, propName) => { + if (props[propName] == null) { + return null; + } + if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { + return new Error(`Expected prop '${propName}' to be of type Element`); + } + return null; + }, + }), + ]), /** * If `true`, the backdrop element is kept in the DOM when closed. * diff --git a/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx b/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx index 4627ae340b..30b40bdf96 100644 --- a/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx +++ b/packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx @@ -100,6 +100,32 @@ DialogBackdrop.propTypes /* remove-proptypes */ = { * Class names applied to the element or a function that returns them based on the component's state. */ className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container: PropTypes.oneOfType([ + (props, propName) => { + if (props[propName] == null) { + return new Error(`Prop '${propName}' is required but wasn't specified`); + } + if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { + return new Error(`Expected prop '${propName}' to be of type Element`); + } + return null; + }, + PropTypes.shape({ + current: (props, propName) => { + if (props[propName] == null) { + return null; + } + if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { + return new Error(`Expected prop '${propName}' to be of type Element`); + } + return null; + }, + }), + ]), /** * If `true`, the backdrop element is kept in the DOM when closed. * From c770422098c76cbf9be36622a1772bd0c3c7e941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Thu, 21 Nov 2024 11:23:33 +0100 Subject: [PATCH 04/11] Simplify proptypes --- docs/data/api/alert-dialog-backdrop.json | 5 +--- docs/data/api/dialog-backdrop.json | 5 +--- .../Backdrop/AlertDialogBackdrop.tsx | 25 +++---------------- .../src/Dialog/Backdrop/DialogBackdrop.tsx | 25 +++---------------- scripts/generateProptypes.ts | 2 -- 5 files changed, 10 insertions(+), 52 deletions(-) diff --git a/docs/data/api/alert-dialog-backdrop.json b/docs/data/api/alert-dialog-backdrop.json index 2648f3cffa..bb233267a8 100644 --- a/docs/data/api/alert-dialog-backdrop.json +++ b/docs/data/api/alert-dialog-backdrop.json @@ -2,10 +2,7 @@ "props": { "className": { "type": { "name": "union", "description": "func
| string" } }, "container": { - "type": { - "name": "union", - "description": "(props, propName) => {\n if (props[propName] == null) {\n return new Error(`Prop '${propName}' is required but wasn't specified`);\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n}
| { current?: (props, propName) => {\n if (props[propName] == null) {\n return null;\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n} }" - }, + "type": { "name": "union", "description": "HTML element
| func" }, "default": "false" }, "keepMounted": { "type": { "name": "bool" }, "default": "false" }, diff --git a/docs/data/api/dialog-backdrop.json b/docs/data/api/dialog-backdrop.json index 764cbc09ab..e52363dd11 100644 --- a/docs/data/api/dialog-backdrop.json +++ b/docs/data/api/dialog-backdrop.json @@ -2,10 +2,7 @@ "props": { "className": { "type": { "name": "union", "description": "func
| string" } }, "container": { - "type": { - "name": "union", - "description": "(props, propName) => {\n if (props[propName] == null) {\n return new Error(`Prop '${propName}' is required but wasn't specified`);\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n}
| { current?: (props, propName) => {\n if (props[propName] == null) {\n return null;\n }\n if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(`Expected prop '${propName}' to be of type Element`);\n }\n return null;\n} }" - }, + "type": { "name": "union", "description": "HTML element
| func" }, "default": "false" }, "keepMounted": { "type": { "name": "bool" }, "default": "false" }, diff --git a/packages/react/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx b/packages/react/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx index 6c3f3db92d..3deb2071c5 100644 --- a/packages/react/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx +++ b/packages/react/src/AlertDialog/Backdrop/AlertDialogBackdrop.tsx @@ -8,6 +8,7 @@ import type { TransitionStatus } from '../../utils/useTransitionStatus'; import type { BaseUIComponentProps } from '../../utils/types'; import type { CustomStyleHookMapping } from '../../utils/getStyleHookProps'; import { popupOpenStateMapping as baseMapping } from '../../utils/popupOpenStateMapping'; +import { HTMLElementType } from '../../utils/proptypes'; const customStyleHookMapping: CustomStyleHookMapping = { ...baseMapping, @@ -103,27 +104,9 @@ AlertDialogBackdrop.propTypes /* remove-proptypes */ = { * The container element to which the backdrop is appended to. * @default false */ - container: PropTypes.oneOfType([ - (props, propName) => { - if (props[propName] == null) { - return new Error(`Prop '${propName}' is required but wasn't specified`); - } - if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { - return new Error(`Expected prop '${propName}' to be of type Element`); - } - return null; - }, - PropTypes.shape({ - current: (props, propName) => { - if (props[propName] == null) { - return null; - } - if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { - return new Error(`Expected prop '${propName}' to be of type Element`); - } - return null; - }, - }), + container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.func, ]), /** * If `true`, the backdrop element is kept in the DOM when closed. diff --git a/packages/react/src/Dialog/Backdrop/DialogBackdrop.tsx b/packages/react/src/Dialog/Backdrop/DialogBackdrop.tsx index 83175f404f..9238692dad 100644 --- a/packages/react/src/Dialog/Backdrop/DialogBackdrop.tsx +++ b/packages/react/src/Dialog/Backdrop/DialogBackdrop.tsx @@ -8,6 +8,7 @@ import { type TransitionStatus } from '../../utils/useTransitionStatus'; import { type BaseUIComponentProps } from '../../utils/types'; import { type CustomStyleHookMapping } from '../../utils/getStyleHookProps'; import { popupOpenStateMapping as baseMapping } from '../../utils/popupOpenStateMapping'; +import { HTMLElementType } from '../../utils/proptypes'; const customStyleHookMapping: CustomStyleHookMapping = { ...baseMapping, @@ -104,27 +105,9 @@ DialogBackdrop.propTypes /* remove-proptypes */ = { * The container element to which the backdrop is appended to. * @default false */ - container: PropTypes.oneOfType([ - (props, propName) => { - if (props[propName] == null) { - return new Error(`Prop '${propName}' is required but wasn't specified`); - } - if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { - return new Error(`Expected prop '${propName}' to be of type Element`); - } - return null; - }, - PropTypes.shape({ - current: (props, propName) => { - if (props[propName] == null) { - return null; - } - if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) { - return new Error(`Expected prop '${propName}' to be of type Element`); - } - return null; - }, - }), + container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.func, ]), /** * If `true`, the backdrop element is kept in the DOM when closed. diff --git a/scripts/generateProptypes.ts b/scripts/generateProptypes.ts index d0da8fa57e..7df212fdef 100644 --- a/scripts/generateProptypes.ts +++ b/scripts/generateProptypes.ts @@ -51,8 +51,6 @@ const getSortLiteralUnions: InjectPropTypesInFileOptions['getSortLiteralUnions'] }; async function generateProptypes(project: TypeScriptProject, sourceFile: string): Promise { - console.log('generating proptypes', { project, sourceFile }); - const components = getPropTypesFromFile({ filePath: sourceFile, project, From 97d68fb487919fc60a15b1746a08dd6e273baddd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Fri, 22 Nov 2024 16:27:39 +0100 Subject: [PATCH 05/11] Add MenuBackdrop --- docs/data/api/menu-backdrop.json | 24 ++++ docs/data/components/menu/menu.mdx | 5 +- .../api-docs/menu-backdrop/menu-backdrop.json | 14 ++ docs/reference/generated/menu-backdrop.json | 24 ++++ .../src/Menu/Backdrop/MenuBackdrop.test.tsx | 18 +++ .../react/src/Menu/Backdrop/MenuBackdrop.tsx | 121 ++++++++++++++++++ packages/react/src/Menu/index.parts.ts | 1 + 7 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 docs/data/api/menu-backdrop.json create mode 100644 docs/data/translations/api-docs/menu-backdrop/menu-backdrop.json create mode 100644 docs/reference/generated/menu-backdrop.json create mode 100644 packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx create mode 100644 packages/react/src/Menu/Backdrop/MenuBackdrop.tsx diff --git a/docs/data/api/menu-backdrop.json b/docs/data/api/menu-backdrop.json new file mode 100644 index 0000000000..30a44dbe86 --- /dev/null +++ b/docs/data/api/menu-backdrop.json @@ -0,0 +1,24 @@ +{ + "props": { + "className": { "type": { "name": "union", "description": "func
| string" } }, + "container": { + "type": { "name": "union", "description": "HTML element
| func" }, + "default": "false" + }, + "keepMounted": { "type": { "name": "bool" }, "default": "false" }, + "render": { "type": { "name": "union", "description": "element
| func" } } + }, + "name": "MenuBackdrop", + "imports": [ + "import { Menu } from '@base-ui-components/react/Menu';\nconst MenuBackdrop = Menu.Backdrop;" + ], + "classes": [], + "spread": true, + "themeDefaultProps": true, + "muiName": "MenuBackdrop", + "forwardsRefTo": "HTMLDivElement", + "filename": "/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx", + "inheritance": null, + "demos": "", + "cssComponent": false +} diff --git a/docs/data/components/menu/menu.mdx b/docs/data/components/menu/menu.mdx index a489187043..97f0ce01ad 100644 --- a/docs/data/components/menu/menu.mdx +++ b/docs/data/components/menu/menu.mdx @@ -2,7 +2,7 @@ productId: base-ui title: React Menu component description: The Menu component provide end users with a list of options on temporary surfaces. -components: MenuItem, MenuPositioner, MenuPopup, MenuRoot, MenuTrigger, SubmenuTrigger, MenuArrow, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuCheckboxItem, MenuCheckboxItemIndicator, MenuGroup, MenuGroupLabel +components: MenuItem, MenuPositioner, MenuPopup, MenuRoot, MenuTrigger, SubmenuTrigger, MenuArrow, MenuRadioGroup, MenuRadioItem, MenuRadioItemIndicator, MenuCheckboxItem, MenuCheckboxItemIndicator, MenuGroup, MenuGroupLabel, MenuBackdrop githubLabel: 'component: menu' waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/ --- @@ -25,6 +25,7 @@ Menus are implemented using a collection of related components: - `` is a top-level component that facilitates communication between other components. It does not render to the DOM. - `` is an optional component (a button by default) that, when clicked, shows the menu. When not used, menu can be shown programmatically using the `open` prop. +- `` renders an optional backdrop element behind the menu popup. - `` renders the element responsible for positioning the popup. - `` is the menu popup. - `` is the menu item. @@ -43,6 +44,8 @@ Menus are implemented using a collection of related components: + + diff --git a/docs/data/translations/api-docs/menu-backdrop/menu-backdrop.json b/docs/data/translations/api-docs/menu-backdrop/menu-backdrop.json new file mode 100644 index 0000000000..787c69424b --- /dev/null +++ b/docs/data/translations/api-docs/menu-backdrop/menu-backdrop.json @@ -0,0 +1,14 @@ +{ + "componentDescription": "Renders a backdrop for the menu.", + "propDescriptions": { + "className": { + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "container": { "description": "The container element to which the backdrop is appended to." }, + "keepMounted": { + "description": "If true, the backdrop remains mounted when the menu popup is closed." + }, + "render": { "description": "A function to customize rendering of the component." } + }, + "classDescriptions": {} +} diff --git a/docs/reference/generated/menu-backdrop.json b/docs/reference/generated/menu-backdrop.json new file mode 100644 index 0000000000..46418b4c42 --- /dev/null +++ b/docs/reference/generated/menu-backdrop.json @@ -0,0 +1,24 @@ +{ + "name": "MenuBackdrop", + "description": "Renders a backdrop for the menu.", + "props": { + "className": { + "type": "string | (state) => string", + "description": "Class names applied to the element or a function that returns them based on the component's state." + }, + "container": { + "type": "React.Ref | HTMLElement | null", + "default": "false", + "description": "The container element to which the backdrop is appended to." + }, + "keepMounted": { + "type": "boolean", + "default": "false", + "description": "If `true`, the backdrop remains mounted when the menu popup is closed." + }, + "render": { + "type": "React.ReactElement | (props, state) => React.ReactElement", + "description": "A function to customize rendering of the component." + } + } +} diff --git a/packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx b/packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx new file mode 100644 index 0000000000..65b5ea55dc --- /dev/null +++ b/packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { Menu } from '@base-ui-components/react/Menu'; +import { createRenderer, describeConformance } from '#test-utils'; + +describe('', () => { + const { render } = createRenderer(); + + describeConformance(, () => ({ + refInstanceof: window.HTMLDivElement, + render(node) { + return render( + + {node} + , + ); + }, + })); +}); diff --git a/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx b/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx new file mode 100644 index 0000000000..708ef07ffc --- /dev/null +++ b/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx @@ -0,0 +1,121 @@ +'use client'; +import * as React from 'react'; +import PropTypes from 'prop-types'; +import { FloatingPortal } from '@floating-ui/react'; +import { useMenuRootContext } from '../Root/MenuRootContext'; +import { useComponentRenderer } from '../../utils/useComponentRenderer'; +import { HTMLElementType } from '../../utils/proptypes'; +import type { BaseUIComponentProps } from '../../utils/types'; +import { type CustomStyleHookMapping } from '../../utils/getStyleHookProps'; +import { popupOpenStateMapping as baseMapping } from '../../utils/popupOpenStateMapping'; +import type { TransitionStatus } from '../../utils/useTransitionStatus'; + +const customStyleHookMapping: CustomStyleHookMapping = { + ...baseMapping, + transitionStatus(value) { + if (value === 'entering') { + return { 'data-entering': '' } as Record; + } + if (value === 'exiting') { + return { 'data-exiting': '' }; + } + return null; + }, +}; + +/** + * Renders a backdrop for the menu. + * + * Demos: + * + * - [Menu](https://base-ui.com/components/react-menu/) + * + * API: + * + * - [MenuBackdrop API](https://base-ui.com/components/react-menu/#api-reference-MenuBackdrop) + */ +const MenuBackdrop = React.forwardRef(function MenuBackdrop( + props: MenuBackdrop.Props, + forwardedRef: React.ForwardedRef, +) { + const { className, render, keepMounted = false, container, ...other } = props; + const { open, mounted, transitionStatus } = useMenuRootContext(); + + const ownerState: MenuBackdrop.OwnerState = React.useMemo( + () => ({ + open, + transitionStatus, + }), + [open, transitionStatus], + ); + + const { renderElement } = useComponentRenderer({ + render: render ?? 'div', + className, + ownerState, + ref: forwardedRef, + extraProps: { role: 'presentation', hidden: !mounted, ...other }, + customStyleHookMapping, + }); + + const shouldRender = keepMounted || mounted; + if (!shouldRender) { + return null; + } + + return {renderElement()}; +}); + +namespace MenuBackdrop { + export interface OwnerState { + open: boolean; + transitionStatus: TransitionStatus; + } + + export interface Props extends BaseUIComponentProps<'div', OwnerState> { + /** + * If `true`, the backdrop remains mounted when the menu popup is closed. + * @default false + */ + keepMounted?: boolean; + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container?: HTMLElement | null | React.MutableRefObject; + } +} + +MenuBackdrop.propTypes /* remove-proptypes */ = { + // ┌────────────────────────────── Warning ──────────────────────────────┐ + // │ These PropTypes are generated from the TypeScript type definitions. │ + // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │ + // └─────────────────────────────────────────────────────────────────────┘ + /** + * @ignore + */ + children: PropTypes.node, + /** + * Class names applied to the element or a function that returns them based on the component's state. + */ + className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), + /** + * The container element to which the backdrop is appended to. + * @default false + */ + container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([ + HTMLElementType, + PropTypes.func, + ]), + /** + * If `true`, the backdrop remains mounted when the menu popup is closed. + * @default false + */ + keepMounted: PropTypes.bool, + /** + * A function to customize rendering of the component. + */ + render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]), +} as any; + +export { MenuBackdrop }; diff --git a/packages/react/src/Menu/index.parts.ts b/packages/react/src/Menu/index.parts.ts index ac63a706d5..5bc367da55 100644 --- a/packages/react/src/Menu/index.parts.ts +++ b/packages/react/src/Menu/index.parts.ts @@ -1,4 +1,5 @@ export { MenuArrow as Arrow } from './Arrow/MenuArrow'; +export { MenuBackdrop as Backdrop } from './Backdrop/MenuBackdrop'; export { MenuCheckboxItem as CheckboxItem } from './CheckboxItem/MenuCheckboxItem'; export { MenuCheckboxItemIndicator as CheckboxItemIndicator } from './CheckboxItemIndicator/MenuCheckboxItemIndicator'; export { MenuGroup as Group } from './Group/MenuGroup'; From 008f10e1b69a65c68a1da1098cc1624b8c505977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Wed, 27 Nov 2024 12:33:58 +0100 Subject: [PATCH 06/11] Change directory to lowercase --- docs/data/api/menu-backdrop.json | 4 ++-- .../src/Menu/{Backdrop => backdrop}/MenuBackdrop.test.tsx | 0 .../react/src/Menu/{Backdrop => backdrop}/MenuBackdrop.tsx | 0 3 files changed, 2 insertions(+), 2 deletions(-) rename packages/react/src/Menu/{Backdrop => backdrop}/MenuBackdrop.test.tsx (100%) rename packages/react/src/Menu/{Backdrop => backdrop}/MenuBackdrop.tsx (100%) diff --git a/docs/data/api/menu-backdrop.json b/docs/data/api/menu-backdrop.json index 30a44dbe86..9aa9961a99 100644 --- a/docs/data/api/menu-backdrop.json +++ b/docs/data/api/menu-backdrop.json @@ -10,14 +10,14 @@ }, "name": "MenuBackdrop", "imports": [ - "import { Menu } from '@base-ui-components/react/Menu';\nconst MenuBackdrop = Menu.Backdrop;" + "import { Menu } from '@base-ui-components/react/menu';\nconst MenuBackdrop = Menu.Backdrop;" ], "classes": [], "spread": true, "themeDefaultProps": true, "muiName": "MenuBackdrop", "forwardsRefTo": "HTMLDivElement", - "filename": "/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx", + "filename": "/packages/react/src/menu/backdrop/MenuBackdrop.tsx", "inheritance": null, "demos": "", "cssComponent": false diff --git a/packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx b/packages/react/src/Menu/backdrop/MenuBackdrop.test.tsx similarity index 100% rename from packages/react/src/Menu/Backdrop/MenuBackdrop.test.tsx rename to packages/react/src/Menu/backdrop/MenuBackdrop.test.tsx diff --git a/packages/react/src/Menu/Backdrop/MenuBackdrop.tsx b/packages/react/src/Menu/backdrop/MenuBackdrop.tsx similarity index 100% rename from packages/react/src/Menu/Backdrop/MenuBackdrop.tsx rename to packages/react/src/Menu/backdrop/MenuBackdrop.tsx From 7f6be6a827b358829265fef5519b12c40274b7a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Wed, 27 Nov 2024 12:41:36 +0100 Subject: [PATCH 07/11] Fix casing mess --- packages/react/src/{Menu => menu}/backdrop/MenuBackdrop.test.tsx | 0 packages/react/src/{Menu => menu}/backdrop/MenuBackdrop.tsx | 0 packages/react/src/{Menu => menu}/index.parts.ts | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename packages/react/src/{Menu => menu}/backdrop/MenuBackdrop.test.tsx (100%) rename packages/react/src/{Menu => menu}/backdrop/MenuBackdrop.tsx (100%) rename packages/react/src/{Menu => menu}/index.parts.ts (100%) diff --git a/packages/react/src/Menu/backdrop/MenuBackdrop.test.tsx b/packages/react/src/menu/backdrop/MenuBackdrop.test.tsx similarity index 100% rename from packages/react/src/Menu/backdrop/MenuBackdrop.test.tsx rename to packages/react/src/menu/backdrop/MenuBackdrop.test.tsx diff --git a/packages/react/src/Menu/backdrop/MenuBackdrop.tsx b/packages/react/src/menu/backdrop/MenuBackdrop.tsx similarity index 100% rename from packages/react/src/Menu/backdrop/MenuBackdrop.tsx rename to packages/react/src/menu/backdrop/MenuBackdrop.tsx diff --git a/packages/react/src/Menu/index.parts.ts b/packages/react/src/menu/index.parts.ts similarity index 100% rename from packages/react/src/Menu/index.parts.ts rename to packages/react/src/menu/index.parts.ts From 16c521a1ce671160afe6f5fbf30a0333b029904c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Thu, 5 Dec 2024 22:44:03 +0700 Subject: [PATCH 08/11] Use `outsideElementsInert` from FloatingFocusManager --- packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx | 4 ++-- packages/react/src/dialog/popup/DialogPopup.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx b/packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx index 18534d87e8..9367b24c37 100644 --- a/packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx +++ b/packages/react/src/alert-dialog/popup/AlertDialogPopup.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { FloatingFocusManager, FloatingOverlay, FloatingPortal } from '@floating-ui/react'; +import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; import { useDialogPopup } from '../../dialog/popup/useDialogPopup'; import { useAlertDialogRootContext } from '../root/AlertDialogRootContext'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; @@ -115,13 +115,13 @@ const AlertDialogPopup = React.forwardRef(function AlertDialogPopup( return ( - {mounted && } {renderElement()} diff --git a/packages/react/src/dialog/popup/DialogPopup.tsx b/packages/react/src/dialog/popup/DialogPopup.tsx index f7cedd7169..a38db9c997 100644 --- a/packages/react/src/dialog/popup/DialogPopup.tsx +++ b/packages/react/src/dialog/popup/DialogPopup.tsx @@ -1,7 +1,7 @@ 'use client'; import * as React from 'react'; import PropTypes from 'prop-types'; -import { FloatingFocusManager, FloatingOverlay, FloatingPortal } from '@floating-ui/react'; +import { FloatingFocusManager, FloatingPortal } from '@floating-ui/react'; import { useDialogPopup } from './useDialogPopup'; import { useDialogRootContext } from '../root/DialogRootContext'; import { useComponentRenderer } from '../../utils/useComponentRenderer'; @@ -114,7 +114,6 @@ const DialogPopup = React.forwardRef(function DialogPopup( return ( - {modal && mounted && } {renderElement()} From b1b8c54c2e25b13ca2179f0cc3e6a9554b938ce4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Fri, 6 Dec 2024 11:14:15 +0700 Subject: [PATCH 09/11] Update Floating UI --- packages/react/package.json | 2 +- pnpm-lock.yaml | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/react/package.json b/packages/react/package.json index 739c1e45e3..bf86dfa13f 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -54,7 +54,7 @@ }, "dependencies": { "@babel/runtime": "^7.26.0", - "@floating-ui/react": "^0.26.28", + "@floating-ui/react": "^0.27.0", "@floating-ui/react-dom": "^2.1.2", "@floating-ui/utils": "^0.2.8", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7ea588fc61..debb99b144 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -619,8 +619,8 @@ importers: specifier: ^7.26.0 version: 7.26.0 '@floating-ui/react': - specifier: ^0.26.28 - version: 0.26.28(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) + specifier: ^0.27.0 + version: 0.27.0(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) '@floating-ui/react-dom': specifier: ^2.1.2 version: 2.1.2(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) @@ -1892,11 +1892,11 @@ packages: react: '>=16.8.0' react-dom: '>=16.8.0' - '@floating-ui/react@0.26.28': - resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==} + '@floating-ui/react@0.27.0': + resolution: {integrity: sha512-WLEksq7fJapXSJbmfiyq9pAW0a7ZFMEJToFE4oTDESxGjoa+nZu3YMjmZE2KvoUtQhqOK2yMMfWQFZyeWD0wGQ==} peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' + react: '>=17.0.0' + react-dom: '>=17.0.0' '@floating-ui/utils@0.2.8': resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==} @@ -7933,6 +7933,7 @@ packages: engines: {node: '>=0.6.0', teleport: '>=0.2.0'} deprecated: |- You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other. + (For a CapTP with native promises, see @endo/eventual-send and @endo/captp) qjobs@1.2.0: @@ -10854,7 +10855,7 @@ snapshots: react: 19.0.0-rc-fb9a90fa48-20240614 react-dom: 19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614) - '@floating-ui/react@0.26.28(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)': + '@floating-ui/react@0.27.0(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614)': dependencies: '@floating-ui/react-dom': 2.1.2(react-dom@19.0.0-rc-fb9a90fa48-20240614(react@19.0.0-rc-fb9a90fa48-20240614))(react@19.0.0-rc-fb9a90fa48-20240614) '@floating-ui/utils': 0.2.8 From 3f2a1eaff682e52c94dc617f6c23e6c281dfbe18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Dudak?= Date: Fri, 6 Dec 2024 11:41:28 +0700 Subject: [PATCH 10/11] Tests --- .../root/AlertDialogRoot.test.tsx | 53 ++++++++- .../react/src/dialog/root/DialogRoot.test.tsx | 106 ++++++++++++------ 2 files changed, 124 insertions(+), 35 deletions(-) diff --git a/packages/react/src/alert-dialog/root/AlertDialogRoot.test.tsx b/packages/react/src/alert-dialog/root/AlertDialogRoot.test.tsx index 97c99404f7..d65b540d25 100644 --- a/packages/react/src/alert-dialog/root/AlertDialogRoot.test.tsx +++ b/packages/react/src/alert-dialog/root/AlertDialogRoot.test.tsx @@ -1,5 +1,54 @@ -// This file is required by the API doc generator +import * as React from 'react'; +import { expect } from 'chai'; +import { screen, waitFor } from '@mui/internal-test-utils'; +import { AlertDialog } from '@base-ui-components/react/alert-dialog'; +import { createRenderer } from '#test-utils'; describe('', () => { - it('no-op', () => {}); + const { render } = createRenderer(); + + describe('modality', () => { + it('makes other interactive elements on the page inert when a modal dialog is open and restores them after the dialog is closed', async () => { + const { user } = await render( +
+ +