From 2faae9b40462ac2754cffcb6e9c95a2df8339b86 Mon Sep 17 00:00:00 2001 From: "steffen.schlabbach" Date: Fri, 2 Jan 2026 17:28:53 +0100 Subject: [PATCH 1/5] feat: add center direction to drawer and add transitions between directions --- .../src/components/examples/responsive.tsx | 43 +++++++ apps/www/src/constants/examples.tsx | 6 + apps/www/src/hooks/use-mobile.ts | 21 ++++ packages/core/src/index.tsx | 77 ++++++++---- packages/core/src/style.css | 112 ++++++++++++++++-- packages/core/src/types/index.ts | 2 +- packages/core/src/utils/helpers.ts | 2 + 7 files changed, 232 insertions(+), 31 deletions(-) create mode 100644 apps/www/src/components/examples/responsive.tsx create mode 100644 apps/www/src/hooks/use-mobile.ts diff --git a/apps/www/src/components/examples/responsive.tsx b/apps/www/src/components/examples/responsive.tsx new file mode 100644 index 0000000..be5cff6 --- /dev/null +++ b/apps/www/src/components/examples/responsive.tsx @@ -0,0 +1,43 @@ +import { Drawer } from "vaul-base" + +import { Button } from "@/components/button" +import { useIsMobile } from "@/hooks/use-mobile" +import { cn } from "@/lib/utils" + +const ResponsiveDrawer = () => { + const isMobile = useIsMobile() + return ( + + } + /> + + + + {isMobile && } +
+

Welcome to the Drawer

+

+ This drawer adapts its direction based on your screen size. +

+

+ We just change the direction prop dynamically using a custom hook. The transition between directions is + handeled smoothly by the drawer component. +

+ + +
+              {`
+  ...
+`}
+              
+            
+ +
+
+
+
+ ) +} + +export default ResponsiveDrawer diff --git a/apps/www/src/constants/examples.tsx b/apps/www/src/constants/examples.tsx index 6cc6e24..674c19d 100644 --- a/apps/www/src/constants/examples.tsx +++ b/apps/www/src/constants/examples.tsx @@ -2,6 +2,7 @@ import BasicDrawer from "@/components/examples/basic" import DirectionsDrawer from "@/components/examples/directions" import NestedDrawer from "@/components/examples/nested" import NonDismissableDrawer from "@/components/examples/non-dismissable" +import ResponsiveDrawer from "@/components/examples/responsive" import ScaledBackgroundDrawer from "@/components/examples/scaled-background" import { ScrollableDrawer } from "@/components/examples/scrollable" import SnapPointsDrawer from "@/components/examples/snap-points" @@ -23,6 +24,11 @@ export const EXAMPLES = { name: "Directions", description: "Drawers can be opened from different sides of the screen.", render: () => , + }, + responsive: { + name: "Responsive", + description: "Changing drawer direction based on screen size.", + render: () => , }, "scaled-background": { name: "Scaled Background", diff --git a/apps/www/src/hooks/use-mobile.ts b/apps/www/src/hooks/use-mobile.ts new file mode 100644 index 0000000..6be5f6c --- /dev/null +++ b/apps/www/src/hooks/use-mobile.ts @@ -0,0 +1,21 @@ +import * as React from 'react'; + +const MOBILE_BREAKPOINT = 768; + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState( + undefined + ); + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`); + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + }; + mql.addEventListener('change', onChange); + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); + return () => mql.removeEventListener('change', onChange); + }, []); + + return !!isMobile; +} diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index fce2049..f24e464 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -115,7 +115,7 @@ export type DialogProps = { * Direction of the drawer. Can be `top` or `bottom`, `left`, `right`. * @default 'bottom' */ - direction?: "top" | "bottom" | "left" | "right" + direction?: "top" | "bottom" | "left" | "right" | "center" /** * Opened by default, skips initial enter animation. Still reacts to `open` state changes * @default false @@ -287,6 +287,7 @@ export function Root({ } function onPress(event: React.PointerEvent) { + if (direction === "center") return if (!dismissible && !snapPoints) return if (drawerRef.current && !drawerRef.current.contains(event.target as Node)) return @@ -466,10 +467,12 @@ export function Root({ const translateValue = Math.min(dampenedDraggedDistance * -1, 0) * directionMultiplier + + const x = isVertical(direction) ? "50%" : `${translateValue}px` + const y = isVertical(direction) ? `${translateValue}px` : "50%" + set(drawerRef.current, { - transform: isVertical(direction) - ? `translate3d(0, ${translateValue}px, 0)` - : `translate3d(${translateValue}px, 0, 0)`, + transform: `translate3d(${x}, ${y}, 0)`, }) return } @@ -518,10 +521,11 @@ export function Root({ if (!snapPoints) { const translateValue = absDraggedDistance * directionMultiplier + const x = isVertical(direction) ? "50%" : `${translateValue}px` + const y = isVertical(direction) ? `${translateValue}px` : "50%" + set(drawerRef.current, { - transform: isVertical(direction) - ? `translate3d(0, ${translateValue}px, 0)` - : `translate3d(${translateValue}px, 0, 0)`, + transform: `translate3d(${x}, ${y}, 0)`, }) } } @@ -626,8 +630,11 @@ export function Root({ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]") const currentSwipeAmount = getTranslate(drawerRef.current, direction) + const x = isVertical(direction) ? "50%" : "0" + const y = isVertical(direction) ? "0" : "50%" + set(drawerRef.current, { - transform: "translate3d(0, 0, 0)", + transform: `translate3d(${x}, ${y}, 0)`, transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`, }) @@ -789,11 +796,12 @@ export function Root({ window.clearTimeout(nestedOpenChangeTimer.current) } + const x = isVertical(direction) ? "50%" : `${initialTranslate}px` + const y = isVertical(direction) ? `${initialTranslate}px` : "50%" + set(drawerRef.current, { transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`, - transform: isVertical(direction) - ? `scale(${scale}) translate3d(0, ${initialTranslate}px, 0)` - : `scale(${scale}) translate3d(${initialTranslate}px, 0, 0)`, + transform: `scale(${scale}) translate3d(${x}, ${y}, 0)`, }) if (!o && drawerRef.current) { @@ -802,11 +810,13 @@ export function Root({ drawerRef.current as HTMLElement, direction ) + + const x = isVertical(direction) ? "50%" : `${translateValue}px` + const y = isVertical(direction) ? `${translateValue}px` : "50%" + set(drawerRef.current, { transition: "none", - transform: isVertical(direction) - ? `translate3d(0, ${translateValue}px, 0)` - : `translate3d(${translateValue}px, 0, 0)`, + transform: `translate3d(${x}, ${y}, 0)`, }) }, 500) } @@ -816,6 +826,7 @@ export function Root({ _event: React.PointerEvent, percentageDragged: number ) { + if (direction === "center") return if (percentageDragged < 0) return const initialScale = @@ -824,10 +835,11 @@ export function Root({ const newTranslate = -NESTED_DISPLACEMENT + percentageDragged * NESTED_DISPLACEMENT + const x = isVertical(direction) ? "50%" : `${newTranslate}px` + const y = isVertical(direction) ? `${newTranslate}px` : "50%" + set(drawerRef.current, { - transform: isVertical(direction) - ? `scale(${newScale}) translate3d(0, ${newTranslate}px, 0)` - : `scale(${newScale}) translate3d(${newTranslate}px, 0, 0)`, + transform: `scale(${newScale}) translate3d(${x}, ${y}, 0)`, transition: "none", }) } @@ -836,16 +848,18 @@ export function Root({ _event: React.PointerEvent, o: boolean ) { + if (direction === "center") return const dim = isVertical(direction) ? window.innerHeight : window.innerWidth const scale = o ? (dim - NESTED_DISPLACEMENT) / dim : 1 const translate = o ? -NESTED_DISPLACEMENT : 0 if (o) { + const x = isVertical(direction) ? "50%" : `${translate}px` + const y = isVertical(direction) ? `${translate}px` : "50%" + set(drawerRef.current, { transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`, - transform: isVertical(direction) - ? `scale(${scale}) translate3d(0, ${translate}px, 0)` - : `scale(${scale}) translate3d(${translate}px, 0, 0)`, + transform: `scale(${scale}) translate3d(${x}, ${y}, 0)`, }) } } @@ -859,6 +873,13 @@ export function Root({ } }, [modal]) + React.useLayoutEffect(() => { + if (drawerRef.current) { + drawerRef.current.style.removeProperty("transform") + drawerRef.current.style.removeProperty("transition") + } + }, [direction]) + return ( (function ( React.useRef | null>(null) const wasBeyondThePointRef = React.useRef(false) const hasSnapPoints = snapPoints && snapPoints.length > 0 + const prevDirectionRef = React.useRef(direction) + const wasOpenRef = React.useRef(isOpen) + const isMorphing = React.useRef(false) + + if (isOpen && wasOpenRef.current && prevDirectionRef.current !== direction) { + isMorphing.current = true + } else if (!isOpen) { + isMorphing.current = false + } + + React.useEffect(() => { + prevDirectionRef.current = direction + wasOpenRef.current = isOpen + }) + useScaleBackground() const isDeltaInDirection = ( @@ -1032,6 +1068,7 @@ export const Content = React.forwardRef(function ( { case "left": case "right": return false + case "center": + return true default: return direction satisfies never } From 0f0e9425995c5d19924fd0560e8e7d60569aee67 Mon Sep 17 00:00:00 2001 From: "steffen.schlabbach" Date: Sat, 3 Jan 2026 10:45:06 +0100 Subject: [PATCH 2/5] chore: update responsive drawer to use data attributes for styling --- .../src/components/examples/responsive.tsx | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/apps/www/src/components/examples/responsive.tsx b/apps/www/src/components/examples/responsive.tsx index be5cff6..21ba4c7 100644 --- a/apps/www/src/components/examples/responsive.tsx +++ b/apps/www/src/components/examples/responsive.tsx @@ -2,7 +2,6 @@ import { Drawer } from "vaul-base" import { Button } from "@/components/button" import { useIsMobile } from "@/hooks/use-mobile" -import { cn } from "@/lib/utils" const ResponsiveDrawer = () => { const isMobile = useIsMobile() @@ -13,8 +12,18 @@ const ResponsiveDrawer = () => { /> - - {isMobile && } + +
+ +
+

Welcome to the Drawer

@@ -23,7 +32,7 @@ const ResponsiveDrawer = () => {

We just change the direction prop dynamically using a custom hook. The transition between directions is handeled smoothly by the drawer component. -

+

@@ -32,7 +41,10 @@ const ResponsiveDrawer = () => {
 `}
               
             
- +
+ +
+
From 7f8dd09a9a8313f2ce489bf2c463442d4801a8e0 Mon Sep 17 00:00:00 2001 From: "steffen.schlabbach" Date: Sat, 3 Jan 2026 10:54:09 +0100 Subject: [PATCH 3/5] fix: adjust responsiveDialog styles --- apps/www/src/components/examples/responsive.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/www/src/components/examples/responsive.tsx b/apps/www/src/components/examples/responsive.tsx index 21ba4c7..001aec2 100644 --- a/apps/www/src/components/examples/responsive.tsx +++ b/apps/www/src/components/examples/responsive.tsx @@ -12,13 +12,13 @@ const ResponsiveDrawer = () => { /> -
From b22a57297307881cc581594576f165ea2329b81b Mon Sep 17 00:00:00 2001 From: "steffen.schlabbach" Date: Sat, 3 Jan 2026 11:49:05 +0100 Subject: [PATCH 4/5] fix: :bug: correct drag gestures for left and top directions - Adjusted `onDrag` transform calculation to offset from 100% for left/top directions, ensuring correct relative movement. - Updated `resetDrawer` to reset to 100% translation for left/top directions so the drawer stays visible in the open state. - Corrected `swipeToClose` calculation in `onRelease` to account for the inverted coordinate system of left/top drawers (calculating distance from the open 100%fix(core): correct drag gestures for left and top directions - Adjusted `onDrag` transform calculation to offset from 100% for left/top directions, ensuring correct relative movement. - Updated `resetDrawer` to reset to 100% translation for left/top directions so the drawer stays visible in the open state. - Corrected `swipeToClose` calculation in `onRelease` to account for the inverted coordinate system of left/top drawers (calculating distance from the open 100% position). --- packages/core/src/index.tsx | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index f24e464..2bd551d 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -468,8 +468,13 @@ export function Root({ const translateValue = Math.min(dampenedDraggedDistance * -1, 0) * directionMultiplier - const x = isVertical(direction) ? "50%" : `${translateValue}px` - const y = isVertical(direction) ? `${translateValue}px` : "50%" + const transformValue = + direction === "left" || direction === "top" + ? `calc(100% + ${translateValue}px)` + : `${translateValue}px` + + const x = isVertical(direction) ? "50%" : transformValue + const y = isVertical(direction) ? transformValue : "50%" set(drawerRef.current, { transform: `translate3d(${x}, ${y}, 0)`, @@ -521,8 +526,13 @@ export function Root({ if (!snapPoints) { const translateValue = absDraggedDistance * directionMultiplier - const x = isVertical(direction) ? "50%" : `${translateValue}px` - const y = isVertical(direction) ? `${translateValue}px` : "50%" + const transformValue = + direction === "left" || direction === "top" + ? `calc(100% + ${translateValue}px)` + : `${translateValue}px` + + const x = isVertical(direction) ? "50%" : transformValue + const y = isVertical(direction) ? transformValue : "50%" set(drawerRef.current, { transform: `translate3d(${x}, ${y}, 0)`, @@ -630,8 +640,11 @@ export function Root({ const wrapper = document.querySelector("[data-vaul-drawer-wrapper]") const currentSwipeAmount = getTranslate(drawerRef.current, direction) - const x = isVertical(direction) ? "50%" : "0" - const y = isVertical(direction) ? "0" : "50%" + const transformValue = + direction === "left" || direction === "top" ? "100%" : "0px" + + const x = isVertical(direction) ? "50%" : transformValue + const y = isVertical(direction) ? transformValue : "50%" set(drawerRef.current, { transform: `translate3d(${x}, ${y}, 0)`, @@ -756,8 +769,14 @@ export function Root({ ) const isHorizontalSwipe = direction === "left" || direction === "right" + const swipeToClose = + direction === "left" || direction === "top" + ? (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) - + swipeAmount + : swipeAmount + if ( - Math.abs(swipeAmount) >= + Math.abs(swipeToClose) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold ) { From d967d7d0af33fc8f8b409951f3a6c2ad0e05f0b1 Mon Sep 17 00:00:00 2001 From: "steffen.schlabbach" Date: Sat, 3 Jan 2026 15:26:39 +0100 Subject: [PATCH 5/5] fix: refactor transform logic and fix drag bahavior and animations --- packages/core/src/index.tsx | 43 ++++----- packages/core/src/style.css | 150 +++++++++++++++++------------ packages/core/src/utils/helpers.ts | 13 +++ 3 files changed, 122 insertions(+), 84 deletions(-) diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx index 2bd551d..eaec2d1 100644 --- a/packages/core/src/index.tsx +++ b/packages/core/src/index.tsx @@ -13,6 +13,7 @@ import { useSnapPoints } from "@/hooks/use-snap-points" import { isIOS, isMobileFirefox } from "@/utils/browser" import { dampenValue, + getTransform, getTranslate, isVertical, reset, @@ -333,7 +334,11 @@ export function Root({ return false } - if (direction === "right" || direction === "left") { + if ( + direction === "right" || + direction === "left" || + direction === "top" + ) { return true } @@ -477,7 +482,7 @@ export function Root({ const y = isVertical(direction) ? transformValue : "50%" set(drawerRef.current, { - transform: `translate3d(${x}, ${y}, 0)`, + transform: getTransform(direction, translateValue), }) return } @@ -535,7 +540,7 @@ export function Root({ const y = isVertical(direction) ? transformValue : "50%" set(drawerRef.current, { - transform: `translate3d(${x}, ${y}, 0)`, + transform: getTransform(direction, translateValue), }) } } @@ -815,27 +820,19 @@ export function Root({ window.clearTimeout(nestedOpenChangeTimer.current) } - const x = isVertical(direction) ? "50%" : `${initialTranslate}px` - const y = isVertical(direction) ? `${initialTranslate}px` : "50%" - set(drawerRef.current, { transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`, - transform: `scale(${scale}) translate3d(${x}, ${y}, 0)`, + transform: `${getTransform(direction, initialTranslate)} scale(${scale})`, }) if (!o && drawerRef.current) { nestedOpenChangeTimer.current = setTimeout(() => { - const translateValue = getTranslate( - drawerRef.current as HTMLElement, - direction - ) - - const x = isVertical(direction) ? "50%" : `${translateValue}px` - const y = isVertical(direction) ? `${translateValue}px` : "50%" + const translateValue = + getTranslate(drawerRef.current as HTMLElement, direction) || 0 set(drawerRef.current, { transition: "none", - transform: `translate3d(${x}, ${y}, 0)`, + transform: getTransform(direction, translateValue), }) }, 500) } @@ -854,11 +851,8 @@ export function Root({ const newTranslate = -NESTED_DISPLACEMENT + percentageDragged * NESTED_DISPLACEMENT - const x = isVertical(direction) ? "50%" : `${newTranslate}px` - const y = isVertical(direction) ? `${newTranslate}px` : "50%" - set(drawerRef.current, { - transform: `scale(${newScale}) translate3d(${x}, ${y}, 0)`, + transform: `${getTransform(direction, newTranslate)} scale(${newScale})`, transition: "none", }) } @@ -873,12 +867,9 @@ export function Root({ const translate = o ? -NESTED_DISPLACEMENT : 0 if (o) { - const x = isVertical(direction) ? "50%" : `${translate}px` - const y = isVertical(direction) ? `${translate}px` : "50%" - set(drawerRef.current, { transition: `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(",")})`, - transform: `scale(${scale}) translate3d(${x}, ${y}, 0)`, + transform: `${getTransform(direction, translate)} scale(${scale})`, }) } } @@ -1088,8 +1079,10 @@ export const Content = React.forwardRef(function ( data-vaul-drawer-direction={direction} data-vaul-drawer="" data-vaul-drawer-morphing={isMorphing.current ? "true" : "false"} - data-vaul-delayed-snap-points={delayedSnapPoints ? "true" : "false"} - data-vaul-snap-points={isOpen && hasSnapPoints ? "true" : "false"} + data-vaul-delayed-snap-points={ + isOpen && delayedSnapPoints ? "true" : "false" + } + data-vaul-snap-points={hasSnapPoints ? "true" : "false"} data-vaul-custom-container={container ? "true" : "false"} data-vaul-animate={shouldAnimate?.current ? "true" : "false"} {...rest} diff --git a/packages/core/src/style.css b/packages/core/src/style.css index 72a2fe1..6f4223e 100644 --- a/packages/core/src/style.css +++ b/packages/core/src/style.css @@ -18,6 +18,10 @@ margin-right: auto; } +[data-vaul-drawer][data-vaul-snap-points="true"] { + --x-transform: 0; +} + [data-vaul-drawer][data-vaul-drawer-morphing="true"] { animation: none !important; } @@ -26,10 +30,10 @@ transform: translate3d(50%, 0, 0); animation-name: slideFromBottom; } + [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="bottom"]:not( [data-open] ) { - transform: translate3d(50%, 100%, 0); animation-name: slideToBottom; } @@ -37,10 +41,10 @@ transform: translate3d(50%, 100%, 0); animation-name: slideFromTop; } + [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="top"]:not( [data-open] ) { - transform: translate3d(50%, 0, 0); animation-name: slideToTop; } @@ -51,7 +55,6 @@ [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="left"]:not( [data-open] ) { - transform: translate3d(0, 50%, 0); animation-name: slideToLeft; } @@ -59,13 +62,43 @@ transform: translate3d(0, 50%, 0); animation-name: slideFromRight; } + [data-vaul-drawer][data-vaul-snap-points="false"][data-vaul-drawer-direction="right"]:not( [data-open] ) { - transform: translate3d(100%, 50%, 0); animation-name: slideToRight; } +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="bottom"]:not( + [data-open] + ) { + animation-name: slideToBottom; +} + +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="top"]:not( + [data-open] + ) { + animation-name: slideToTop; +} + +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="left"]:not( + [data-open] + ) { + animation-name: slideToLeft; +} + +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="right"]:not( + [data-open] + ) { + animation-name: slideToRight; +} + +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="bottom"], +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="top"] { + right: 0; + left: 0; +} + [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="bottom"] { transform: translate3d(0, var(--initial-transform, 100%), 0); } @@ -74,37 +107,50 @@ transform: translate3d(0, calc(var(--initial-transform, 100%) * -1), 0); } +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="left"], +[data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="right"] { + top: 0; + bottom: 0; +} + [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="left"] { transform: translate3d(calc(var(--initial-transform, 100%) * -1), 0, 0); + left: 0; + right: auto; } [data-vaul-drawer][data-vaul-snap-points="true"][data-vaul-drawer-direction="right"] { transform: translate3d(var(--initial-transform, 100%), 0, 0); } -[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="top"] { - transform: translate3d(0, var(--snap-point-height, 0), 0); -} - +[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="top"], [data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="bottom"] { transform: translate3d(0, var(--snap-point-height, 0), 0); + right: 0; + left: 0; } -[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="left"] { +[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="left"], +[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="right"] { transform: translate3d(var(--snap-point-height, 0), 0, 0); + top: 0; + bottom: 0; } -[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="right"] { - transform: translate3d(var(--snap-point-height, 0), 0, 0); +[data-vaul-drawer][data-vaul-delayed-snap-points="true"][data-vaul-drawer-direction="left"] { + left: 0; + right: auto; } [data-vaul-overlay][data-vaul-snap-points="false"] { animation-duration: 0.5s; animation-timing-function: cubic-bezier(0.32, 0.72, 0, 1); } + [data-vaul-overlay][data-vaul-snap-points="false"][data-open] { animation-name: fadeIn; } + [data-vaul-overlay]:not([data-open]) { animation-name: fadeOut; } @@ -113,13 +159,9 @@ animation: none !important; } -[data-vaul-overlay][data-vaul-snap-points="true"] { - opacity: 0; - transition: opacity 0.5s cubic-bezier(0.32, 0.72, 0, 1); -} - [data-vaul-overlay][data-vaul-snap-points="true"] { opacity: 1; + transition: opacity 0.5s cubic-bezier(0.32, 0.72, 0, 1); } [data-vaul-drawer]:not([data-vaul-custom-container="true"])::after { @@ -129,36 +171,38 @@ background-color: inherit; } -[data-vaul-drawer][data-vaul-drawer-direction="top"]::after { - top: initial; - bottom: 100%; +[data-vaul-drawer][data-vaul-drawer-direction="top"]::after, +[data-vaul-drawer][data-vaul-drawer-direction="bottom"]::after { left: 0; right: 0; height: 200%; } +[data-vaul-drawer][data-vaul-drawer-direction="top"]::after { + top: initial; + bottom: 100%; +} + [data-vaul-drawer][data-vaul-drawer-direction="bottom"]::after { top: 100%; bottom: initial; - left: 0; - right: 0; - height: 200%; } -[data-vaul-drawer][data-vaul-drawer-direction="left"]::after { - left: initial; - right: 100%; +[data-vaul-drawer][data-vaul-drawer-direction="left"]::after, +[data-vaul-drawer][data-vaul-drawer-direction="right"]::after { top: 0; bottom: 0; width: 200%; } +[data-vaul-drawer][data-vaul-drawer-direction="left"]::after { + left: initial; + right: 100%; +} + [data-vaul-drawer][data-vaul-drawer-direction="right"]::after { left: 100%; right: initial; - top: 0; - bottom: 0; - width: 200%; } [data-vaul-overlay][data-vaul-snap-points="true"]:not( @@ -227,37 +271,35 @@ } } -@keyframes slideFromBottom { - from { - transform: translate3d(0, var(--initial-transform, 100%), 0); - } - to { - transform: translate3d(0, 0, 0); - } -} - -@keyframes slideToBottom { - to { - transform: translate3d(0, var(--initial-transform, 100%), 0); - } -} - [data-vaul-drawer][data-vaul-drawer-direction="center"][data-open] { opacity: 1; transform: translate3d(50%, 50%, 0) scale(1); animation-name: none; } + [data-vaul-drawer][data-vaul-drawer-direction="center"]:not([data-open]) { opacity: 0; transform: translate3d(50%, 50%, 0) scale(0.95); animation-name: none; } -[data-vaul-drawer][data-vaul-drawer-direction="center"] { - bottom: 50%; - right: 50%; +[data-vaul-drawer][data-vaul-drawer-direction="center"], +[data-vaul-drawer][data-vaul-drawer-direction="bottom"], +[data-vaul-drawer][data-vaul-drawer-direction="top"], +[data-vaul-drawer][data-vaul-drawer-direction="left"], +[data-vaul-drawer][data-vaul-drawer-direction="right"] { left: auto; top: auto; +} + +[data-vaul-drawer][data-vaul-drawer-direction="center"], +[data-vaul-drawer][data-vaul-drawer-direction="bottom"], +[data-vaul-drawer][data-vaul-drawer-direction="top"] { + right: 50%; +} + +[data-vaul-drawer][data-vaul-drawer-direction="center"] { + bottom: 50%; transform: translate3d(50%, 50%, 0) scale(0.95); position: fixed; touch-action: auto; @@ -265,33 +307,23 @@ [data-vaul-drawer][data-vaul-drawer-direction="bottom"] { bottom: 0; - right: 50%; - left: auto; - top: auto; transform: translate3d(50%, 0, 0); } [data-vaul-drawer][data-vaul-drawer-direction="top"] { bottom: 100%; - right: 50%; - left: auto; - top: auto; transform: translate3d(50%, 100%, 0); } [data-vaul-drawer][data-vaul-drawer-direction="left"] { bottom: 50%; right: 100%; - left: auto; - top: auto; transform: translate3d(100%, 50%, 0); } [data-vaul-drawer][data-vaul-drawer-direction="right"] { bottom: 50%; right: 0; - left: auto; - top: auto; transform: translate3d(0, 50%, 0); } @@ -306,7 +338,7 @@ @keyframes slideToTop { to { - transform: translate3d(50%, 0, 0); + transform: translate3d(var(--x-transform, 50%), 0, 0); } } @@ -351,6 +383,6 @@ @keyframes slideToBottom { to { - transform: translate3d(50%, 100%, 0); + transform: translate3d(var(--x-transform, 50%), 100%, 0); } -} +} \ No newline at end of file diff --git a/packages/core/src/utils/helpers.ts b/packages/core/src/utils/helpers.ts index 7a8b75e..3da47f5 100644 --- a/packages/core/src/utils/helpers.ts +++ b/packages/core/src/utils/helpers.ts @@ -75,6 +75,19 @@ export const isVertical = (direction: DrawerDirection) => { } } +export function getTransform(direction: DrawerDirection, translateValue: number) { + let x = isVertical(direction) ? "50%" : `${translateValue}px` + let y = isVertical(direction) ? `${translateValue}px` : "50%" + + if (direction === "left") { + x = `calc(100% + ${translateValue}px)` + } else if (direction === "top") { + y = `calc(100% + ${translateValue}px)` + } + + return `translate3d(${x}, ${y}, 0)` +} + export function getTranslate( element: HTMLElement, direction: DrawerDirection