diff --git a/packages/ui-common/src/components/SmartFileInput/index.tsx b/packages/ui-common/src/components/SmartFileInput/index.tsx
index d39e431c7..0da3e2f37 100644
--- a/packages/ui-common/src/components/SmartFileInput/index.tsx
+++ b/packages/ui-common/src/components/SmartFileInput/index.tsx
@@ -11,6 +11,7 @@ import {
} from "lucide-react";
import { cn } from "../../lib/utils";
import { Button } from "../button";
+import type { ViewableFile } from "../FileViewer";
export interface SmartFileInputProps {
/** The settings object containing features and their upload fields config. */
@@ -35,8 +36,18 @@ export interface SmartFileInputProps {
*/
existingFiles?: Record<
string,
- { name: string; url: string; size?: number }[]
+ { name: string; url: string; size?: number; mimeType?: string | null }[]
>;
+ /**
+ * When provided, already-uploaded files render as buttons that call this with
+ * the file instead of opening a new browser tab. Wire it to `useFileViewer`'s
+ * `view` to preview documents inline:
+ *
+ * const { view, viewer } = useFileViewer();
+ *
+ * {viewer}
+ */
+ onViewFile?: (file: ViewableFile) => void;
/** Disabled state for the entire file input group. */
disabled?: boolean;
/** Display variant style. Default is "default" (large dropzone). Minimal renders a compact upload button. */
@@ -80,6 +91,68 @@ function FileIcon({ name, className }: { name: string; className?: string }) {
return ;
}
+type ExistingFile = {
+ name: string;
+ url: string;
+ size?: number;
+ mimeType?: string | null;
+};
+
+/**
+ * A single already-uploaded file. Renders a click-to-view button when
+ * `onViewFile` is set (inline preview via the FileViewer), otherwise a plain
+ * new-tab anchor.
+ */
+function ExistingFileLink({
+ file: f,
+ onViewFile,
+ className,
+ showSize = false,
+}: {
+ file: ExistingFile;
+ onViewFile?: (file: ViewableFile) => void;
+ className?: string;
+ showSize?: boolean;
+}) {
+ const label =
+ showSize && typeof f.size === "number"
+ ? `${f.name} (${formatBytes(f.size)})`
+ : f.name;
+
+ if (onViewFile) {
+ return (
+
+ );
+ }
+
+ return (
+ e.stopPropagation()}
+ className={cn(
+ "relative z-10 text-xs text-primary hover:underline truncate max-w-xs",
+ className,
+ )}
+ >
+ {label}
+
+ );
+}
+
export function SmartFileInput({
file,
value,
@@ -87,6 +160,7 @@ export function SmartFileInput({
errors,
uploadedKeys,
existingFiles,
+ onViewFile,
disabled = false,
variant = "default",
className,
@@ -433,15 +507,11 @@ export function SmartFileInput({
{existingForField.length > 0 && (
{existingForField.map((f, idx) => (
-
- {f.name}
-
+ file={f}
+ onViewFile={onViewFile}
+ />
))}
)}
@@ -488,19 +558,12 @@ export function SmartFileInput({
{existingForField.length > 0 ? (
) : (
diff --git a/packages/ui-common/src/hooks/useFileViewer.tsx b/packages/ui-common/src/hooks/useFileViewer.tsx
new file mode 100644
index 000000000..68269f7cf
--- /dev/null
+++ b/packages/ui-common/src/hooks/useFileViewer.tsx
@@ -0,0 +1,27 @@
+import { useCallback, useState } from "react";
+import { FileViewerModal, type ViewableFile } from "../components/FileViewer";
+
+/**
+ * Drives a single shared {@link FileViewerModal} for a page. Call `view(file)`
+ * from any file row to open the document inline (pdf / image / video / office /
+ * text); render `viewer` once near the page root.
+ *
+ * const { view, viewer } = useFileViewer();
+ *
+ * {viewer}
+ *
+ * Pass `view` straight into `SmartFileInput`'s `onViewFile` prop to make its
+ * already-uploaded files open in the viewer instead of a new tab.
+ */
+export function useFileViewer() {
+ const [file, setFile] = useState(null);
+
+ const view = useCallback((f: ViewableFile) => setFile(f), []);
+ const close = useCallback(() => setFile(null), []);
+
+ const viewer = (
+
+ );
+
+ return { view, close, viewer };
+}
diff --git a/packages/ui-common/src/index.ts b/packages/ui-common/src/index.ts
index 2e8779076..43c39d962 100644
--- a/packages/ui-common/src/index.ts
+++ b/packages/ui-common/src/index.ts
@@ -20,6 +20,8 @@ export type {
ViewableFile,
} from "./components/FileViewer";
+export { useFileViewer } from "./hooks/useFileViewer";
+
export { OperationDatePicker } from "./components/OperationDatePicker";
export type { OperationDatePickerProps } from "./components/OperationDatePicker";