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";2425const clearCheckpointForItem = (item: UploadFileItem) => {26 if (!item.file) return;27 clearMockResumableCheckpoint(item.file);28};2930export 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 Files39 </button>40 </UplofileTrigger>4142 <PauseResumeToolbar />43 </div>4445 <p className="text-xs text-muted-foreground">46 This demo treats <code className="code-inline">canceled</code> as47 "paused". Resume uses <code className="code-inline">retry</code> from48 the last checkpoint, while Retry clears the checkpoint and restarts49 from 0%.50 </p>5152 <UplofilePreview53 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 )}6061 {items.map((item) => (62 <FileRow key={item.uid} item={item} />63 ))}64 </div>65 )}66 />67 </div>68 </UplofileRoot>69 );70}7172function 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;7677 if (uploading === 0 && paused === 0) return null;7879 return (80 <div className="flex items-center gap-2 text-xs">81 {uploading > 0 && (82 <button83 onClick={() => {84 items85 .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 )}9495 {paused > 0 && (96 <button97 onClick={() => {98 items99 .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}111112function 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>120121 <div className="h-1.5 overflow-hidden rounded-full bg-secondary">122 <div123 className="h-full bg-primary transition-all duration-300"124 style={{ width: `${item.progress ?? 0}%` }}125 />126 </div>127 </div>128129 <ActionButtons item={item} />130131 <UplofileRemove132 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}140141function ActionButtons({ item }: { item: UploadFileItem }) {142 const { actions } = useUplofile();143144 if (item.status === "uploading") {145 return (146 <div className="flex items-center gap-1">147 <button148 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 Pause153 </button>154 <button155 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 Cancel164 </button>165 </div>166 );167 }168169 if (item.status === "canceled") {170 return (171 <div className="flex items-center gap-1">172 <button173 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 Resume178 </button>179 <button180 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 Retry188 </button>189 </div>190 );191 }192193 if (item.status === "error") {194 return (195 <button196 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 Retry204 </button>205 );206 }207208 return null;209}210211function 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 }220221 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 done226 </span>227 );228 }229230 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 error235 </span>236 );237 }238239 if (item.status === "canceled") {240 return <span className="text-xs text-amber-600">paused</span>;241 }242243 return <span className="text-xs text-muted-foreground">{item.status}</span>;244}
Key Points
- → Uses
useUplofilefor custom controls instead of adding package-level actions - → Maps pause to
actions.canceland resume toactions.retry - → Resumable behavior comes from the upload adapter (this demo stores checkpoints per file fingerprint)
- → For production, replace the adapter with a resumable client like
tus-js-client