Skip to content

Commit 2b8249f

Browse files
authored
feat(mobile): pull-to-refresh to pick up new deploys (#28) (#66)
Co-authored-by: gnacho <hnacho@proton.me>
1 parent a298f16 commit 2b8249f

2 files changed

Lines changed: 93 additions & 6 deletions

File tree

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { useEffect, useRef, useState } from 'react';
2+
import type { ReactNode } from 'react';
3+
import { RefreshCw } from 'lucide-react';
4+
import { cn } from '@/lib/utils';
5+
6+
const THRESHOLD = 70;
7+
8+
/**
9+
* Pull-to-refresh móvil: al tirar hacia abajo desde el top de la página, recarga
10+
* (permite coger un deploy nuevo sin cerrar y reabrir la app). Solo actúa cuando
11+
* window.scrollY <= 0 y el gesto es un pull vertical. Patrón portado de Keynest
12+
* (issue helios #28).
13+
*/
14+
export default function PullToRefresh({ children }: { children: ReactNode }) {
15+
const startY = useRef<number | null>(null);
16+
const pullRef = useRef(0);
17+
const [pull, setPull] = useState(0);
18+
const [refreshing, setRefreshing] = useState(false);
19+
20+
useEffect(() => {
21+
const onTouchStart = (e: TouchEvent) => {
22+
if (window.scrollY <= 0 && e.touches.length === 1) {
23+
startY.current = e.touches[0].clientY;
24+
} else {
25+
startY.current = null;
26+
}
27+
};
28+
const onTouchMove = (e: TouchEvent) => {
29+
if (startY.current === null) return;
30+
const dy = e.touches[0].clientY - startY.current;
31+
if (dy > 0) {
32+
const v = Math.min(dy * 0.5, 120);
33+
pullRef.current = v;
34+
setPull(v);
35+
} else {
36+
pullRef.current = 0;
37+
setPull(0);
38+
}
39+
};
40+
const onTouchEnd = () => {
41+
if (startY.current === null) return;
42+
startY.current = null;
43+
if (pullRef.current >= THRESHOLD) {
44+
setRefreshing(true);
45+
window.location.reload();
46+
return;
47+
}
48+
pullRef.current = 0;
49+
setPull(0);
50+
};
51+
window.addEventListener('touchstart', onTouchStart, { passive: true });
52+
window.addEventListener('touchmove', onTouchMove, { passive: true });
53+
window.addEventListener('touchend', onTouchEnd, { passive: true });
54+
return () => {
55+
window.removeEventListener('touchstart', onTouchStart);
56+
window.removeEventListener('touchmove', onTouchMove);
57+
window.removeEventListener('touchend', onTouchEnd);
58+
};
59+
}, []);
60+
61+
return (
62+
<div className="relative">
63+
<div
64+
className="pointer-events-none absolute inset-x-0 top-0 z-50 flex items-center justify-center overflow-hidden"
65+
style={{
66+
height: Math.max(pull, refreshing ? 28 : 0),
67+
transition: refreshing ? 'height 0.2s' : 'none',
68+
}}
69+
>
70+
<RefreshCw
71+
className={cn('h-5 w-5', (pull > 0 || refreshing) && 'animate-spin', 'text-muted')}
72+
/>
73+
</div>
74+
<div
75+
style={{
76+
transform: pull > 0 ? `translateY(${pull}px)` : undefined,
77+
transition: pull > 0 ? 'none' : 'transform 0.2s',
78+
}}
79+
>
80+
{children}
81+
</div>
82+
</div>
83+
);
84+
}

‎app/src/components/shell/AppLayout.tsx‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import UpdateRibbon from '@/components/UpdateRibbon';
2929
import ThemeToggle from '@/components/ThemeToggle';
3030
import Footer from '@/components/Footer';
3131
import HeliosToaster from '@/components/HeliosToaster';
32+
import PullToRefresh from '@/components/PullToRefresh';
3233
import { apiFetch } from '@/data/api-client';
3334

3435
/**
@@ -439,12 +440,14 @@ export default function AppLayout({ children }: { children: ReactNode }) {
439440
</header>
440441

441442
<main className={cn('mx-auto max-w-[1440px] px-4 pb-24 pt-4 md:ml-16 md:px-6 md:pb-6 md:pt-6', lgMargin)}>
442-
<UpdateRibbon />
443-
{isDemo && <DemoBanner onExit={exitDemo} />}
444-
{children}
445-
<div className="hidden lg:block">
446-
<Footer />
447-
</div>
443+
<PullToRefresh>
444+
<UpdateRibbon />
445+
{isDemo && <DemoBanner onExit={exitDemo} />}
446+
{children}
447+
<div className="hidden lg:block">
448+
<Footer />
449+
</div>
450+
</PullToRefresh>
448451
</main>
449452

450453
{/* Bottom nav < md */}

0 commit comments

Comments
 (0)