Uplofile is open sourceStar on GitHub

Pause/Resume (Custom Resumable Adapter)

A custom pause and resume flow built with useUplofile and a resumable upload adapter.

Preview

This demo treats canceled as "paused". Resume uses retry from the last checkpoint, while Retry clears the checkpoint and restarts from 0%.

Add files to test pause and resume behavior.

Code

1import {
2 UplofileRoot,
3 UplofileTrigger,
4 UplofilePreview,
5 UplofileRemove,
6 type UploadFileItem,
7} from "@/components/ui/uplofile";
8import { useUplofile } from "uplofile";
9import {
10 clearMockResumableCheckpoint,
11 mockResumableUpload,
12} from "@/lib/utils.ts";
13import {
14 IoCloudUploadOutline,
15 IoPauseOutline,
16 IoPlayOutline,
17 IoRefreshOutline,
18 IoCloseOutline,
19 IoTrashOutline,
20 IoReloadOutline,
21 IoCheckmarkCircleOutline,
22 IoWarningOutline,
23} from "react-icons/io5";
24
25const clearCheckpointForItem = (item: UploadFileItem) => {
26 if (!item.file) return;
27 clearMockResumableCheckpoint(item.file);
28};
29
30export default function PauseResumeResumableDemo() {
31 return (
32 <UplofileRoot upload={mockResumableUpload} multiple accept="*/*">
33 <div className="space-y-4">
34 <div className="flex items-center justify-between gap-3">
35 <UplofileTrigger asChild>
36 <button className="inline-flex items-center gap-2 rounded-lg bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition-all hover:bg-primary/90 active:scale-95">
37 <IoCloudUploadOutline className="h-4 w-4" />
38 Add Files
39 </button>
40 </UplofileTrigger>
41
42 <PauseResumeToolbar />
43 </div>
44
45 <p className="text-xs text-muted-foreground">
46 This demo treats <code className="code-inline">canceled</code> as
47 "paused". Resume uses <code className="code-inline">retry</code> from
48 the last checkpoint, while Retry clears the checkpoint and restarts
49 from 0%.
50 </p>
51
52 <UplofilePreview
53 render={({ items }) => (
54 <div className="divide-y rounded-xl border border-border bg-card shadow-sm">
55 {items.length === 0 && (
56 <div className="p-8 text-center text-sm text-muted-foreground">
57 Add files to test pause and resume behavior.
58 </div>
59 )}
60
61 {items.map((item) => (
62 <FileRow key={item.uid} item={item} />
63 ))}
64 </div>
65 )}
66 />
67 </div>
68 </UplofileRoot>
69 );
70}
71
72function PauseResumeToolbar() {
73 const { items, actions } = useUplofile();
74 const uploading = items.filter((item) => item.status === "uploading").length;
75 const paused = items.filter((item) => item.status === "canceled").length;
76
77 if (uploading === 0 && paused === 0) return null;
78
79 return (
80 <div className="flex items-center gap-2 text-xs">
81 {uploading > 0 && (
82 <button
83 onClick={() => {
84 items
85 .filter((item) => item.status === "uploading")
86 .forEach((item) => actions.cancel(item.uid));
87 }}
88 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 font-medium hover:bg-muted"
89 >
90 <IoPauseOutline className="h-3 w-3" />
91 Pause all ({uploading})
92 </button>
93 )}
94
95 {paused > 0 && (
96 <button
97 onClick={() => {
98 items
99 .filter((item) => item.status === "canceled")
100 .forEach((item) => actions.retry(item.uid));
101 }}
102 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 font-medium hover:bg-muted"
103 >
104 <IoPlayOutline className="h-3 w-3" />
105 Resume all ({paused})
106 </button>
107 )}
108 </div>
109 );
110}
111
112function FileRow({ item }: { item: UploadFileItem }) {
113 return (
114 <div className="flex items-center gap-3 p-3">
115 <div className="min-w-0 flex-1">
116 <div className="mb-1 flex items-center justify-between gap-2">
117 <p className="truncate text-sm font-medium">{item.name}</p>
118 <StatusBadge item={item} />
119 </div>
120
121 <div className="h-1.5 overflow-hidden rounded-full bg-secondary">
122 <div
123 className="h-full bg-primary transition-all duration-300"
124 style={{ width: `${item.progress ?? 0}%` }}
125 />
126 </div>
127 </div>
128
129 <ActionButtons item={item} />
130
131 <UplofileRemove
132 uid={item.uid}
133 className="rounded-md p-2 text-muted-foreground transition-colors hover:bg-muted hover:text-destructive"
134 >
135 <IoTrashOutline className="h-4 w-4" />
136 </UplofileRemove>
137 </div>
138 );
139}
140
141function ActionButtons({ item }: { item: UploadFileItem }) {
142 const { actions } = useUplofile();
143
144 if (item.status === "uploading") {
145 return (
146 <div className="flex items-center gap-1">
147 <button
148 onClick={() => actions.cancel(item.uid)}
149 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium hover:bg-muted"
150 >
151 <IoPauseOutline className="h-3 w-3" />
152 Pause
153 </button>
154 <button
155 onClick={() => {
156 clearCheckpointForItem(item);
157 actions.cancel(item.uid);
158 actions.remove(item.uid);
159 }}
160 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium hover:bg-muted"
161 >
162 <IoCloseOutline className="h-3 w-3" />
163 Cancel
164 </button>
165 </div>
166 );
167 }
168
169 if (item.status === "canceled") {
170 return (
171 <div className="flex items-center gap-1">
172 <button
173 onClick={() => actions.retry(item.uid)}
174 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium hover:bg-muted"
175 >
176 <IoPlayOutline className="h-3 w-3" />
177 Resume
178 </button>
179 <button
180 onClick={() => {
181 clearCheckpointForItem(item);
182 actions.retry(item.uid);
183 }}
184 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium hover:bg-muted"
185 >
186 <IoRefreshOutline className="h-3 w-3" />
187 Retry
188 </button>
189 </div>
190 );
191 }
192
193 if (item.status === "error") {
194 return (
195 <button
196 onClick={() => {
197 clearCheckpointForItem(item);
198 actions.retry(item.uid);
199 }}
200 className="inline-flex items-center gap-1 rounded-md border px-2 py-1 text-xs font-medium hover:bg-muted"
201 >
202 <IoRefreshOutline className="h-3 w-3" />
203 Retry
204 </button>
205 );
206 }
207
208 return null;
209}
210
211function StatusBadge({ item }: { item: UploadFileItem }) {
212 if (item.status === "uploading") {
213 return (
214 <span className="inline-flex items-center gap-1 text-xs text-muted-foreground">
215 <IoReloadOutline className="h-3 w-3 animate-spin" />
216 {item.progress ?? 0}%
217 </span>
218 );
219 }
220
221 if (item.status === "done") {
222 return (
223 <span className="inline-flex items-center gap-1 text-xs text-emerald-600">
224 <IoCheckmarkCircleOutline className="h-3 w-3" />
225 done
226 </span>
227 );
228 }
229
230 if (item.status === "error") {
231 return (
232 <span className="inline-flex items-center gap-1 text-xs text-destructive">
233 <IoWarningOutline className="h-3 w-3" />
234 error
235 </span>
236 );
237 }
238
239 if (item.status === "canceled") {
240 return <span className="text-xs text-amber-600">paused</span>;
241 }
242
243 return <span className="text-xs text-muted-foreground">{item.status}</span>;
244}

Key Points

  • Uses useUplofile for custom controls instead of adding package-level actions
  • Maps pause to actions.cancel and resume to actions.retry
  • Resumable behavior comes from the upload adapter (this demo stores checkpoints per file fingerprint)
  • For production, replace the adapter with a resumable client liketus-js-client