Skip to content

Commit

Permalink
Use common animation primitives
Browse files Browse the repository at this point in the history
  • Loading branch information
michaldudak committed May 29, 2024
1 parent 3156e7e commit 8ba9468
Show file tree
Hide file tree
Showing 15 changed files with 114 additions and 184 deletions.
9 changes: 2 additions & 7 deletions docs/pages/base-ui/api/dialog-popup.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
{
"props": {
"animated": { "type": { "name": "bool" }, "default": "false" },
"animated": { "type": { "name": "bool" }, "default": "true" },
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"container": {
"type": {
"name": "union",
"description": "function (props, propName) {\n if (props[propName] == null) {\n return new Error(\"Prop '\" + propName + \"' is required but wasn't specified\");\n } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(\"Expected prop '\" + propName + \"' to be of type Element\");\n }\n}<br>&#124;&nbsp;{ current?: function (props, propName) {\n if (props[propName] == null) {\n return null;\n } else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {\n return new Error(\"Expected prop '\" + propName + \"' to be of type Element\");\n }\n} }"
}
},
"container": { "type": { "name": "union", "description": "HTML element<br>&#124;&nbsp;ref" } },
"keepMounted": { "type": { "name": "bool" }, "default": "false" },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
Expand Down
8 changes: 0 additions & 8 deletions docs/pages/base-ui/api/use-transitioned-element.json

This file was deleted.

4 changes: 2 additions & 2 deletions docs/pages/experiments/dialog.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
transform: translate(-50%, -35%) scale(0.8, calc(pow(0.95, var(--nested-dialogs))))
translateY(calc(-30px * var(--nested-dialogs)));
visibility: hidden;
opacity: 0.5;
opacity: 0;
transition:
transform var(--transition-duration) ease-in,
opacity var(--transition-duration) ease-in,
Expand All @@ -92,7 +92,7 @@
@starting-style {
& {
transform: translate(-50%, -35%) scale(0.8) translateY(0);
opacity: 0.5;
opacity: 0;
}
}

Expand Down
9 changes: 5 additions & 4 deletions docs/pages/experiments/dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from 'react';
import clsx from 'clsx';
import * as Dialog from '@base_ui/react/Dialog';
import { useTransitionStatus } from '@base_ui/react/Transitions';
// eslint-disable-next-line no-restricted-imports
import { useTransitionStatus } from '@base_ui/react/utils/useTransitionStatus';
import { animated as springAnimated, useSpring, useSpringRef } from '@react-spring/web';
import classes from './dialog.module.css';

Expand Down Expand Up @@ -161,7 +162,7 @@ function ReactSpringTransition(props: { open: boolean; children?: React.ReactEle
from: { opacity: 0, transform: 'translateY(-8px) scale(0.95)' },
});

const { mounted, onTransitionEnded } = useTransitionStatus(open);
const { mounted, setMounted } = useTransitionStatus(open);

React.useEffect(() => {
if (open) {
Expand All @@ -175,10 +176,10 @@ function ReactSpringTransition(props: { open: boolean; children?: React.ReactEle
opacity: 0,
transform: 'translateY(-8px) scale(0.95)',
config: { tension: 170, friction: 26 },
onRest: () => onTransitionEnded(),
onRest: () => setMounted(false),
});
}
}, [api, open, mounted, onTransitionEnded]);
}, [api, open, mounted, setMounted]);

return mounted ? (
<springAnimated.div style={springs} className={classes.springWrapper}>
Expand Down

This file was deleted.

13 changes: 9 additions & 4 deletions packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,27 @@ const DialogBackdrop = React.forwardRef(function DialogBackdrop(
) {
const { render, className, animated = false, keepMounted = false, ...other } = props;
const { open, modal, hasParentDialog } = useDialogRootContext();
const { getRootProps, mounted } = useDialogBackdrop({ animated, open });
const { getRootProps, mounted, transitionStatus } = useDialogBackdrop({
animated,
open,
ref: forwardedRef,
});

const ownerState: DialogBackdropOwnerState = React.useMemo(
() => ({ open, modal }),
[open, modal],
() => ({ open, modal, transitionStatus }),
[open, modal, transitionStatus],
);

const { renderElement } = useComponentRenderer({
render: render ?? 'div',
className,
ownerState,
propGetter: getRootProps,
ref: forwardedRef,
extraProps: other,
customStyleHookMapping: {
open: (value) => ({ 'data-state': value ? 'open' : 'closed' }),
transitionStatus: (value) =>
value !== undefined ? { 'data-transition-status': value } : null,
},
});

Expand Down
12 changes: 11 additions & 1 deletion packages/mui-base/src/Dialog/Backdrop/DialogBackdrop.types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { BaseUIComponentProps } from '@base_ui/react/utils/BaseUI.types';
import { TransitionStatus } from '../../utils/useTransitionStatus';
import { BaseUIComponentProps } from '../../utils/BaseUI.types';

export interface DialogBackdropProps extends BaseUIComponentProps<'div', DialogBackdropOwnerState> {
/**
Expand All @@ -19,6 +20,7 @@ export interface DialogBackdropProps extends BaseUIComponentProps<'div', DialogB
export interface DialogBackdropOwnerState {
open: boolean;
modal: boolean;
transitionStatus: TransitionStatus;
}

export interface UseDialogBackdropParams {
Expand All @@ -31,6 +33,10 @@ export interface UseDialogBackdropParams {
* Determines if the dialog is open.
*/
open: boolean;
/**
* The ref to the background element.
*/
ref: React.Ref<HTMLElement>;
}

export interface UseDialogBackdropReturnValue {
Expand All @@ -42,4 +48,8 @@ export interface UseDialogBackdropReturnValue {
* 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;
}
30 changes: 16 additions & 14 deletions packages/mui-base/src/Dialog/Backdrop/useDialogBackdrop.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,39 @@
import * as React from 'react';
import type { UseDialogBackdropParams, UseDialogBackdropReturnValue } from './DialogBackdrop.types';
import { useTransitionedElement } from '../../Transitions';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useAnimatedElement } from '../../utils/useAnimatedElement';
import { useForkRef } from '../../utils/useForkRef';

/**
*
* API:
*
* - [useDialogBackdrop API](https://mui.com/base-ui/api/use-dialog-backdrop/)
*/
export function useDialogBackdrop(params: UseDialogBackdropParams): UseDialogBackdropReturnValue {
const { open } = params;
const { animated, open, ref } = params;

const ref = React.useRef<HTMLElement | null>(null);
const backdropRef = React.useRef<HTMLElement | null>(null);
const handleRef = useForkRef(ref, backdropRef);

const { getRootProps: getTransitionProps, mounted } = useTransitionedElement({
isRendered: open,
elementRef: ref,
const { mounted, transitionStatus } = useAnimatedElement({
open,
ref: backdropRef,
enabled: animated,
});

const getRootProps = React.useCallback(
(externalProps: React.ComponentPropsWithRef<any>) =>
mergeReactProps(
externalProps,
getTransitionProps({
role: 'presentation',
ref,
}),
),
[getTransitionProps],
mergeReactProps(externalProps, {
role: 'presentation',
ref: handleRef,
}),
[handleRef],
);

return {
getRootProps,
mounted,
transitionStatus,
};
}
24 changes: 4 additions & 20 deletions packages/mui-base/src/Dialog/Popup/DialogPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { DialogPopupOwnerState, DialogPopupProps } from './DialogPopup.types';
import { useDialogPopup } from './useDialogPopup';
import { useDialogRootContext } from '../Root/DialogRootContext';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { refType, HTMLElementType } from '../../utils/proptypes';

const DialogPopup = React.forwardRef(function DialogPopup(
props: DialogPopupProps,
forwardedRef: React.ForwardedRef<HTMLDivElement>,
) {
const {
animated = false,
animated = true,
className,
container,
id: idProp,
Expand Down Expand Up @@ -73,7 +74,7 @@ DialogPopup.propTypes /* remove-proptypes */ = {
* If `true`, the dialog supports CSS-based animations and transitions.
* It is kept in the DOM until the animation completes.
*
* @default false
* @default true
*/
animated: PropTypes.bool,
/**
Expand All @@ -87,24 +88,7 @@ DialogPopup.propTypes /* remove-proptypes */ = {
/**
* The container element to which the popup is appended to.
*/
container: PropTypes.oneOfType([
function (props, propName) {
if (props[propName] == null) {
return new Error("Prop '" + propName + "' is required but wasn't specified");
} else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {
return new Error("Expected prop '" + propName + "' to be of type Element");
}
},
PropTypes.shape({
current: function (props, propName) {
if (props[propName] == null) {
return null;
} else if (typeof props[propName] !== 'object' || props[propName].nodeType !== 1) {
return new Error("Expected prop '" + propName + "' to be of type Element");
}
},
}),
]),
container: PropTypes /* @typescript-to-proptypes-ignore */.oneOfType([HTMLElementType, refType]),
/**
* @ignore
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/mui-base/src/Dialog/Popup/DialogPopup.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export interface DialogPopupProps extends BaseUIComponentProps<'div', DialogPopu
* If `true`, the dialog supports CSS-based animations and transitions.
* It is kept in the DOM until the animation completes.
*
* @default false
* @default true
*/
animated?: boolean;
/**
Expand Down
48 changes: 29 additions & 19 deletions packages/mui-base/src/Dialog/Popup/useDialogPopup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { UseDialogPopupParameters, UseDialogPopupReturnValue } from './DialogPop
import { useId } from '../../utils/useId';
import { useForkRef } from '../../utils/useForkRef';
import { mergeReactProps } from '../../utils/mergeReactProps';
import { useTransitionedElement } from '../../Transitions';
import { useAnimationsFinished } from '../../utils/useAnimationsFinished';
import { useTransitionStatus } from '../../utils/useTransitionStatus';
/**
*
* Demos:
Expand All @@ -17,6 +18,7 @@ import { useTransitionedElement } from '../../Transitions';
*/
export function useDialogPopup(parameters: UseDialogPopupParameters): UseDialogPopupReturnValue {
const {
animated,
descriptionElementId,
id: idParam,
modal,
Expand Down Expand Up @@ -47,10 +49,20 @@ export function useDialogPopup(parameters: UseDialogPopupParameters): UseDialogP
const id = useId(idParam);
const handleRef = useForkRef(ref, popupRef, refs.setFloating);

const { getRootProps: getTransitionProps, mounted } = useTransitionedElement({
isRendered: open,
elementRef: popupRef,
});
const { mounted, setMounted, transitionStatus } = useTransitionStatus(open, animated);
const runOnceAnimationsFinish = useAnimationsFinished(() => popupRef.current);

React.useEffect(() => {
if (!open) {
if (animated) {
runOnceAnimationsFinish(() => {
setMounted(false);
});
} else {
setMounted(false);
}
}
}, [animated, open, runOnceAnimationsFinish, setMounted]);

React.useEffect(() => {
setPopupElementId(id);
Expand All @@ -60,20 +72,18 @@ export function useDialogPopup(parameters: UseDialogPopupParameters): UseDialogP
}, [id, setPopupElementId]);

const getRootProps = (externalProps: React.HTMLAttributes<any>) =>
mergeReactProps(
externalProps,
getTransitionProps({
'aria-labelledby': titleElementId ?? undefined,
'aria-describedby': descriptionElementId ?? undefined,
'aria-hidden': !open || undefined,
'aria-modal': open && modal ? true : undefined,
role: type,
tabIndex: -1,
...getFloatingProps(),
id,
ref: handleRef,
}),
);
mergeReactProps(externalProps, {
'aria-labelledby': titleElementId ?? undefined,
'aria-describedby': descriptionElementId ?? undefined,
'aria-hidden': !open || undefined,
'aria-modal': open && modal ? true : undefined,
role: type,
tabIndex: -1,
...getFloatingProps(),
id,
ref: handleRef,
'data-transition-status': transitionStatus,
});

return {
floatingContext: context,
Expand Down
2 changes: 0 additions & 2 deletions packages/mui-base/src/Transitions/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
export * from './CssAnimation';
export * from './CssTransition';
export * from './useTransitionStatus';
export * from './useTransitionedElement';
30 changes: 0 additions & 30 deletions packages/mui-base/src/Transitions/useTransitionStatus.ts

This file was deleted.

Loading

0 comments on commit 8ba9468

Please sign in to comment.