UNPKG

23.9 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import * as ReactDOM from 'react-dom';
5import PropTypes from 'prop-types';
6import elementTypeAcceptingRef from '@mui/utils/elementTypeAcceptingRef';
7import NoSsr from "../NoSsr/index.js";
8import Drawer, { getAnchor, isHorizontal } from "../Drawer/Drawer.js";
9import useForkRef from "../utils/useForkRef.js";
10import ownerDocument from "../utils/ownerDocument.js";
11import ownerWindow from "../utils/ownerWindow.js";
12import useEventCallback from "../utils/useEventCallback.js";
13import useEnhancedEffect from "../utils/useEnhancedEffect.js";
14import { useTheme } from "../zero-styled/index.js";
15import { useDefaultProps } from "../DefaultPropsProvider/index.js";
16import { getTransitionProps } from "../transitions/utils.js";
17import SwipeArea from "./SwipeArea.js";
18
19// This value is closed to what browsers are using internally to
20// trigger a native scroll.
21import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
22const UNCERTAINTY_THRESHOLD = 3; // px
23
24// This is the part of the drawer displayed on touch start.
25const DRAG_STARTED_SIGNAL = 20; // px
26
27// We can only have one instance at the time claiming ownership for handling the swipe.
28// Otherwise, the UX would be confusing.
29// That's why we use a singleton here.
30let claimedSwipeInstance = null;
31
32// Exported for test purposes.
33export function reset() {
34 claimedSwipeInstance = null;
35}
36function calculateCurrentX(anchor, touches, doc) {
37 return anchor === 'right' ? doc.body.offsetWidth - touches[0].pageX : touches[0].pageX;
38}
39function calculateCurrentY(anchor, touches, containerWindow) {
40 return anchor === 'bottom' ? containerWindow.innerHeight - touches[0].clientY : touches[0].clientY;
41}
42function getMaxTranslate(horizontalSwipe, paperInstance) {
43 return horizontalSwipe ? paperInstance.clientWidth : paperInstance.clientHeight;
44}
45function getTranslate(currentTranslate, startLocation, open, maxTranslate) {
46 return Math.min(Math.max(open ? startLocation - currentTranslate : maxTranslate + startLocation - currentTranslate, 0), maxTranslate);
47}
48
49/**
50 * @param {Element | null} element
51 * @param {Element} rootNode
52 */
53function getDomTreeShapes(element, rootNode) {
54 // Adapted from https://github.com/oliviertassinari/react-swipeable-views/blob/7666de1dba253b896911adf2790ce51467670856/packages/react-swipeable-views/src/SwipeableViews.js#L129
55 const domTreeShapes = [];
56 while (element && element !== rootNode.parentElement) {
57 const style = ownerWindow(rootNode).getComputedStyle(element);
58 if (
59 // Ignore the scroll children if the element is absolute positioned.
60 style.getPropertyValue('position') === 'absolute' ||
61 // Ignore the scroll children if the element has an overflowX hidden
62 style.getPropertyValue('overflow-x') === 'hidden') {
63 // noop
64 } else if (element.clientWidth > 0 && element.scrollWidth > element.clientWidth || element.clientHeight > 0 && element.scrollHeight > element.clientHeight) {
65 // Ignore the nodes that have no width.
66 // Keep elements with a scroll
67 domTreeShapes.push(element);
68 }
69 element = element.parentElement;
70 }
71 return domTreeShapes;
72}
73
74/**
75 * @param {object} param0
76 * @param {ReturnType<getDomTreeShapes>} param0.domTreeShapes
77 */
78function computeHasNativeHandler({
79 domTreeShapes,
80 start,
81 current,
82 anchor
83}) {
84 // Adapted from https://github.com/oliviertassinari/react-swipeable-views/blob/7666de1dba253b896911adf2790ce51467670856/packages/react-swipeable-views/src/SwipeableViews.js#L175
85 const axisProperties = {
86 scrollPosition: {
87 x: 'scrollLeft',
88 y: 'scrollTop'
89 },
90 scrollLength: {
91 x: 'scrollWidth',
92 y: 'scrollHeight'
93 },
94 clientLength: {
95 x: 'clientWidth',
96 y: 'clientHeight'
97 }
98 };
99 return domTreeShapes.some(shape => {
100 // Determine if we are going backward or forward.
101 let goingForward = current >= start;
102 if (anchor === 'top' || anchor === 'left') {
103 goingForward = !goingForward;
104 }
105 const axis = anchor === 'left' || anchor === 'right' ? 'x' : 'y';
106 const scrollPosition = Math.round(shape[axisProperties.scrollPosition[axis]]);
107 const areNotAtStart = scrollPosition > 0;
108 const areNotAtEnd = scrollPosition + shape[axisProperties.clientLength[axis]] < shape[axisProperties.scrollLength[axis]];
109 if (goingForward && areNotAtEnd || !goingForward && areNotAtStart) {
110 return true;
111 }
112 return false;
113 });
114}
115const iOS = typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent);
116const SwipeableDrawer = /*#__PURE__*/React.forwardRef(function SwipeableDrawer(inProps, ref) {
117 const props = useDefaultProps({
118 name: 'MuiSwipeableDrawer',
119 props: inProps
120 });
121 const theme = useTheme();
122 const transitionDurationDefault = {
123 enter: theme.transitions.duration.enteringScreen,
124 exit: theme.transitions.duration.leavingScreen
125 };
126 const {
127 anchor = 'left',
128 disableBackdropTransition = false,
129 disableDiscovery = false,
130 disableSwipeToOpen = iOS,
131 hideBackdrop,
132 hysteresis = 0.52,
133 allowSwipeInChildren = false,
134 minFlingVelocity = 450,
135 ModalProps: {
136 BackdropProps,
137 ...ModalPropsProp
138 } = {},
139 onClose,
140 onOpen,
141 open = false,
142 PaperProps = {},
143 SwipeAreaProps,
144 swipeAreaWidth = 20,
145 transitionDuration = transitionDurationDefault,
146 variant = 'temporary',
147 // Mobile first.
148 ...other
149 } = props;
150 const [maybeSwiping, setMaybeSwiping] = React.useState(false);
151 const swipeInstance = React.useRef({
152 isSwiping: null
153 });
154 const swipeAreaRef = React.useRef();
155 const backdropRef = React.useRef();
156 const paperRef = React.useRef();
157 const handleRef = useForkRef(PaperProps.ref, paperRef);
158 const touchDetected = React.useRef(false);
159
160 // Ref for transition duration based on / to match swipe speed
161 const calculatedDurationRef = React.useRef();
162
163 // Use a ref so the open value used is always up to date inside useCallback.
164 useEnhancedEffect(() => {
165 calculatedDurationRef.current = null;
166 }, [open]);
167 const setPosition = React.useCallback((translate, options = {}) => {
168 const {
169 mode = null,
170 changeTransition = true
171 } = options;
172 const anchorRtl = getAnchor(theme, anchor);
173 const rtlTranslateMultiplier = ['right', 'bottom'].includes(anchorRtl) ? 1 : -1;
174 const horizontalSwipe = isHorizontal(anchor);
175 const transform = horizontalSwipe ? `translate(${rtlTranslateMultiplier * translate}px, 0)` : `translate(0, ${rtlTranslateMultiplier * translate}px)`;
176 const drawerStyle = paperRef.current.style;
177 drawerStyle.webkitTransform = transform;
178 drawerStyle.transform = transform;
179 let transition = '';
180 if (mode) {
181 transition = theme.transitions.create('all', getTransitionProps({
182 easing: undefined,
183 style: undefined,
184 timeout: transitionDuration
185 }, {
186 mode
187 }));
188 }
189 if (changeTransition) {
190 drawerStyle.webkitTransition = transition;
191 drawerStyle.transition = transition;
192 }
193 if (!disableBackdropTransition && !hideBackdrop) {
194 const backdropStyle = backdropRef.current.style;
195 backdropStyle.opacity = 1 - translate / getMaxTranslate(horizontalSwipe, paperRef.current);
196 if (changeTransition) {
197 backdropStyle.webkitTransition = transition;
198 backdropStyle.transition = transition;
199 }
200 }
201 }, [anchor, disableBackdropTransition, hideBackdrop, theme, transitionDuration]);
202 const handleBodyTouchEnd = useEventCallback(nativeEvent => {
203 if (!touchDetected.current) {
204 return;
205 }
206 // TODO: uncomment once we enable eslint-plugin-react-compiler // eslint-disable-next-line react-compiler/react-compiler -- claimedSwipeInstance is a singleton
207 claimedSwipeInstance = null;
208 touchDetected.current = false;
209 ReactDOM.flushSync(() => {
210 setMaybeSwiping(false);
211 });
212
213 // The swipe wasn't started.
214 if (!swipeInstance.current.isSwiping) {
215 swipeInstance.current.isSwiping = null;
216 return;
217 }
218 swipeInstance.current.isSwiping = null;
219 const anchorRtl = getAnchor(theme, anchor);
220 const horizontal = isHorizontal(anchor);
221 let current;
222 if (horizontal) {
223 current = calculateCurrentX(anchorRtl, nativeEvent.changedTouches, ownerDocument(nativeEvent.currentTarget));
224 } else {
225 current = calculateCurrentY(anchorRtl, nativeEvent.changedTouches, ownerWindow(nativeEvent.currentTarget));
226 }
227 const startLocation = horizontal ? swipeInstance.current.startX : swipeInstance.current.startY;
228 const maxTranslate = getMaxTranslate(horizontal, paperRef.current);
229 const currentTranslate = getTranslate(current, startLocation, open, maxTranslate);
230 const translateRatio = currentTranslate / maxTranslate;
231 if (Math.abs(swipeInstance.current.velocity) > minFlingVelocity) {
232 // Calculate transition duration to match swipe speed
233 calculatedDurationRef.current = Math.abs((maxTranslate - currentTranslate) / swipeInstance.current.velocity) * 1000;
234 }
235 if (open) {
236 if (swipeInstance.current.velocity > minFlingVelocity || translateRatio > hysteresis) {
237 onClose();
238 } else {
239 // Reset the position, the swipe was aborted.
240 setPosition(0, {
241 mode: 'exit'
242 });
243 }
244 return;
245 }
246 if (swipeInstance.current.velocity < -minFlingVelocity || 1 - translateRatio > hysteresis) {
247 onOpen();
248 } else {
249 // Reset the position, the swipe was aborted.
250 setPosition(getMaxTranslate(horizontal, paperRef.current), {
251 mode: 'enter'
252 });
253 }
254 });
255 const startMaybeSwiping = (force = false) => {
256 if (!maybeSwiping) {
257 // on Safari Mobile, if you want to be able to have the 'click' event fired on child elements, nothing in the DOM can be changed.
258 // this is because Safari Mobile will not fire any mouse events (still fires touch though) if the DOM changes during mousemove.
259 // so do this change on first touchmove instead of touchstart
260 if (force || !(disableDiscovery && allowSwipeInChildren)) {
261 ReactDOM.flushSync(() => {
262 setMaybeSwiping(true);
263 });
264 }
265 const horizontalSwipe = isHorizontal(anchor);
266 if (!open && paperRef.current) {
267 // The ref may be null when a parent component updates while swiping.
268 setPosition(getMaxTranslate(horizontalSwipe, paperRef.current) + (disableDiscovery ? 15 : -DRAG_STARTED_SIGNAL), {
269 changeTransition: false
270 });
271 }
272 swipeInstance.current.velocity = 0;
273 swipeInstance.current.lastTime = null;
274 swipeInstance.current.lastTranslate = null;
275 swipeInstance.current.paperHit = false;
276 touchDetected.current = true;
277 }
278 };
279 const handleBodyTouchMove = useEventCallback(nativeEvent => {
280 // the ref may be null when a parent component updates while swiping
281 if (!paperRef.current || !touchDetected.current) {
282 return;
283 }
284
285 // We are not supposed to handle this touch move because the swipe was started in a scrollable container in the drawer
286 if (claimedSwipeInstance !== null && claimedSwipeInstance !== swipeInstance.current) {
287 return;
288 }
289 startMaybeSwiping(true);
290 const anchorRtl = getAnchor(theme, anchor);
291 const horizontalSwipe = isHorizontal(anchor);
292 const currentX = calculateCurrentX(anchorRtl, nativeEvent.touches, ownerDocument(nativeEvent.currentTarget));
293 const currentY = calculateCurrentY(anchorRtl, nativeEvent.touches, ownerWindow(nativeEvent.currentTarget));
294 if (open && paperRef.current.contains(nativeEvent.target) && claimedSwipeInstance === null) {
295 const domTreeShapes = getDomTreeShapes(nativeEvent.target, paperRef.current);
296 const hasNativeHandler = computeHasNativeHandler({
297 domTreeShapes,
298 start: horizontalSwipe ? swipeInstance.current.startX : swipeInstance.current.startY,
299 current: horizontalSwipe ? currentX : currentY,
300 anchor
301 });
302 if (hasNativeHandler) {
303 claimedSwipeInstance = true;
304 return;
305 }
306 claimedSwipeInstance = swipeInstance.current;
307 }
308
309 // We don't know yet.
310 if (swipeInstance.current.isSwiping == null) {
311 const dx = Math.abs(currentX - swipeInstance.current.startX);
312 const dy = Math.abs(currentY - swipeInstance.current.startY);
313 const definitelySwiping = horizontalSwipe ? dx > dy && dx > UNCERTAINTY_THRESHOLD : dy > dx && dy > UNCERTAINTY_THRESHOLD;
314 if (definitelySwiping && nativeEvent.cancelable) {
315 nativeEvent.preventDefault();
316 }
317 if (definitelySwiping === true || (horizontalSwipe ? dy > UNCERTAINTY_THRESHOLD : dx > UNCERTAINTY_THRESHOLD)) {
318 swipeInstance.current.isSwiping = definitelySwiping;
319 if (!definitelySwiping) {
320 handleBodyTouchEnd(nativeEvent);
321 return;
322 }
323
324 // Shift the starting point.
325 swipeInstance.current.startX = currentX;
326 swipeInstance.current.startY = currentY;
327
328 // Compensate for the part of the drawer displayed on touch start.
329 if (!disableDiscovery && !open) {
330 if (horizontalSwipe) {
331 swipeInstance.current.startX -= DRAG_STARTED_SIGNAL;
332 } else {
333 swipeInstance.current.startY -= DRAG_STARTED_SIGNAL;
334 }
335 }
336 }
337 }
338 if (!swipeInstance.current.isSwiping) {
339 return;
340 }
341 const maxTranslate = getMaxTranslate(horizontalSwipe, paperRef.current);
342 let startLocation = horizontalSwipe ? swipeInstance.current.startX : swipeInstance.current.startY;
343 if (open && !swipeInstance.current.paperHit) {
344 startLocation = Math.min(startLocation, maxTranslate);
345 }
346 const translate = getTranslate(horizontalSwipe ? currentX : currentY, startLocation, open, maxTranslate);
347 if (open) {
348 if (!swipeInstance.current.paperHit) {
349 const paperHit = horizontalSwipe ? currentX < maxTranslate : currentY < maxTranslate;
350 if (paperHit) {
351 swipeInstance.current.paperHit = true;
352 swipeInstance.current.startX = currentX;
353 swipeInstance.current.startY = currentY;
354 } else {
355 return;
356 }
357 } else if (translate === 0) {
358 swipeInstance.current.startX = currentX;
359 swipeInstance.current.startY = currentY;
360 }
361 }
362 if (swipeInstance.current.lastTranslate === null) {
363 swipeInstance.current.lastTranslate = translate;
364 swipeInstance.current.lastTime = performance.now() + 1;
365 }
366 const velocity = (translate - swipeInstance.current.lastTranslate) / (performance.now() - swipeInstance.current.lastTime) * 1e3;
367
368 // Low Pass filter.
369 swipeInstance.current.velocity = swipeInstance.current.velocity * 0.4 + velocity * 0.6;
370 swipeInstance.current.lastTranslate = translate;
371 swipeInstance.current.lastTime = performance.now();
372
373 // We are swiping, let's prevent the scroll event on iOS.
374 if (nativeEvent.cancelable) {
375 nativeEvent.preventDefault();
376 }
377 setPosition(translate);
378 });
379 const handleBodyTouchStart = useEventCallback(nativeEvent => {
380 // We are not supposed to handle this touch move.
381 // Example of use case: ignore the event if there is a Slider.
382 if (nativeEvent.defaultPrevented) {
383 return;
384 }
385
386 // We can only have one node at the time claiming ownership for handling the swipe.
387 if (nativeEvent.defaultMuiPrevented) {
388 return;
389 }
390
391 // At least one element clogs the drawer interaction zone.
392 if (open && (hideBackdrop || !backdropRef.current.contains(nativeEvent.target)) && !paperRef.current.contains(nativeEvent.target)) {
393 return;
394 }
395 const anchorRtl = getAnchor(theme, anchor);
396 const horizontalSwipe = isHorizontal(anchor);
397 const currentX = calculateCurrentX(anchorRtl, nativeEvent.touches, ownerDocument(nativeEvent.currentTarget));
398 const currentY = calculateCurrentY(anchorRtl, nativeEvent.touches, ownerWindow(nativeEvent.currentTarget));
399 if (!open) {
400 // logic for if swipe should be ignored:
401 // if disableSwipeToOpen
402 // if target != swipeArea, and target is not a child of paper ref
403 // if is a child of paper ref, and `allowSwipeInChildren` does not allow it
404 if (disableSwipeToOpen || !(nativeEvent.target === swipeAreaRef.current || paperRef.current?.contains(nativeEvent.target) && (typeof allowSwipeInChildren === 'function' ? allowSwipeInChildren(nativeEvent, swipeAreaRef.current, paperRef.current) : allowSwipeInChildren))) {
405 return;
406 }
407 if (horizontalSwipe) {
408 if (currentX > swipeAreaWidth) {
409 return;
410 }
411 } else if (currentY > swipeAreaWidth) {
412 return;
413 }
414 }
415 nativeEvent.defaultMuiPrevented = true;
416 claimedSwipeInstance = null;
417 swipeInstance.current.startX = currentX;
418 swipeInstance.current.startY = currentY;
419 startMaybeSwiping();
420 });
421 React.useEffect(() => {
422 if (variant === 'temporary') {
423 const doc = ownerDocument(paperRef.current);
424 doc.addEventListener('touchstart', handleBodyTouchStart);
425 // A blocking listener prevents Firefox's navbar to auto-hide on scroll.
426 // It only needs to prevent scrolling on the drawer's content when open.
427 // When closed, the overlay prevents scrolling.
428 doc.addEventListener('touchmove', handleBodyTouchMove, {
429 passive: !open
430 });
431 doc.addEventListener('touchend', handleBodyTouchEnd);
432 return () => {
433 doc.removeEventListener('touchstart', handleBodyTouchStart);
434 doc.removeEventListener('touchmove', handleBodyTouchMove, {
435 passive: !open
436 });
437 doc.removeEventListener('touchend', handleBodyTouchEnd);
438 };
439 }
440 return undefined;
441 }, [variant, open, handleBodyTouchStart, handleBodyTouchMove, handleBodyTouchEnd]);
442 React.useEffect(() => () => {
443 // We need to release the lock.
444 if (claimedSwipeInstance === swipeInstance.current) {
445 claimedSwipeInstance = null;
446 }
447 }, []);
448 React.useEffect(() => {
449 if (!open) {
450 setMaybeSwiping(false);
451 }
452 }, [open]);
453 return /*#__PURE__*/_jsxs(React.Fragment, {
454 children: [/*#__PURE__*/_jsx(Drawer, {
455 open: variant === 'temporary' && maybeSwiping ? true : open,
456 variant: variant,
457 ModalProps: {
458 BackdropProps: {
459 ...BackdropProps,
460 ref: backdropRef
461 },
462 // Ensures that paperRef.current will be defined inside the touch start event handler
463 // See https://github.com/mui/material-ui/issues/30414 for more information
464 ...(variant === 'temporary' && {
465 keepMounted: true
466 }),
467 ...ModalPropsProp
468 },
469 hideBackdrop: hideBackdrop,
470 PaperProps: {
471 ...PaperProps,
472 style: {
473 pointerEvents: variant === 'temporary' && !open && !allowSwipeInChildren ? 'none' : '',
474 ...PaperProps.style
475 },
476 ref: handleRef
477 },
478 anchor: anchor,
479 transitionDuration: calculatedDurationRef.current || transitionDuration,
480 onClose: onClose,
481 ref: ref,
482 ...other
483 }), !disableSwipeToOpen && variant === 'temporary' && /*#__PURE__*/_jsx(NoSsr, {
484 children: /*#__PURE__*/_jsx(SwipeArea, {
485 anchor: anchor,
486 ref: swipeAreaRef,
487 width: swipeAreaWidth,
488 ...SwipeAreaProps
489 })
490 })]
491 });
492});
493process.env.NODE_ENV !== "production" ? SwipeableDrawer.propTypes /* remove-proptypes */ = {
494 // ┌────────────────────────────── Warning ──────────────────────────────┐
495 // │ These PropTypes are generated from the TypeScript type definitions. │
496 // │ To update them, edit the d.ts file and run `pnpm proptypes`. │
497 // └─────────────────────────────────────────────────────────────────────┘
498 /**
499 * If set to true, the swipe event will open the drawer even if the user begins the swipe on one of the drawer's children.
500 * This can be useful in scenarios where the drawer is partially visible.
501 * You can customize it further with a callback that determines which children the user can drag over to open the drawer
502 * (for example, to ignore other elements that handle touch move events, like sliders).
503 *
504 * @param {TouchEvent} event The 'touchstart' event
505 * @param {HTMLDivElement} swipeArea The swipe area element
506 * @param {HTMLDivElement} paper The drawer's paper element
507 *
508 * @default false
509 */
510 allowSwipeInChildren: PropTypes.oneOfType([PropTypes.func, PropTypes.bool]),
511 /**
512 * @ignore
513 */
514 anchor: PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
515 /**
516 * The content of the component.
517 */
518 children: PropTypes.node,
519 /**
520 * Disable the backdrop transition.
521 * This can improve the FPS on low-end devices.
522 * @default false
523 */
524 disableBackdropTransition: PropTypes.bool,
525 /**
526 * If `true`, touching the screen near the edge of the drawer will not slide in the drawer a bit
527 * to promote accidental discovery of the swipe gesture.
528 * @default false
529 */
530 disableDiscovery: PropTypes.bool,
531 /**
532 * If `true`, swipe to open is disabled. This is useful in browsers where swiping triggers
533 * navigation actions. Swipe to open is disabled on iOS browsers by default.
534 * @default typeof navigator !== 'undefined' && /iPad|iPhone|iPod/.test(navigator.userAgent)
535 */
536 disableSwipeToOpen: PropTypes.bool,
537 /**
538 * @ignore
539 */
540 hideBackdrop: PropTypes.bool,
541 /**
542 * Affects how far the drawer must be opened/closed to change its state.
543 * Specified as percent (0-1) of the width of the drawer
544 * @default 0.52
545 */
546 hysteresis: PropTypes.number,
547 /**
548 * Defines, from which (average) velocity on, the swipe is
549 * defined as complete although hysteresis isn't reached.
550 * Good threshold is between 250 - 1000 px/s
551 * @default 450
552 */
553 minFlingVelocity: PropTypes.number,
554 /**
555 * @ignore
556 */
557 ModalProps: PropTypes /* @typescript-to-proptypes-ignore */.shape({
558 BackdropProps: PropTypes.shape({
559 component: elementTypeAcceptingRef
560 })
561 }),
562 /**
563 * Callback fired when the component requests to be closed.
564 *
565 * @param {React.SyntheticEvent<{}>} event The event source of the callback.
566 */
567 onClose: PropTypes.func.isRequired,
568 /**
569 * Callback fired when the component requests to be opened.
570 *
571 * @param {React.SyntheticEvent<{}>} event The event source of the callback.
572 */
573 onOpen: PropTypes.func.isRequired,
574 /**
575 * If `true`, the component is shown.
576 * @default false
577 */
578 open: PropTypes.bool,
579 /**
580 * @ignore
581 */
582 PaperProps: PropTypes /* @typescript-to-proptypes-ignore */.shape({
583 component: elementTypeAcceptingRef,
584 style: PropTypes.object
585 }),
586 /**
587 * The element is used to intercept the touch events on the edge.
588 */
589 SwipeAreaProps: PropTypes.object,
590 /**
591 * The width of the left most (or right most) area in `px` that
592 * the drawer can be swiped open from.
593 * @default 20
594 */
595 swipeAreaWidth: PropTypes.number,
596 /**
597 * The duration for the transition, in milliseconds.
598 * You may specify a single timeout for all transitions, or individually with an object.
599 * @default {
600 * enter: theme.transitions.duration.enteringScreen,
601 * exit: theme.transitions.duration.leavingScreen,
602 * }
603 */
604 transitionDuration: PropTypes.oneOfType([PropTypes.number, PropTypes.shape({
605 appear: PropTypes.number,
606 enter: PropTypes.number,
607 exit: PropTypes.number
608 })]),
609 /**
610 * @ignore
611 */
612 variant: PropTypes.oneOf(['permanent', 'persistent', 'temporary'])
613} : void 0;
614export default SwipeableDrawer;
\No newline at end of file