Skip to main content

FileViewer Component

FileViewer adalah komponen reusable untuk menampilkan berbagai jenis file dengan multiple viewing modes, modal preview, dan navigasi antar file.

📋 Overview​

🎯 Fungsi Utama​

  • Multi-format Support: Support untuk gambar, PDF, video, dan file lainnya
  • Multiple View Modes: Thumbnail, list, dan carousel view modes
  • Modal Preview: Full-size preview dengan navigation controls
  • Responsive Design: Optimized untuk desktop dan mobile
  • Download Functionality: Built-in download capability

🎨 View Modes​

ModeDescriptionUse Case
thumbnailGrid layout dengan thumbnail previewPhoto galleries, file browsers
listVertical list dengan file detailsDocument management systems
carouselHorizontal scrolling carouselFeatured content, product galleries

🚀 Quick Start​

Basic Usage​

import { FileViewer } from '@/components/FileViewer/FileViewer';
import type { FileViewerProps } from '@/components/FileViewer/types';

function MyComponent() {
return (
<FileViewer
files="/path/to/image.jpg"
viewMode="thumbnail"
cols={3}
height={200}
/>
);
}

Multiple Files​

import { FileViewer } from '@/components/FileViewer/FileViewer';

function FileGallery() {
const files = [
'/uploads/image1.jpg',
'/uploads/image2.jpg',
'/uploads/document.pdf'
];

const handleFileClick = (file, index) => {
console.log('File clicked:', file, 'at index:', index);
};

return (
<FileViewer
files={files}
viewMode="thumbnail"
onFileClick={handleFileClick}
enableModal={true}
modalTitle="File Preview"
/>
);
}

📝 API Reference​

Props Interface​

interface FileViewerProps {
// File Data
files: string | string[]; // Required - File path(s)

// Display Options
viewMode?: ViewMode; // 'thumbnail' | 'list' | 'carousel' (default: 'thumbnail')
cols?: number; // Grid columns for thumbnail (default: 4)
height?: number; // Preview height in px (default: 200)

// Interaction
onFileClick?: (file: FileItem, index: number) => void; // Click handler
enableModal?: boolean; // Enable modal viewer (default: true)

// Modal Configuration
modalProps?: ModalProps; // Additional modal props
modalTitle?: string; // Modal title (default: 'File Preview')
downloadable?: boolean; // Enable download button (default: true)

// Styling
className?: string; // Additional CSS classes
}

type ViewMode = 'thumbnail' | 'list' | 'carousel';

interface FileItem {
path: string;
name: string;
type: string;
size?: number;
url?: string;
}

Props Breakdown​

PropTypeDefaultDescription
filesstring | string[]-Required. File path(s) to display
viewModeViewMode'thumbnail'Display mode for files
colsnumber4Number of columns in thumbnail mode
heightnumber200Height of file previews in pixels
onFileClickfunction-Callback when file is clicked
enableModalbooleantrueEnable modal preview on click
modalTitlestring'File Preview'Title displayed in modal
downloadablebooleantrueShow download button in modal
modalPropsModalProps-Additional Mantine Modal props
classNamestring-Additional CSS classes

🎨 Examples​

import { FileViewer } from '@/components/FileViewer/FileViewer';

function PhotoGallery() {
const weddingPhotos = [
'/photos/wedding/001.jpg',
'/photos/wedding/002.jpg',
'/photos/wedding/003.jpg',
'/photos/wedding/004.jpg',
'/photos/wedding/005.jpg',
'/photos/wedding/006.jpg'
];

return (
<div>
<h2>Wedding Gallery</h2>
<FileViewer
files={weddingPhotos}
viewMode="thumbnail"
cols={4}
height={250}
modalTitle="Wedding Photos"
downloadable={false} // Disable download for protected photos
/>
</div>
);
}

Document Browser​

import { FileViewer } from '@/components/FileViewer/FileViewer';

function DocumentBrowser() {
const documents = [
'/docs/report.pdf',
'/docs/presentation.pptx',
'/docs/spreadsheet.xlsx',
'/docs/image-diagram.png'
];

const handleDocumentSelect = (document, index) => {
// Navigate to document detail page
router.push(`/documents/${index}`);
};

return (
<div>
<h2>Project Documents</h2>
<FileViewer
files={documents}
viewMode="list"
height={120}
onFileClick={handleDocumentSelect}
enableModal={false} // Use custom navigation instead of modal
modalTitle="Document Preview"
/>
</div>
);
}
import { FileViewer } from '@/components/FileViewer/FileViewer';

function FeaturedProducts() {
const productImages = [
'/products/featured-1.jpg',
'/products/featured-2.jpg',
'/products/featured-3.jpg'
];

return (
<div>
<h2>Featured Products</h2>
<FileViewer
files={productImages}
viewMode="carousel"
height={300}
modalTitle="Product Details"
onFileClick={(product, index) => {
// Track product clicks
analytics.track('product_view', {
productId: index,
imagePath: product.path
});
}}
/>
</div>
);
}

Mixed File Types​

import { FileViewer } from '@/components/FileViewer/FileViewer';

function FileManager() {
const mixedFiles = [
'/images/avatar.jpg',
'/documents/contract.pdf',
'/videos/intro.mp4',
'/images/logo.png',
'/documents/specification.docx'
];

return (
<div>
<h2>File Manager</h2>
<FileViewer
files={mixedFiles}
viewMode="thumbnail"
cols={3}
height={180}
modalTitle="File Preview"
downloadable={true}
onFileClick={(file, index) => {
console.log(`Opening file: ${file.name}`);
}}
className="my-file-viewer"
/>
</div>
);
}

🔧 Advanced Usage​

Custom File Processing​

import { FileViewer } from '@/components/FileViewer/FileViewer';

function CustomFileViewer({ files, onFileAction }) {
const processedFiles = files.map(file => ({
...file,
// Add custom processing
url: `${process.env.NEXT_PUBLIC_API_URL}${file.path}`,
name: file.path.split('/').pop()
}));

const handleFileClick = (file, index) => {
// Custom action handler
onFileAction?.(file, index);
};

return (
<FileViewer
files={processedFiles}
viewMode="thumbnail"
onFileClick={handleFileClick}
modalProps={{
size: 'xl',
centered: true
}}
/>
);
}

Integration with State Management​

import { useState } from 'react';
import { FileViewer } from '@/components/FileViewer/FileViewer';

function StatefulFileViewer() {
const [selectedFiles, setSelectedFiles] = useState([]);
const [viewMode, setViewMode] = useState('thumbnail');

const handleFileClick = (file, index) => {
setSelectedFiles(prev =>
prev.includes(index)
? prev.filter(i => i !== index)
: [...prev, index]
);
};

return (
<div>
<div className="controls">
<button onClick={() => setViewMode('thumbnail')}>Thumbnails</button>
<button onClick={() => setViewMode('list')}>List</button>
<span>Selected: {selectedFiles.length} files</span>
</div>

<FileViewer
files={fileList}
viewMode={viewMode}
onFileClick={handleFileClick}
height={viewMode === 'list' ? 80 : 200}
/>
</div>
);
}

🎯 File Type Support​

Supported Formats​

TypeExtensionsPreview Type
Images.jpg, .jpeg, .png, .gif, .webp, .svgNative image preview
Documents.pdfPDF embed viewer
Videos.mp4, .webm, .oggHTML5 video player
Audio.mp3, .wav, .oggHTML5 audio player
OtherAny file typeIcon + download link

File Detection​

// Component automatically detects file type
const getFileType = (filePath: string): FileType => {
const extension = filePath.split('.').pop()?.toLowerCase();

if (['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'].includes(extension)) {
return 'image';
}
if (extension === 'pdf') {
return 'pdf';
}
if (['mp4', 'webm', 'ogg'].includes(extension)) {
return 'video';
}

return 'unknown';
};

🎨 Styling​

CSS Customization​

/* Custom styles for FileViewer */
.my-file-viewer {
border: 2px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
background: #f8f9fa;
}

.my-file-viewer .file-item {
transition: transform 0.2s ease;
}

.my-file-viewer .file-item:hover {
transform: scale(1.05);
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
}

Theme Integration​

import { useMantineTheme } from '@mantine/core';

function ThemedFileViewer({ files }) {
const theme = useMantineTheme();

return (
<FileViewer
files={files}
modalProps={{
overlayColor: theme.colorScheme === 'dark'
? theme.colors.dark[9]
: theme.colors.gray[2],
shadow: 'xl'
}}
/>
);
}

🚨 Error Handling​

Loading States​

import { useState, useEffect } from 'react';
import { FileViewer } from '@/components/FileViewer/FileViewer';

function FileViewerWithErrorBoundary() {
const [files, setFiles] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

useEffect(() => {
loadFiles();
}, []);

const loadFiles = async () => {
try {
setLoading(true);
const response = await fetch('/api/files');
const data = await response.json();
setFiles(data.files);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};

if (loading) return <div>Loading files...</div>;
if (error) return <div>Error loading files: {error}</div>;

return <FileViewer files={files} />;
}

Fallback for Missing Files​

function RobustFileViewer({ files }) {
const handleFileError = (file, error) => {
console.error(`Failed to load file: ${file.path}`, error);
// Show error state or fallback content
};

return (
<FileViewer
files={files}
onError={handleFileError}
fallbackComponent={({ file }) => (
<div className="file-error">
<span>⚠️ File unavailable</span>
<span>{file.name}</span>
</div>
)}
/>
);
}

🔧 Dependencies​

Required Dependencies​

npm install @mantine/core @mantine/carousel @tabler/icons-react

Internal Dependencies​

  • @/components/Modal/ModalFileViewer - Modal preview component
  • @/components/FileViewer/types.ts - TypeScript interfaces
  • @/components/FileViewer/utils.ts - Utility functions

Environment Variables​

# File host configuration
NEXT_PUBLIC_FILE_HOST=https://your-cdn.com
NEXT_PUBLIC_API_URL=https://api.example.com

📚 Best Practices​

1. Performance Optimization​

// Lazy loading for large file lists
import { useState } from 'react';

function OptimizedFileViewer({ files }) {
const [visibleFiles, setVisibleFiles] = useState(files.slice(0, 20));

const loadMore = () => {
setVisibleFiles(prev => [
...prev,
...files.slice(prev.length, prev.length + 20)
]);
};

return (
<div>
<FileViewer files={visibleFiles} />
{visibleFiles.length < files.length && (
<button onClick={loadMore}>Load More</button>
)}
</div>
);
}

2. Accessibility​

function AccessibleFileViewer({ files }) {
return (
<FileViewer
files={files}
aria-label="File gallery"
modalProps={{
trapFocus: true,
returnFocus: true
}}
/>
);
}

3. Security​

function SecureFileViewer({ files }) {
const sanitizedFiles = files.map(file => ({
...file,
path: sanitizeFilePath(file.path) // Sanitize file paths
}));

return (
<FileViewer
files={sanitizedFiles}
downloadable={false} // Disable download for sensitive files
/>
);
}

🔍 Troubleshooting​

Common Issues​

Problem: Files not displaying Solution: Check file paths and ensure files are accessible

Problem: Modal not opening Solution: Ensure enableModal={true} and check modal props

Problem: Layout breaking on mobile Solution: Use responsive cols prop and proper CSS

Debug Mode​

function DebugFileViewer({ files }) {
const handleDebug = (file, index) => {
console.log('File clicked:', { file, index });
console.log('File URL:', getFileUrl(file));
console.log('File type:', getFileType(file.path));
};

return (
<FileViewer
files={files}
onFileClick={handleDebug}
debug={true} // Enable debug logging
/>
);
}