feat: add viewing existingFiles to smartfileinput

This commit is contained in:
Nathnael
2026-07-03 06:22:32 +00:00
parent 1e408536aa
commit 633c7c52c5

View File

@@ -27,6 +27,16 @@ export interface SmartFileInputProps {
* no in-memory File is currently selected for them.
*/
uploadedKeys?: string[];
/**
* Metadata for already-uploaded files, keyed by fileKey. When a field has
* entries here, they're listed with view/download links (instead of the
* generic placeholder text) and the field counts as uploaded even if it's
* not also listed in `uploadedKeys`.
*/
existingFiles?: Record<
string,
{ name: string; url: string; size?: number }[]
>;
/** Disabled state for the entire file input group. */
disabled?: boolean;
/** Display variant style. Default is "default" (large dropzone). Minimal renders a compact upload button. */
@@ -76,6 +86,7 @@ export function SmartFileInput({
onChange,
errors,
uploadedKeys,
existingFiles,
disabled = false,
variant = "default",
className,
@@ -270,9 +281,11 @@ export function SmartFileInput({
const fieldError =
errors?.[field.fileKey] || localErrors[field.fileKey];
const isDragOver = dragActive[field.fileKey];
const existingForField = existingFiles?.[field.fileKey] ?? [];
// Already uploaded server-side and nothing newly picked to replace it.
const isUploaded =
(uploadedKeys?.includes(field.fileKey) ?? false) &&
((uploadedKeys?.includes(field.fileKey) ?? false) ||
existingForField.length > 0) &&
currentFiles.length === 0;
// Format accepted files for the HTML input element
@@ -417,6 +430,21 @@ export function SmartFileInput({
{field.allowedExtensions.join(", ").toUpperCase() ||
"All"}
</span>
{existingForField.length > 0 && (
<div className="flex flex-col gap-1 basis-full">
{existingForField.map((f, idx) => (
<a
key={`${f.url}-${idx}`}
href={f.url}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-primary hover:underline truncate max-w-xs"
>
{f.name}
</a>
))}
</div>
)}
</div>
) : isUploaded ? (
// Uploaded state: a solid success panel that still doubles as a
@@ -431,7 +459,7 @@ export function SmartFileInput({
? "border-2 border-dashed border-primary bg-primary/5 dark:bg-primary/10"
: "border-emerald-300/70 bg-emerald-50/60 dark:border-emerald-500/30 dark:bg-emerald-500/10",
disabled &&
"opacity-50 pointer-events-none cursor-not-allowed",
"opacity-50 pointer-events-none cursor-not-allowed",
)}
>
<input
@@ -457,11 +485,31 @@ export function SmartFileInput({
<p className="text-sm font-semibold text-foreground">
{isDragOver ? "Drop to replace" : "Document uploaded"}
</p>
<p className="mt-0.5 text-xs text-muted-foreground">
{isDragOver
? "Release to replace the document on file."
: "Saved to your application. Drag a new file here or click to replace it."}
</p>
{existingForField.length > 0 ? (
<div className="mt-0.5 flex flex-col gap-0.5">
{existingForField.map((f, idx) => (
<a
key={`${f.url}-${idx}`}
href={f.url}
target="_blank"
rel="noopener noreferrer"
onClick={(e) => e.stopPropagation()}
className="relative z-10 text-xs text-primary hover:underline truncate max-w-xs"
>
{f.name}
{typeof f.size === "number"
? ` (${formatBytes(f.size)})`
: ""}
</a>
))}
</div>
) : (
<p className="mt-0.5 text-xs text-muted-foreground">
{isDragOver
? "Release to replace the document on file."
: "Saved to your application. Drag a new file here or click to replace it."}
</p>
)}
</div>
<span className="hidden shrink-0 items-center gap-1.5 rounded-md border border-border bg-card px-3 py-1.5 text-xs font-medium text-foreground shadow-2xs transition group-hover:border-primary/50 group-hover:text-primary sm:inline-flex">
@@ -480,9 +528,9 @@ export function SmartFileInput({
? "border-primary bg-primary/5 dark:bg-primary/10"
: "border-border hover:border-primary/50 hover:bg-muted/10",
fieldError &&
"border-destructive hover:border-destructive/80",
"border-destructive hover:border-destructive/80",
disabled &&
"opacity-50 pointer-events-none cursor-not-allowed",
"opacity-50 pointer-events-none cursor-not-allowed",
)}
>
<input
@@ -534,4 +582,4 @@ export function SmartFileInput({
);
}
export default SmartFileInput;
export default SmartFileInput;