@@ -9,6 +9,7 @@ import { toast, ToastState } from './state';
99import './styles.css' ;
1010import {
1111 isAction ,
12+ SwipeDirection ,
1213 type ExternalToast ,
1314 type HeightT ,
1415 type ToasterProps ,
@@ -45,6 +46,21 @@ function cn(...classes: (string | undefined)[]) {
4546 return classes . filter ( Boolean ) . join ( ' ' ) ;
4647}
4748
49+ function getDefaultSwipeDirections ( position : string ) : Array < SwipeDirection > {
50+ const [ y , x ] = position . split ( '-' ) ;
51+ const directions : Array < SwipeDirection > = [ ] ;
52+
53+ if ( y ) {
54+ directions . push ( y as SwipeDirection ) ;
55+ }
56+
57+ if ( x ) {
58+ directions . push ( x as SwipeDirection ) ;
59+ }
60+
61+ return directions ;
62+ }
63+
4864const Toast = ( props : ToastProps ) => {
4965 const {
5066 invert : ToasterInvert ,
@@ -75,6 +91,8 @@ const Toast = (props: ToastProps) => {
7591 closeButtonAriaLabel = 'Close toast' ,
7692 pauseWhenPageIsHidden,
7793 } = props ;
94+ const [ swipeDirection , setSwipeDirection ] = React . useState < 'x' | 'y' | null > ( null ) ;
95+ const [ swipeOutDirection , setSwipeOutDirection ] = React . useState < 'left' | 'right' | 'up' | 'down' | null > ( null ) ;
7896 const [ mounted , setMounted ] = React . useState ( false ) ;
7997 const [ removed , setRemoved ] = React . useState ( false ) ;
8098 const [ swiping , setSwiping ] = React . useState ( false ) ;
@@ -278,6 +296,7 @@ const Toast = (props: ToastProps) => {
278296 data-type = { toastType }
279297 data-invert = { invert }
280298 data-swipe-out = { swipeOut }
299+ data-swipe-direction = { swipeOutDirection }
281300 data-expanded = { Boolean ( expanded || ( expandByDefault && mounted ) ) }
282301 style = {
283302 {
@@ -304,37 +323,82 @@ const Toast = (props: ToastProps) => {
304323 if ( swipeOut || ! dismissible ) return ;
305324
306325 pointerStartRef . current = null ;
307- const swipeAmount = Number ( toastRef . current ?. style . getPropertyValue ( '--swipe-amount' ) . replace ( 'px' , '' ) || 0 ) ;
326+ const swipeAmountX = Number (
327+ toastRef . current ?. style . getPropertyValue ( '--swipe-amount-x' ) . replace ( 'px' , '' ) || 0 ,
328+ ) ;
329+ const swipeAmountY = Number (
330+ toastRef . current ?. style . getPropertyValue ( '--swipe-amount-y' ) . replace ( 'px' , '' ) || 0 ,
331+ ) ;
308332 const timeTaken = new Date ( ) . getTime ( ) - dragStartTime . current ?. getTime ( ) ;
333+
334+ const swipeAmount = swipeDirection === 'x' ? swipeAmountX : swipeAmountY ;
309335 const velocity = Math . abs ( swipeAmount ) / timeTaken ;
310336
311- // Remove only if threshold is met
312337 if ( Math . abs ( swipeAmount ) >= SWIPE_THRESHOLD || velocity > 0.11 ) {
313338 setOffsetBeforeRemove ( offset . current ) ;
314339 toast . onDismiss ?.( toast ) ;
340+
341+ if ( swipeDirection === 'x' ) {
342+ setSwipeOutDirection ( swipeAmountX > 0 ? 'right' : 'left' ) ;
343+ } else {
344+ setSwipeOutDirection ( swipeAmountY > 0 ? 'down' : 'up' ) ;
345+ }
346+
315347 deleteToast ( ) ;
316348 setSwipeOut ( true ) ;
317349 setIsSwiped ( false ) ;
318350 return ;
319351 }
320352
321- toastRef . current ?. style . setProperty ( '--swipe-amount' , '0px' ) ;
322353 setSwiping ( false ) ;
354+ setSwipeDirection ( null ) ;
323355 } }
324356 onPointerMove = { ( event ) => {
325357 if ( ! pointerStartRef . current || ! dismissible ) return ;
326358
327- const yPosition = event . clientY - pointerStartRef . current . y ;
328359 const isHighlighted = window . getSelection ( ) ?. toString ( ) . length > 0 ;
329- const swipeAmount = y === 'top' ? Math . min ( 0 , yPosition ) : Math . max ( 0 , yPosition ) ;
360+ if ( isHighlighted ) return ;
330361
331- if ( Math . abs ( swipeAmount ) > 0 ) {
332- setIsSwiped ( true ) ;
362+ const yDelta = event . clientY - pointerStartRef . current . y ;
363+ const xDelta = event . clientX - pointerStartRef . current . x ;
364+
365+ const swipeDirections = props . swipeDirections ?? getDefaultSwipeDirections ( position ) ;
366+
367+ // Determine swipe direction if not already locked
368+ if ( ! swipeDirection && ( Math . abs ( xDelta ) > 1 || Math . abs ( yDelta ) > 1 ) ) {
369+ setSwipeDirection ( Math . abs ( xDelta ) > Math . abs ( yDelta ) ? 'x' : 'y' ) ;
333370 }
334371
335- if ( isHighlighted ) return ;
372+ let swipeAmount = { x : 0 , y : 0 } ;
373+
374+ // Only apply swipe in the locked direction
375+ if ( swipeDirection === 'y' ) {
376+ // Handle vertical swipes
377+ if ( swipeDirections . includes ( 'top' ) || swipeDirections . includes ( 'bottom' ) ) {
378+ if ( swipeDirections . includes ( 'top' ) && yDelta < 0 ) {
379+ swipeAmount . y = yDelta ;
380+ } else if ( swipeDirections . includes ( 'bottom' ) && yDelta > 0 ) {
381+ swipeAmount . y = yDelta ;
382+ }
383+ }
384+ } else if ( swipeDirection === 'x' ) {
385+ // Handle horizontal swipes
386+ if ( swipeDirections . includes ( 'left' ) || swipeDirections . includes ( 'right' ) ) {
387+ if ( swipeDirections . includes ( 'left' ) && xDelta < 0 ) {
388+ swipeAmount . x = xDelta ;
389+ } else if ( swipeDirections . includes ( 'right' ) && xDelta > 0 ) {
390+ swipeAmount . x = xDelta ;
391+ }
392+ }
393+ }
394+
395+ if ( Math . abs ( swipeAmount . x ) > 0 || Math . abs ( swipeAmount . y ) > 0 ) {
396+ setIsSwiped ( true ) ;
397+ }
336398
337- toastRef . current ?. style . setProperty ( '--swipe-amount' , `${ swipeAmount } px` ) ;
399+ // Apply transform using both x and y values
400+ toastRef . current ?. style . setProperty ( '--swipe-amount-x' , `${ swipeAmount . x } px` ) ;
401+ toastRef . current ?. style . setProperty ( '--swipe-amount-y' , `${ swipeAmount . y } px` ) ;
338402 } }
339403 >
340404 { closeButton && ! toast . jsx ? (
@@ -783,6 +847,7 @@ const Toaster = forwardRef<HTMLElement, ToasterProps>(function Toaster(props, re
783847 loadingIcon = { loadingIcon }
784848 expanded = { expanded }
785849 pauseWhenPageIsHidden = { pauseWhenPageIsHidden }
850+ swipeDirections = { props . swipeDirections }
786851 />
787852 ) ) }
788853 </ ol >
0 commit comments