UI Components
Overview
Kumpulan reusable UI components untuk aplikasi frontend. Components ini dirancang untuk modular, customizable, dan mudah diintegrasikan ke dalam berbagai project.
Komponen
Field Dropzone
Advanced file upload component dengan drag-and-drop functionality.
Features:
- Drag and drop file upload
- Image compression otomatis
- Multi-format preview (images, PDF, video)
- Dual view modes (thumbnail grid & table row)
- File validation (size, type)
- Auto-cleanup untuk deleted files
- Integration dengan file management system
Teknologi: React, React Dropzone, File API
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew'
<FieldDropzoneNew
label="Upload Files"
value={files}
onFilesChange={setFiles}
maxSize={5242880} // 5MB
accept={{
'image/*': ['.png', '.jpg', '.jpeg'],
'application/pdf': ['.pdf']
}}
/>
File Viewer
Component untuk menampilkan berbagai tipe file dengan preview.
Features:
- Support multiple file types (images, PDF, videos, documents)
- Lightbox untuk images
- PDF viewer dengan zoom controls
- Video player dengan controls
- Download functionality
- Responsive design
Teknologi: React, PDF.js, Custom viewers
import { FileViewer } from '@/components/FileViewer'
<FileViewer
fileUrl="/path/to/file.pdf"
fileType="application/pdf"
fileName="document.pdf"
/>
Modal File Viewer
Modal dialog untuk viewing files dengan full-screen support.
Features:
- Full-screen file preview
- Navigation untuk multiple files
- Keyboard shortcuts (ESC to close, arrows to navigate)
- Download button
- Close button dan overlay click to close
- Responsive modal layout
Teknologi: React, Modal component, File Viewer integration
import { ModalFileViewer } from '@/components/ModalFileViewer'
<ModalFileViewer
isOpen={isOpen}
onClose={() => setIsOpen(false)}
files={fileList}
currentIndex={selectedIndex}
onNavigate={setSelectedIndex}
/>
Async Select Components
Advanced async dropdown components untuk data dari API dengan lazy loading dan pagination.
AsyncSingleSelect
Standalone single-select dropdown dengan async loading (tanpa form library).
Features:
- Async lazy loading dari API
- Infinite scroll pagination
- Debounce search (500ms)
- Clearable option
- Loading states
- Controlled component
Use Case: Simple forms, custom form handling, non-RHF projects
import { AsyncSingleSelect } from '@/components/AsyncSingleSelect'
<AsyncSingleSelect
placeholder="Pilih User"
selected={selectedUser}
onChange={setSelectedUser}
loadOptions={loadUsers}
clearable={true}
/>
AsyncSingleSelectRHF
Single-select dropdown dengan React Hook Form integration.
Features:
- React Hook Form integration
- Form validation built-in
- Pagination dengan caching
- IntersectionObserver untuk smooth scroll
- TypeScript generics support
- Value transformation (mapFormToOption/mapOptionToForm)
Use Case: Complex forms dengan validation, RHF projects
import { FormAsyncSingleSelect } from '@/components/AsyncSingleSelectRHF'
<FormAsyncSingleSelect
name="userId"
control={control}
loadOptions={loadUsers}
rules={{ required: "User wajib dipilih" }}
/>
AsyncMultiSelect
Standalone multi-select dropdown dengan chips display (tanpa form library).
Features:
- Multiple selection dengan chips/badges
- Async lazy loading
- Infinite scroll pagination
- Debounce search (500ms)
- Removable chips
- Throttled scroll (300ms)
Use Case: Simple multi-selection, custom forms
import { AsyncMultiSelect } from '@/components/AsyncMultiSelect'
<AsyncMultiSelect
placeholder="Pilih Tags"
selected={selectedTags}
onChange={setSelectedTags}
loadOptions={loadTags}
pageSize={10}
/>
AsyncMultiSelectRHF
Multi-select dropdown dengan React Hook Form integration dan advanced features.
Features:
- React Hook Form integration
- Non-removable items control (fixed items)
- Custom chip styling
- Form validation
- Caching mechanism
- IntersectionObserver scroll
Use Case: Complex multi-selection forms, task assignees, tagging systems
import { FormAsyncMultiSelect } from '@/components/AsyncMultiSelectRHF'
<FormAsyncMultiSelect
name="assignees"
control={control}
loadOptions={loadUsers}
isValueRemovable={(user) => !user.isCurrentUser}
rules={{
validate: (v) => v.length >= 1 || "Min 1 assignee required"
}}
/>
Comparison: Async Select Variants
| Feature | AsyncSingleSelect | AsyncSingleSelectRHF | AsyncMultiSelect | AsyncMultiSelectRHF |
|---|---|---|---|---|
| Form Integration | ❌ Manual | ✅ React Hook Form | ❌ Manual | ✅ React Hook Form |
| Validation | ❌ Manual | ✅ Built-in | ❌ Manual | ✅ Built-in |
| Selection Type | Single | Single | Multiple | Multiple |
| Chips Display | N/A | N/A | ✅ Yes | ✅ Yes |
| Caching | ❌ No | ✅ Yes | ❌ No | ✅ Yes |
| IntersectionObserver | ❌ No | ✅ Yes | ❌ No | ✅ Yes |
| Non-Removable Items | N/A | N/A | ❌ No | ✅ Yes |
| Custom Chip Style | N/A | N/A | ❌ Fixed | ✅ Customizable |
| TypeScript Generics | ✅ Good | ✅ Excellent | ✅ Good | ✅ Excellent |
| Use Case | Simple forms | Complex forms | Simple multi-select | Complex multi-select |
Common Patterns
File Upload Flow
import { useState } from 'react'
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew'
import { ModalFileViewer } from '@/components/ModalFileViewer'
function FileManagement() {
const [files, setFiles] = useState([])
const [viewerOpen, setViewerOpen] = useState(false)
const [selectedFile, setSelectedFile] = useState(0)
const handleFileClick = (index) => {
setSelectedFile(index)
setViewerOpen(true)
}
return (
<>
{/* Upload Section */}
<FieldDropzoneNew
label="Upload Documents"
value={files}
onFilesChange={setFiles}
onFileClick={handleFileClick}
/>
{/* Viewer Modal */}
<ModalFileViewer
isOpen={viewerOpen}
onClose={() => setViewerOpen(false)}
files={files}
currentIndex={selectedFile}
onNavigate={setSelectedFile}
/>
</>
)
}
Image Gallery
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew'
function ImageGallery() {
const [images, setImages] = useState([])
return (
<FieldDropzoneNew
label="Photo Gallery"
value={images}
onFilesChange={setImages}
accept={{
'image/*': ['.png', '.jpg', '.jpeg', '.gif', '.webp']
}}
maxSize={10485760} // 10MB
view="thumbnail" // Grid view untuk gallery
compressImage={true} // Enable compression
maxWidth={1920}
maxHeight={1080}
quality={0.8}
/>
)
}
Document Management
import { FieldDropzoneNew } from '@/components/Field/FieldDropzoneNew'
function DocumentManager() {
const [documents, setDocuments] = useState([])
return (
<FieldDropzoneNew
label="Documents"
value={documents}
onFilesChange={setDocuments}
accept={{
'application/pdf': ['.pdf'],
'application/msword': ['.doc'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document': ['.docx'],
'application/vnd.ms-excel': ['.xls'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': ['.xlsx']
}}
maxSize={52428800} // 50MB
view="row" // Table view untuk documents
multiple={true}
/>
)
}
Styling & Customization
Theme Integration
Components mendukung customization melalui CSS variables:
/* Custom theme colors */
:root {
--dropzone-border: #e2e8f0;
--dropzone-bg: #f8fafc;
--dropzone-hover: #eff6ff;
--dropzone-text: #64748b;
--primary-color: #3b82f6;
}
Custom Styling
<FieldDropzoneNew
className="custom-dropzone"
style={{
borderRadius: '12px',
padding: '24px'
}}
// ... other props
/>
Best Practices
1. File Size Limits
Selalu set file size limits yang reasonable:
// Images: 5-10MB
<FieldDropzoneNew maxSize={10485760} />
// Documents: 50MB
<FieldDropzoneNew maxSize={52428800} />
// Videos: 100MB
<FieldDropzoneNew maxSize={104857600} />
2. Image Compression
Enable compression untuk images:
<FieldDropzoneNew
compressImage={true}
maxWidth={1920}
maxHeight={1080}
quality={0.8} // 80% quality
/>
3. File Type Validation
Specify accepted file types:
<FieldDropzoneNew
accept={{
'image/*': ['.png', '.jpg', '.jpeg'],
'application/pdf': ['.pdf']
}}
/>
4. Error Handling
Handle upload errors properly:
<FieldDropzoneNew
onError={(error) => {
console.error('Upload error:', error)
toast.error('Failed to upload file')
}}
/>
5. Loading States
Show loading indicators:
const [uploading, setUploading] = useState(false)
<FieldDropzoneNew
disabled={uploading}
onUploadStart={() => setUploading(true)}
onUploadEnd={() => setUploading(false)}
/>
Accessibility
Components dibangun dengan accessibility in mind:
- Keyboard navigation support
- ARIA labels dan roles
- Screen reader friendly
- Focus management
- Color contrast compliance
<FieldDropzoneNew
aria-label="Upload files"
aria-describedby="upload-instructions"
/>
Performance Tips
1. Lazy Loading
import { lazy, Suspense } from 'react'
const FieldDropzoneNew = lazy(() =>
import('@/components/Field/FieldDropzoneNew')
)
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<FieldDropzoneNew />
</Suspense>
)
}
2. Memoization
import { useMemo } from 'react'
const memoizedFiles = useMemo(() => {
return files.map(file => ({
...file,
preview: URL.createObjectURL(file)
}))
}, [files])
3. Cleanup
useEffect(() => {
return () => {
// Revoke object URLs to free memory
files.forEach(file => {
if (file.preview) {
URL.revokeObjectURL(file.preview)
}
})
}
}, [files])
Troubleshooting
File Upload Fails
Problem: Files tidak ter-upload
Solutions:
- Check file size limits
- Verify file types are accepted
- Ensure auth token valid
- Check network connectivity
Preview Not Showing
Problem: File preview tidak muncul
Solutions:
- Verify file type support
- Check file URL accessibility
- Ensure CORS headers configured
- Check browser console for errors
Memory Leaks
Problem: Memory usage meningkat
Solutions:
- Revoke object URLs after use
- Implement proper cleanup in useEffect
- Limit preview generation
- Use pagination untuk large file lists
Related Components
- Hooks - Custom hooks untuk file handling
- Utils - Utility functions (ImageCompressor)
- Code Generation - API client generation
Technology Stack: React, TypeScript, React Dropzone, File API
Use Case: File upload, File management, Document handling
Last Updated: November 2025