@@ -58,6 +58,50 @@ function DialogContent({
5858 let closeRef = React . useRef < HTMLButtonElement > ( null )
5959 let dragY = useMotionValue ( 0 )
6060 let isBeyondThresholdRef = React . useRef ( false )
61+ let dragStartYRef = React . useRef < number | null > ( null )
62+ let resetDragOnMount = React . useCallback (
63+ ( el : HTMLDivElement | null ) => {
64+ if ( el ) dragY . set ( 0 )
65+ } ,
66+ [ dragY ] ,
67+ )
68+
69+ let onHandlePointerDown = ( event : React . PointerEvent < HTMLDivElement > ) => {
70+ if ( event . button !== 0 && event . pointerType === "mouse" ) return
71+ dragStartYRef . current = event . clientY
72+ event . currentTarget . setPointerCapture ( event . pointerId )
73+ dragY . stop ( )
74+ }
75+
76+ let onHandlePointerMove = ( event : React . PointerEvent < HTMLDivElement > ) => {
77+ if ( dragStartYRef . current == null ) return
78+ let offset = Math . max ( 0 , event . clientY - dragStartYRef . current )
79+ dragY . set ( offset )
80+ let isBeyond = offset > SWIPE_CLOSE_THRESHOLD
81+ if ( isBeyond !== isBeyondThresholdRef . current ) {
82+ isBeyondThresholdRef . current = isBeyond
83+ triggerHaptic ( )
84+ }
85+ }
86+
87+ let onHandlePointerEnd = ( event : React . PointerEvent < HTMLDivElement > ) => {
88+ if ( dragStartYRef . current == null ) return
89+ let offset = Math . max ( 0 , event . clientY - dragStartYRef . current )
90+ dragStartYRef . current = null
91+ isBeyondThresholdRef . current = false
92+ if ( event . currentTarget . hasPointerCapture ( event . pointerId ) ) {
93+ event . currentTarget . releasePointerCapture ( event . pointerId )
94+ }
95+ if ( offset > SWIPE_CLOSE_THRESHOLD ) {
96+ animate ( dragY , window . innerHeight , {
97+ duration : 0.15 ,
98+ ease : "easeOut" ,
99+ onComplete : ( ) => closeRef . current ?. click ( ) ,
100+ } )
101+ } else {
102+ animate ( dragY , 0 , { type : "spring" , stiffness : 300 , damping : 25 } )
103+ }
104+ }
61105
62106 let contentClassName = cn (
63107 "bg-background ring-foreground/5 fixed z-50 flex max-h-[95dvh] flex-col gap-6 overflow-y-auto p-6 text-sm shadow-lg ring-1 outline-none will-change-transform max-md:duration-200 md:duration-100" ,
@@ -99,49 +143,23 @@ function DialogContent({
99143 data-slot = "dialog-content"
100144 render = {
101145 < motion . div
102- style = { { ...mobileStyle , y : dragY } }
146+ ref = { resetDragOnMount }
147+ style = { { ...mobileStyle , y : dragY , transition : "none" } }
103148 className = { contentClassName }
104149 />
105150 }
106151 { ...props }
107152 initialFocus = { false }
108153 >
109- < motion . div
110- drag = "y"
111- dragConstraints = { { top : 0 , bottom : 0 } }
112- dragElastic = { 0 }
113- style = { { x : 0 , y : 0 } }
114- onDrag = { ( _ , info ) => {
115- let offset = Math . max ( 0 , info . offset . y )
116- dragY . set ( offset )
117-
118- let isBeyond = offset > SWIPE_CLOSE_THRESHOLD
119- if ( isBeyond !== isBeyondThresholdRef . current ) {
120- isBeyondThresholdRef . current = isBeyond
121- triggerHaptic ( )
122- }
123- } }
124- onDragEnd = { ( _ , info ) => {
125- if ( info . offset . y > SWIPE_CLOSE_THRESHOLD ) {
126- isBeyondThresholdRef . current = false
127- animate ( dragY , window . innerHeight , {
128- duration : 0.15 ,
129- ease : "easeOut" ,
130- onComplete : ( ) => closeRef . current ?. click ( ) ,
131- } )
132- } else {
133- isBeyondThresholdRef . current = false
134- animate ( dragY , 0 , {
135- type : "spring" ,
136- stiffness : 300 ,
137- damping : 25 ,
138- } )
139- }
140- } }
154+ < div
155+ onPointerDown = { onHandlePointerDown }
156+ onPointerMove = { onHandlePointerMove }
157+ onPointerUp = { onHandlePointerEnd }
158+ onPointerCancel = { onHandlePointerEnd }
141159 className = "-mt-4 -mb-1 flex cursor-grab touch-none justify-center pt-1 pb-1.5 select-none active:cursor-grabbing pointer-fine:hidden"
142160 >
143161 < div className = "bg-muted-foreground/30 h-1.5 w-10 rounded-full" />
144- </ motion . div >
162+ </ div >
145163 { children }
146164 { closeButton }
147165 </ DialogPrimitive . Popup >
0 commit comments