S3 File Viewer
An inline preview of one stored file: an image when it is one, a labelled link when it is not.
'use client'
import * as React from 'react'
import { S3FileViewer } from '@/components/custom/s3-file-viewer'
import { StorageProvider, type StorageAdapter } from '@/lib/kiota-storage'
const RECEIPT_SVG =
'data:image/svg+xml;utf8,' +
encodeURIComponent(
`<svg xmlns="http://www.w3.org/2000/svg" width="240" height="140">
<rect width="240" height="140" fill="#d7ece8"/>
<text x="120" y="76" font-family="sans-serif" font-size="15" fill="#0d5a4e" text-anchor="middle">receipt.png</text>
</svg>`,
)
// Stands in for your API client. In a real app these are the same functions
// your server actions already expose.
const demoAdapter: StorageAdapter = {
signFile: async () => ({ data: { url: '/api/demo-upload', key: 'demo' } }),
getFile: async (key) => ({ data: { url: key.endsWith('.png') ? RECEIPT_SVG : null } }),
deleteFile: async () => ({ data: { ok: true } }),
}
export default function S3FileViewerDemo() {
return (
<StorageProvider adapter={demoAdapter}>
<div className="flex w-full flex-wrap items-start gap-8">
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">Image key</p>
<S3FileViewer url="/payouts/1723459200_receipt.png" className="h-32 w-56 rounded-md border" />
</div>
<div className="space-y-2">
<p className="text-sm font-medium text-muted-foreground">Document key</p>
<S3FileViewer url="/payouts/1723459200_settlement-report.pdf" />
</div>
</div>
</StorageProvider>
)
}Installation
pnpm dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/s3-file-viewer.jsonnpx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/s3-file-viewer.jsonyarn dlx shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/s3-file-viewer.jsonbunx --bun shadcn@latest add https://kiotapay-ui-staging.staging.kiotapay.com/r/s3-file-viewer.jsonInstalls the component and lib/kiota-storage.tsx. Wire the adapter once — see Storage adapter.
Usage
import { S3FileViewer } from '@/components/custom/s3-file-viewer'
export function AvatarCell({ row }) {
return <S3FileViewer url={row.original.photoKey} alt="Employee photo" className="size-10 rounded-full" />
}Props
| Prop | Type | Default | Description |
|---|---|---|---|
url | string | null | — | A storage key, or an http/data/blob URL. |
alt | string | 'file' | Alt text for the image branch. |
className | string | — | Applied to the wrapper. |
adapter | StorageAdapter | — | Overrides the provider for this instance. |
Behaviour notes
Renders null for an empty value, so it can sit unguarded in a table column that is often blank.
URLs skip the round trip. A value that already starts with http:, https:, data: or blob: is used as-is — only opaque keys are sent to getFile.
Filenames drop their prefix. Keys are typically <timestamp>_<original name>; the display strips everything up to the first underscore, because otherwise every filename in a list begins with the same unreadable run of digits. The full name stays in the title attribute.
A failed resolve falls back to the raw value rather than blanking out, so a filename the user can still read survives an expired presign endpoint.
For anything bigger than a thumbnail, use View S3 Attachment — it opens a dialog with PDF, video and audio handling.
