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
/>
);
}