@eazip/react
Headless usage
Task state without the tray — build your own UI on useEazip().
<EazipTray /> is optional — everything it renders is derived from
useEazip()'s task, so any part of it (or all of it) is easy to
reimplement in your own design system.
import { useEazip } from '@eazip/react';
function ExportButton({ files }: { files: File[] }) {
const zip = useEazip();
const task = zip.task;
if (!task) {
return (
<button disabled={zip.isBusy} onClick={() => zip.download({ files })}>
Download as ZIP
</button>
);
}
return (
<div>
{task.state === 'processing' ? (
<>
{task.progress ? (
<progress value={task.progress.filesCompleted} max={task.progress.filesTotal} />
) : (
<progress />
)}
<button onClick={() => zip.cancel()}>Cancel</button>
</>
) : null}
{task.state === 'completed' || task.state === 'partial' ? (
<button onClick={() => zip.downloadAll()}>
{task.zips.length > 1 ? `Download ${task.zips.length} zips` : 'Save file'}
</button>
) : null}
{task.state === 'failed' && task.canRetry ? (
<button onClick={() => zip.retry()}>Try again</button>
) : null}
</div>
);
}A few things worth knowing before you build on this:
task.progressis only ever populated forlocaljobs — cloud jobs poll a session, not a byte stream, so render an indeterminate spinner fortask.strategy === 'cloud'instead of a percentage.task.zips[]carriesfilename/size/downloadUrlper part; usedownloadZip(task.id, index)to trigger one specific part instead ofdownloadAll().task.skipped(populated oncestateis'partial') is the per-file reason list — see Partial results & errors for whatreasonmeans onlocalversuscloud, where only a count is available.task.canRetryreflects whether the original request is still replayable — see Persistence & reload-resume for the one case (a restored, backend-created session) where it isn't.
isBusy is just task?.state === 'processing' — reach for it when all you
need is a boolean, and task directly for anything richer.