Skip to content

Commit 7e84efb

Browse files
committed
add swipe + drag to dismiss 👋🏼
1 parent e6c5809 commit 7e84efb

3 files changed

Lines changed: 57 additions & 1 deletion

File tree

src/sileo.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
position: relative;
5656
cursor: pointer;
5757
pointer-events: auto;
58+
touch-action: none;
5859
border: 0;
5960
background: transparent;
6061
padding: 0;

src/sileo.tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ interface SileoProps {
6666
refreshKey?: string;
6767
onMouseEnter?: MouseEventHandler<HTMLButtonElement>;
6868
onMouseLeave?: MouseEventHandler<HTMLButtonElement>;
69+
onDismiss?: () => void;
6970
}
7071

7172
/* ---------------------------------- Icons --------------------------------- */
@@ -133,6 +134,7 @@ export const Sileo = memo(function Sileo({
133134
refreshKey,
134135
onMouseEnter,
135136
onMouseLeave,
137+
onDismiss,
136138
}: SileoProps) {
137139
const next: View = useMemo(
138140
() => ({ title, description, state, icon, styles, button, fill }),
@@ -428,10 +430,59 @@ export const Sileo = memo(function Sileo({
428430
[open],
429431
);
430432

433+
/* -------------------------------- Swipe ----------------------------------- */
434+
435+
const SWIPE_DISMISS = 30;
436+
const SWIPE_MAX = 20;
437+
const buttonRef = useRef<HTMLButtonElement>(null);
438+
const pointerStartRef = useRef<number | null>(null);
439+
const onDismissRef = useRef(onDismiss);
440+
onDismissRef.current = onDismiss;
441+
442+
useEffect(() => {
443+
const el = buttonRef.current;
444+
if (!el) return;
445+
446+
const onMove = (e: PointerEvent) => {
447+
if (pointerStartRef.current === null) return;
448+
const dy = e.clientY - pointerStartRef.current;
449+
const sign = dy > 0 ? 1 : -1;
450+
const clamped = Math.min(Math.abs(dy), SWIPE_MAX) * sign;
451+
el.style.transform = `translateY(${clamped}px)`;
452+
};
453+
454+
const onUp = (e: PointerEvent) => {
455+
if (pointerStartRef.current === null) return;
456+
const dy = e.clientY - pointerStartRef.current;
457+
pointerStartRef.current = null;
458+
el.style.transform = "";
459+
if (Math.abs(dy) > SWIPE_DISMISS) {
460+
onDismissRef.current?.();
461+
}
462+
};
463+
464+
el.addEventListener("pointermove", onMove);
465+
el.addEventListener("pointerup", onUp);
466+
return () => {
467+
el.removeEventListener("pointermove", onMove);
468+
el.removeEventListener("pointerup", onUp);
469+
};
470+
}, []);
471+
472+
const handlePointerDown = useCallback(
473+
(e: React.PointerEvent<HTMLButtonElement>) => {
474+
if (exiting || !onDismiss) return;
475+
pointerStartRef.current = e.clientY;
476+
e.currentTarget.setPointerCapture(e.pointerId);
477+
},
478+
[exiting, onDismiss],
479+
);
480+
431481
/* --------------------------------- Render --------------------------------- */
432482

433483
return (
434484
<button
485+
ref={buttonRef}
435486
type="button"
436487
data-sileo-toast
437488
data-ready={ready}
@@ -445,6 +496,7 @@ export const Sileo = memo(function Sileo({
445496
onMouseEnter={handleEnter}
446497
onMouseLeave={handleLeave}
447498
onTransitionEnd={handleTransitionEnd}
499+
onPointerDown={handlePointerDown}
448500
>
449501
<div data-sileo-canvas data-edge={expand}>
450502
<svg

src/toast.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ const createToast = (options: InternalSileoOptions) => {
144144
if (prev) {
145145
store.update((p) => p.map((t) => (t.id === id ? item : t)));
146146
} else {
147-
store.update((p) => [...p, item]);
147+
store.update((p) => [...p.filter((t) => t.id !== id), item]);
148148
}
149149
return { id, duration: merged.duration ?? DEFAULT_DURATION };
150150
};
@@ -236,6 +236,7 @@ export function Toaster({
236236
{
237237
enter: MouseEventHandler<HTMLButtonElement>;
238238
leave: MouseEventHandler<HTMLButtonElement>;
239+
dismiss: () => void;
239240
}
240241
>(),
241242
);
@@ -343,6 +344,7 @@ export function Toaster({
343344
);
344345
handleMouseLeaveRef.current?.(e);
345346
}) as MouseEventHandler<HTMLButtonElement>,
347+
dismiss: () => dismissToast(toastId),
346348
};
347349

348350
handlersCache.current.set(toastId, cached);
@@ -427,6 +429,7 @@ export function Toaster({
427429
canExpand={activeId === undefined || activeId === item.id}
428430
onMouseEnter={h.enter}
429431
onMouseLeave={h.leave}
432+
onDismiss={h.dismiss}
430433
/>
431434
);
432435
})}

0 commit comments

Comments
 (0)