zentrixui
Version:
ZentrixUI - A modern, highly customizable and accessible React file upload component library with multiple variants, JSON-based configuration, and excellent developer experience.
166 lines (165 loc) • 6.89 kB
JavaScript
import { jsxs, jsx } from "react/jsx-runtime";
import { cn } from "../../../utils/theme.js";
import { ProgressBar } from "../progress/progress-bar.js";
import { StatusIndicator } from "../progress/status-indicator.js";
import { LoadingSpinner } from "../progress/loading-spinner.js";
import { AccessibilityAnnouncer } from "../progress/accessibility-announcer.js";
import { ErrorFeedback } from "./error-feedback.js";
const UploadFeedback = ({
files = [],
isUploading,
showIndividualProgress = true,
showOverallProgress = true,
showStatusIndicators = true,
showFileNames = true,
enableAccessibilityAnnouncements = true,
showErrorFeedback = true,
showAccessibilityAnnouncer = true,
layout = "default",
progressSize = "md",
statusSize = "md",
maxVisibleFiles = 10,
className,
onRetry,
onRemove
}) => {
if (!files || files.length === 0) {
return null;
}
const totalProgress = files.reduce((sum, file) => sum + file.progress, 0);
const overallProgress = files.length > 0 ? totalProgress / files.length : 0;
const completedFiles = files.filter((f) => f.status === "success").length;
const failedFiles = files.filter((f) => f.status === "error").length;
const uploadingFiles = files.filter((f) => f.status === "uploading").length;
const overallStatus = failedFiles > 0 && !isUploading ? "error" : completedFiles === files.length && files.length > 0 ? "success" : "default";
const visibleFiles = files.slice(0, maxVisibleFiles);
const hiddenFilesCount = Math.max(0, files.length - maxVisibleFiles);
return /* @__PURE__ */ jsxs("div", { className: cn("space-y-4", className), children: [
showErrorFeedback && /* @__PURE__ */ jsx(
ErrorFeedback,
{
compact: layout === "compact",
showAccessibilityAnnouncer
}
),
enableAccessibilityAnnouncements && /* @__PURE__ */ jsx(
AccessibilityAnnouncer,
{
files,
isUploading,
announceProgress: true,
announceStatus: true,
announceErrors: true
}
),
showOverallProgress && files.length > 1 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
isUploading && /* @__PURE__ */ jsx(LoadingSpinner, { size: "sm" }),
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: isUploading ? "Uploading files..." : "Upload complete" })
] }),
/* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-600", children: [
completedFiles,
"/",
files.length,
" files"
] })
] }),
/* @__PURE__ */ jsx(
ProgressBar,
{
progress: overallProgress,
variant: overallStatus,
showPercentage: true,
"aria-label": `Overall upload progress: ${Math.round(overallProgress)}%`
}
),
/* @__PURE__ */ jsx("div", { className: "flex justify-between text-xs text-gray-600", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-4", children: [
completedFiles > 0 && /* @__PURE__ */ jsxs("span", { className: "text-green-600", children: [
"✓ ",
completedFiles,
" completed"
] }),
uploadingFiles > 0 && /* @__PURE__ */ jsxs("span", { className: "text-blue-600", children: [
"↑ ",
uploadingFiles,
" uploading"
] }),
failedFiles > 0 && /* @__PURE__ */ jsxs("span", { className: "text-red-600", children: [
"✗ ",
failedFiles,
" failed"
] })
] }) })
] }),
showIndividualProgress && /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
visibleFiles.map((file) => /* @__PURE__ */ jsxs(
"div",
{
className: "flex items-center gap-3 p-3 bg-gray-50 rounded-lg",
children: [
showStatusIndicators && /* @__PURE__ */ jsx(
StatusIndicator,
{
status: file.status,
size: "sm",
"aria-label": `${file.name} status: ${file.status}`
}
),
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
showFileNames && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium truncate", children: file.name }),
/* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500 ml-2", children: [
(file.size / 1024 / 1024).toFixed(1),
" MB"
] })
] }),
(file.status === "uploading" || file.progress > 0) && /* @__PURE__ */ jsx(
ProgressBar,
{
progress: file.progress,
size: "sm",
variant: file.status === "error" ? "error" : file.status === "success" ? "success" : "default",
showPercentage: file.status === "uploading",
"aria-label": `${file.name} upload progress: ${file.progress}%`
}
),
file.status === "error" && file.error && /* @__PURE__ */ jsx("div", { className: "mt-1 text-xs text-red-600", children: file.error })
] }),
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
file.status === "error" && onRetry && /* @__PURE__ */ jsx(
"button",
{
onClick: () => onRetry(file.id),
className: "text-xs text-blue-600 hover:text-blue-800 px-2 py-1 rounded border border-blue-200 hover:border-blue-300",
"aria-label": `Retry upload for ${file.name}`,
children: "Retry"
}
),
onRemove && file.status !== "uploading" && /* @__PURE__ */ jsx(
"button",
{
onClick: () => onRemove(file.id),
className: "text-xs text-gray-600 hover:text-red-600 px-2 py-1 rounded border border-gray-200 hover:border-red-300",
"aria-label": `Remove ${file.name}`,
children: "Remove"
}
)
] })
]
},
file.id
)),
hiddenFilesCount > 0 && /* @__PURE__ */ jsxs("div", { className: "text-center text-sm text-gray-600 py-2", children: [
"... and ",
hiddenFilesCount,
" more file",
hiddenFilesCount === 1 ? "" : "s"
] })
] })
] });
};
export {
UploadFeedback
};
//# sourceMappingURL=upload-feedback.js.map