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
| Mode | Description | Use Case |
|---|---|---|
| thumbnail | Grid layout dengan thumbnail preview | Photo galleries, file browsers |
| list | Vertical list dengan file details | Document management systems |
| carousel | Horizontal scrolling carousel | Featured 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
| Prop | Type | Default | Description |
|---|---|---|---|
files | string | string[] | - | Required. File path(s) to display |
viewMode | ViewMode | 'thumbnail' | Display mode for files |
cols | number | 4 | Number of columns in thumbnail mode |
height | number | 200 | Height of file previews in pixels |
onFileClick | function | - | Callback when file is clicked |
enableModal | boolean | true | Enable modal preview on click |
modalTitle | string | 'File Preview' | Title displayed in modal |
downloadable | boolean | true | Show download button in modal |
modalProps | ModalProps | - | Additional Mantine Modal props |
className | string | - | Additional CSS classes |
🎨 Examples
Gallery View
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>
);
}
Carousel Mode
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
| Type | Extensions | Preview Type |
|---|---|---|
| Images | .jpg, .jpeg, .png, .gif, .webp, .svg | Native image preview |
| Documents | .pdf | PDF embed viewer |
| Videos | .mp4, .webm, .ogg | HTML5 video player |
| Audio | .mp3, .wav, .ogg | HTML5 audio player |
| Other | Any file type | Icon + 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
/>
);
}
🔗 Related Components
- ModalFileViewer - Modal component used internally
- FieldDropzoneNew - File upload component