Skip to content

Commit fc8ae6d

Browse files
552
1 parent 38deaea commit fc8ae6d

1 file changed

Lines changed: 178 additions & 0 deletions

File tree

playground/app/originui/page.tsx

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,9 @@ export default function Page() {
7979
<GridItem>
8080
<Comp547 />
8181
</GridItem>
82+
<GridItem>
83+
<Comp552 />
84+
</GridItem>
8285
<GridItem>
8386
<Comp553 />
8487
</GridItem>
@@ -463,6 +466,181 @@ const getFilePreview = (file: AnyFile<AnyFileRoute> & { preview?: string }) => {
463466
);
464467
};
465468

469+
function Comp552() {
470+
const [isPending, startTransition] = React.useTransition();
471+
const {
472+
getRootProps,
473+
getInputProps,
474+
isDragActive,
475+
routeConfig,
476+
files,
477+
clearFiles,
478+
removeFile,
479+
setFiles,
480+
} = useUploadThingDropzone({
481+
route: routeRegistry.anyPublic,
482+
disabled: isPending,
483+
});
484+
485+
const uploadFiles = () => {
486+
startTransition(async () => {
487+
await future_uploadFiles((rr) => rr.anyPublic, {
488+
files: files.filter((f) => f.status === "pending"),
489+
input: {},
490+
onEvent: (event) => {
491+
setFiles((prev) =>
492+
prev.map((f) =>
493+
"file" in event && f === event.file ? event.file : f,
494+
),
495+
);
496+
},
497+
});
498+
});
499+
};
500+
501+
return (
502+
<div className="flex flex-col gap-2">
503+
{/* Drop area */}
504+
<div
505+
{...getRootProps()}
506+
data-dragging={isDragActive || undefined}
507+
data-files={files.length > 0 || undefined}
508+
className="border-input data-[dragging=true]:bg-accent/50 has-[input:focus]:border-ring has-[input:focus]:ring-ring/50 not-data-[files]:justify-center relative flex min-h-52 flex-col items-center overflow-hidden rounded-xl border border-dashed p-4 transition-colors has-[input:focus]:ring-[3px]"
509+
>
510+
{files.length > 0 ? (
511+
<div className="flex w-full flex-col gap-3">
512+
<div className="flex items-center justify-between gap-2">
513+
<h3 className="truncate text-sm font-medium">
514+
Files ({files.length})
515+
</h3>
516+
<div className="flex gap-2">
517+
<label
518+
className={twMerge(
519+
buttonVariants({
520+
variant: "outline",
521+
size: "sm",
522+
}),
523+
"size-8",
524+
)}
525+
>
526+
<input
527+
{...getInputProps()}
528+
className="sr-only"
529+
aria-label="Upload files"
530+
/>
531+
<PlusIcon
532+
className="size-3.5 opacity-60"
533+
aria-hidden="true"
534+
/>
535+
</label>
536+
<Button
537+
variant="outline"
538+
size="sm"
539+
onClick={(e) => {
540+
e.stopPropagation();
541+
clearFiles();
542+
}}
543+
>
544+
<Trash2Icon
545+
className="size-3.5 opacity-60"
546+
aria-hidden="true"
547+
/>
548+
</Button>
549+
<Button
550+
size="sm"
551+
disabled={
552+
isPending ||
553+
files.filter((f) => f.status === "pending").length === 0
554+
}
555+
onClick={(e) => {
556+
e.stopPropagation();
557+
uploadFiles();
558+
}}
559+
>
560+
Upload
561+
</Button>
562+
</div>
563+
</div>
564+
565+
<div className="grid grid-cols-2 gap-4 md:grid-cols-3">
566+
{files.map((file, index) => (
567+
<div
568+
key={file.key ?? index}
569+
className="bg-background relative flex flex-col rounded-md border"
570+
>
571+
{getFilePreview(file)}
572+
<div className="absolute -right-2 -top-2">
573+
{file.status === "uploaded" ? (
574+
<div className="border-background flex size-6 items-center justify-center rounded-full border-2 bg-green-500">
575+
<CheckIcon className="size-3.5 text-white" />
576+
</div>
577+
) : file.status === "uploading" ||
578+
(file.status === "pending" && file.key) ? (
579+
<div className="text-muted-foreground/80 bg-background flex size-6 items-center justify-center rounded-full">
580+
<Loader2Icon
581+
aria-hidden="true"
582+
className="size-3.5 animate-spin"
583+
/>
584+
</div>
585+
) : (
586+
<Button
587+
onClick={(e) => {
588+
e.stopPropagation();
589+
removeFile(file);
590+
}}
591+
size="icon"
592+
className="border-background focus-visible:border-background size-6 rounded-full border-2 shadow-none"
593+
aria-label="Remove image"
594+
>
595+
<XIcon className="size-3.5" />
596+
</Button>
597+
)}
598+
</div>
599+
<div className="flex min-w-0 flex-col gap-0.5 border-t p-3">
600+
<p className="truncate text-[13px] font-medium">
601+
{file.name}
602+
</p>
603+
<p className="text-muted-foreground truncate text-xs">
604+
{bytesToFileSize(file.size)}
605+
</p>
606+
</div>
607+
</div>
608+
))}
609+
</div>
610+
</div>
611+
) : (
612+
<div className="flex flex-col items-center justify-center px-4 py-3 text-center">
613+
<div
614+
className="bg-background mb-2 flex size-11 shrink-0 items-center justify-center rounded-full border"
615+
aria-hidden="true"
616+
>
617+
<ImageIcon className="size-4 opacity-60" />
618+
</div>
619+
<p className="mb-1.5 text-sm font-medium">Drop your files here</p>
620+
<p className="text-muted-foreground text-xs">
621+
{allowedContentTextLabelGenerator(routeConfig)}
622+
</p>
623+
<label
624+
className={buttonVariants({
625+
variant: "outline",
626+
className: "mt-4",
627+
})}
628+
>
629+
<input
630+
{...getInputProps()}
631+
className="sr-only"
632+
aria-label="Upload files"
633+
/>
634+
<UploadIcon className="-ms-1 opacity-60" aria-hidden="true" />
635+
Select files
636+
</label>
637+
</div>
638+
)}
639+
</div>
640+
</div>
641+
);
642+
}
643+
466644
function Comp553() {
467645
const [isPending, startTransition] = React.useTransition();
468646

0 commit comments

Comments
 (0)