@eternalheart/react-file-preview 1.4.0 → 1.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. package/README.md +437 -60
  2. package/README.zh-CN.md +437 -60
  3. package/lib/FilePreviewContent.d.ts +1 -0
  4. package/lib/FilePreviewContent.d.ts.map +1 -1
  5. package/lib/FilePreviewEmbed.d.ts +2 -0
  6. package/lib/FilePreviewEmbed.d.ts.map +1 -1
  7. package/lib/FilePreviewModal.d.ts +2 -0
  8. package/lib/FilePreviewModal.d.ts.map +1 -1
  9. package/lib/chunks/index-BBYKNNLb.mjs +329 -0
  10. package/lib/chunks/index-BBYKNNLb.mjs.map +1 -0
  11. package/lib/chunks/index-BFh22D_W.mjs +78 -0
  12. package/lib/chunks/index-BFh22D_W.mjs.map +1 -0
  13. package/lib/chunks/{index-DoFsoBKL.mjs → index-BKXvtJh5.mjs} +27 -25
  14. package/lib/chunks/index-BKXvtJh5.mjs.map +1 -0
  15. package/lib/chunks/index-Bw3Fh4b5.mjs +116 -0
  16. package/lib/chunks/index-Bw3Fh4b5.mjs.map +1 -0
  17. package/lib/chunks/{index-kALp0tqz.mjs → index-CEC_DHgr.mjs} +22 -20
  18. package/lib/chunks/index-CEC_DHgr.mjs.map +1 -0
  19. package/lib/chunks/{index-CzM2mxrD.mjs → index-COOUxB5e.mjs} +130 -128
  20. package/lib/chunks/{index-CzM2mxrD.mjs.map → index-COOUxB5e.mjs.map} +1 -1
  21. package/lib/chunks/index-CU1Lc3lV.mjs +120 -0
  22. package/lib/chunks/index-CU1Lc3lV.mjs.map +1 -0
  23. package/lib/chunks/index-CgFv7B_G.mjs +359 -0
  24. package/lib/chunks/index-CgFv7B_G.mjs.map +1 -0
  25. package/lib/chunks/index-Cn4ZyhGM.mjs +587 -0
  26. package/lib/chunks/index-Cn4ZyhGM.mjs.map +1 -0
  27. package/lib/chunks/{index-DaAXRBWL.mjs → index-DGNNEnWE.mjs} +864 -862
  28. package/lib/chunks/{index-DaAXRBWL.mjs.map → index-DGNNEnWE.mjs.map} +1 -1
  29. package/lib/chunks/index-DLk08ylq.mjs +313 -0
  30. package/lib/chunks/index-DLk08ylq.mjs.map +1 -0
  31. package/lib/chunks/index-DVtPyN-s.mjs +200 -0
  32. package/lib/chunks/index-DVtPyN-s.mjs.map +1 -0
  33. package/lib/chunks/index-DreA69iU.mjs +2409 -0
  34. package/lib/chunks/index-DreA69iU.mjs.map +1 -0
  35. package/lib/chunks/{index-Cp68OevR.mjs → index-Dta7iGov.mjs} +1299 -1297
  36. package/lib/chunks/{index-Cp68OevR.mjs.map → index-Dta7iGov.mjs.map} +1 -1
  37. package/lib/chunks/index-fQGAUFAX.mjs +275 -0
  38. package/lib/chunks/index-fQGAUFAX.mjs.map +1 -0
  39. package/lib/chunks/{index-C_BJatqr.mjs → index-fSw6Hl5e.mjs} +42 -40
  40. package/lib/chunks/index-fSw6Hl5e.mjs.map +1 -0
  41. package/lib/chunks/index-jvNrkVkp.mjs +291 -0
  42. package/lib/chunks/index-jvNrkVkp.mjs.map +1 -0
  43. package/lib/chunks/index-oVJyD-FV.mjs +107 -0
  44. package/lib/chunks/index-oVJyD-FV.mjs.map +1 -0
  45. package/lib/chunks/{index-DuP0Tlpo.mjs → index-vRLKumL8.mjs} +43 -41
  46. package/lib/chunks/index-vRLKumL8.mjs.map +1 -0
  47. package/lib/chunks/useShikiHighlight-C6nJcETW.mjs +36 -0
  48. package/lib/chunks/useShikiHighlight-C6nJcETW.mjs.map +1 -0
  49. package/lib/components/preview/FilePreviewToolbar.d.ts +1 -0
  50. package/lib/components/preview/FilePreviewToolbar.d.ts.map +1 -1
  51. package/lib/components/preview/ToolbarButton.d.ts +3 -1
  52. package/lib/components/preview/ToolbarButton.d.ts.map +1 -1
  53. package/lib/hooks/index.d.ts +0 -6
  54. package/lib/hooks/index.d.ts.map +1 -1
  55. package/lib/hooks/useShikiHighlight.d.ts +3 -1
  56. package/lib/hooks/useShikiHighlight.d.ts.map +1 -1
  57. package/lib/index.cjs +32 -30
  58. package/lib/index.cjs.map +1 -1
  59. package/lib/index.css +1 -1
  60. package/lib/index.mjs +1 -1
  61. package/lib/renderers/Audio/index.d.ts +2 -1
  62. package/lib/renderers/Audio/index.d.ts.map +1 -1
  63. package/lib/renderers/Csv/index.d.ts +2 -1
  64. package/lib/renderers/Csv/index.d.ts.map +1 -1
  65. package/lib/renderers/Docx/index.d.ts +2 -1
  66. package/lib/renderers/Docx/index.d.ts.map +1 -1
  67. package/lib/renderers/Epub/index.d.ts +2 -3
  68. package/lib/renderers/Epub/index.d.ts.map +1 -1
  69. package/lib/renderers/Font/index.d.ts +2 -1
  70. package/lib/renderers/Font/index.d.ts.map +1 -1
  71. package/lib/renderers/Image/index.d.ts +6 -7
  72. package/lib/renderers/Image/index.d.ts.map +1 -1
  73. package/lib/renderers/Json/index.d.ts +2 -1
  74. package/lib/renderers/Json/index.d.ts.map +1 -1
  75. package/lib/renderers/Markdown/index.d.ts +2 -2
  76. package/lib/renderers/Markdown/index.d.ts.map +1 -1
  77. package/lib/renderers/Mobi/index.d.ts +2 -3
  78. package/lib/renderers/Mobi/index.d.ts.map +1 -1
  79. package/lib/renderers/Msg/index.d.ts +2 -1
  80. package/lib/renderers/Msg/index.d.ts.map +1 -1
  81. package/lib/renderers/Pdf/index.d.ts +4 -8
  82. package/lib/renderers/Pdf/index.d.ts.map +1 -1
  83. package/lib/renderers/Pptx/index.d.ts +2 -1
  84. package/lib/renderers/Pptx/index.d.ts.map +1 -1
  85. package/lib/renderers/Subtitle/index.d.ts +2 -1
  86. package/lib/renderers/Subtitle/index.d.ts.map +1 -1
  87. package/lib/renderers/Text/index.d.ts +2 -3
  88. package/lib/renderers/Text/index.d.ts.map +1 -1
  89. package/lib/renderers/Video/index.d.ts +2 -1
  90. package/lib/renderers/Video/index.d.ts.map +1 -1
  91. package/lib/renderers/Xlsx/index.d.ts +2 -1
  92. package/lib/renderers/Xlsx/index.d.ts.map +1 -1
  93. package/lib/renderers/Xml/index.d.ts +2 -1
  94. package/lib/renderers/Xml/index.d.ts.map +1 -1
  95. package/lib/renderers/Zip/index.d.ts +7 -2
  96. package/lib/renderers/Zip/index.d.ts.map +1 -1
  97. package/lib/renderers/base.types.d.ts +38 -0
  98. package/lib/renderers/base.types.d.ts.map +1 -0
  99. package/lib/renderers/registry.d.ts +36 -0
  100. package/lib/renderers/registry.d.ts.map +1 -0
  101. package/lib/renderers/toolbar.types.d.ts +2 -0
  102. package/lib/renderers/toolbar.types.d.ts.map +1 -1
  103. package/lib/toolbar/renderItems.d.ts.map +1 -1
  104. package/package.json +3 -3
  105. package/lib/chunks/index-0v5STX5f.mjs +0 -105
  106. package/lib/chunks/index-0v5STX5f.mjs.map +0 -1
  107. package/lib/chunks/index-10O8tfTH.mjs +0 -529
  108. package/lib/chunks/index-10O8tfTH.mjs.map +0 -1
  109. package/lib/chunks/index-BCyv1HM9.mjs +0 -175
  110. package/lib/chunks/index-BCyv1HM9.mjs.map +0 -1
  111. package/lib/chunks/index-Bo90aGhy.mjs +0 -114
  112. package/lib/chunks/index-Bo90aGhy.mjs.map +0 -1
  113. package/lib/chunks/index-CEeKt7L3.mjs +0 -2808
  114. package/lib/chunks/index-CEeKt7L3.mjs.map +0 -1
  115. package/lib/chunks/index-CWKbnvW6.mjs +0 -270
  116. package/lib/chunks/index-CWKbnvW6.mjs.map +0 -1
  117. package/lib/chunks/index-C_BJatqr.mjs.map +0 -1
  118. package/lib/chunks/index-Cbz5Z6ZK.mjs +0 -263
  119. package/lib/chunks/index-Cbz5Z6ZK.mjs.map +0 -1
  120. package/lib/chunks/index-DTYBFuAH.mjs +0 -357
  121. package/lib/chunks/index-DTYBFuAH.mjs.map +0 -1
  122. package/lib/chunks/index-DoFsoBKL.mjs.map +0 -1
  123. package/lib/chunks/index-DuP0Tlpo.mjs.map +0 -1
  124. package/lib/chunks/index-Dv3RQz86.mjs +0 -270
  125. package/lib/chunks/index-Dv3RQz86.mjs.map +0 -1
  126. package/lib/chunks/index-QfpHck8N.mjs +0 -55
  127. package/lib/chunks/index-QfpHck8N.mjs.map +0 -1
  128. package/lib/chunks/index-gjSQeou7.mjs +0 -194
  129. package/lib/chunks/index-gjSQeou7.mjs.map +0 -1
  130. package/lib/chunks/index-kALp0tqz.mjs.map +0 -1
  131. package/lib/chunks/index-kCeSnFs-.mjs +0 -54
  132. package/lib/chunks/index-kCeSnFs-.mjs.map +0 -1
  133. package/lib/chunks/useShikiHighlight-BA9qgdGA.mjs +0 -23
  134. package/lib/chunks/useShikiHighlight-BA9qgdGA.mjs.map +0 -1
  135. package/lib/hooks/rendererReducer.d.ts +0 -10
  136. package/lib/hooks/rendererReducer.d.ts.map +0 -1
  137. package/lib/hooks/types.d.ts +0 -152
  138. package/lib/hooks/types.d.ts.map +0 -1
  139. package/lib/hooks/useBookRenderer.d.ts +0 -14
  140. package/lib/hooks/useBookRenderer.d.ts.map +0 -1
  141. package/lib/hooks/useFilePreviewState.d.ts +0 -10
  142. package/lib/hooks/useFilePreviewState.d.ts.map +0 -1
  143. package/lib/hooks/useImageAutoFit.d.ts +0 -13
  144. package/lib/hooks/useImageAutoFit.d.ts.map +0 -1
  145. package/lib/hooks/useToolbarConfig.d.ts +0 -25
  146. package/lib/hooks/useToolbarConfig.d.ts.map +0 -1
  147. package/lib/renderers/Epub/toolbar.d.ts +0 -13
  148. package/lib/renderers/Epub/toolbar.d.ts.map +0 -1
  149. package/lib/renderers/Image/toolbar.d.ts +0 -15
  150. package/lib/renderers/Image/toolbar.d.ts.map +0 -1
  151. package/lib/renderers/Markdown/toolbar.d.ts +0 -9
  152. package/lib/renderers/Markdown/toolbar.d.ts.map +0 -1
  153. package/lib/renderers/Mobi/toolbar.d.ts +0 -13
  154. package/lib/renderers/Mobi/toolbar.d.ts.map +0 -1
  155. package/lib/renderers/Pdf/toolbar.d.ts +0 -16
  156. package/lib/renderers/Pdf/toolbar.d.ts.map +0 -1
  157. package/lib/renderers/Text/toolbar.d.ts +0 -12
  158. package/lib/renderers/Text/toolbar.d.ts.map +0 -1
  159. package/lib/renderers/Zip/toolbar.d.ts +0 -13
  160. package/lib/renderers/Zip/toolbar.d.ts.map +0 -1
  161. package/lib/toolbar/registry.d.ts +0 -51
  162. package/lib/toolbar/registry.d.ts.map +0 -1
package/README.md CHANGED
@@ -6,24 +6,23 @@ A modern, feature-rich file preview component for React with support for images,
6
6
 
7
7
 
8
8
 
9
- ## ✨ Features
10
-
11
- - 🎨 **Modern UI** - Apple-inspired minimalist design with glassmorphism effects
12
- - 📁 **Multi-format Support** - Supports 20+ file formats
13
- - 🪟 **Two Display Modes** - Full-screen modal **or** inline embedded preview
14
- - 🖼️ **Powerful Image Viewer** - Zoom, rotate, drag, mouse wheel zoom
15
- - 🎬 **Custom Video Player** - Built on Video.js, supports multiple video formats
16
- - 🎵 **Custom Audio Player** - Beautiful audio control interface
17
- - 📄 **PDF Viewer** - Pagination support
18
- - 📊 **Office Documents Support** - Word, Excel, PowerPoint file preview
19
- - 📝 **Markdown Rendering** - GitHub Flavored Markdown support
20
- - 💻 **Code Highlighting** - Supports 40+ programming languages
21
- - 🎭 **Smooth Animations** - Powered by Framer Motion
22
- - 📱 **Responsive Design** - Adapts to all screen sizes
23
- - ⌨️ **Keyboard Navigation** - Arrow keys and ESC support
24
- - 🎯 **Drag & Drop** - File upload via drag and drop
25
-
26
- ## 📦 Installation
9
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2728.svg" width="20" height="20" alt="" /> Features
10
+
11
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg" width="16" height="16" alt="🎨" style="vertical-align: middle;" /> **Modern UI** - Clean and modern interface with smooth animations
12
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c1.svg" width="16" height="16" alt="📁" style="vertical-align: middle;" /> **Multi-format Support** - Supports 20+ file formats
13
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1fa9f.svg" width="16" height="16" alt="🪟" style="vertical-align: middle;" /> **Two Display Modes** - Full-screen modal **or** inline embedded preview
14
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f5bc.svg" width="16" height="16" alt="🖼️" style="vertical-align: middle;" /> **Powerful Image Viewer** - Zoom, rotate, drag, mouse wheel zoom
15
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ac.svg" width="16" height="16" alt="🎬" style="vertical-align: middle;" /> **Custom Video Player** - Built on Video.js, supports multiple video formats
16
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3b5.svg" width="16" height="16" alt="🎵" style="vertical-align: middle;" /> **Custom Audio Player** - Beautiful audio control interface
17
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="16" height="16" alt="📄" style="vertical-align: middle;" /> **PDF Viewer** - Pagination support
18
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4ca.svg" width="16" height="16" alt="📊" style="vertical-align: middle;" /> **Office Documents Support** - Word, Excel, PowerPoint file preview
19
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4dd.svg" width="16" height="16" alt="📝" style="vertical-align: middle;" /> **Markdown Rendering** - GitHub Flavored Markdown support
20
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4bb.svg" width="16" height="16" alt="💻" style="vertical-align: middle;" /> **Code Highlighting** - Supports 40+ programming languages
21
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ad.svg" width="16" height="16" alt="🎭" style="vertical-align: middle;" /> **Smooth Animations** - Powered by Framer Motion
22
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4f1.svg" width="16" height="16" alt="📱" style="vertical-align: middle;" /> **Responsive Design** - Adapts to all screen sizes
23
+ - <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="16" height="16" alt="⌨️" style="vertical-align: middle;" /> **Keyboard Navigation** - Arrow keys and ESC support
24
+
25
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4e6.svg" width="20" height="20" alt="📦" /> Installation
27
26
 
28
27
  ```bash
29
28
  # Using npm
@@ -138,7 +137,7 @@ export default defineConfig({
138
137
 
139
138
  > Note: `@jsquash/avif` is only used as a fallback when the browser does not natively support AVIF (Chrome 85+, Firefox 93+, Safari 16+ all support it natively). If your target browsers cover the native list, you can also remove `@jsquash/avif` from your dependencies entirely.
140
139
 
141
- ## 🚀 Quick Start
140
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f680.svg" width="20" height="20" alt="🚀" /> Quick Start
142
141
 
143
142
  📖 **New to this library?** Check out the [Quick Start Guide](./QUICK_START.md) for a 5-minute introduction!
144
143
 
@@ -189,14 +188,18 @@ import { FilePreviewModal, PreviewFileInput } from '@eternalheart/react-file-pre
189
188
  import '@eternalheart/react-file-preview/style.css';
190
189
 
191
190
  function App() {
191
+ // Assume `file1` comes from a File API source: <input type="file">,
192
+ // drag &amp; drop, clipboard paste, or fetch().then(r =&gt; r.blob())
193
+ const file1 = new File(['content'], 'example.txt', { type: 'text/plain' });
194
+
192
195
  const files: PreviewFileInput[] = [
193
- // 1. Native File object
196
+ // 1. Native File object (auto revoked when unmounted)
194
197
  file1,
195
198
 
196
- // 2. HTTP URL string
199
+ // 2. HTTP URL string (loaded on demand)
197
200
  'https://example.com/image.jpg',
198
201
 
199
- // 3. File object with metadata
202
+ // 3. File object with metadata (recommended for remote resources)
200
203
  {
201
204
  name: 'document.pdf',
202
205
  type: 'application/pdf',
@@ -205,6 +208,9 @@ function App() {
205
208
  },
206
209
  ];
207
210
 
211
+ // Memory note: If you generate URLs via URL.createObjectURL(),
212
+ // call URL.revokeObjectURL() when files are removed to avoid memory leaks.
213
+
208
214
  return (
209
215
  <FilePreviewModal
210
216
  files={files}
@@ -258,7 +264,7 @@ Differences from `FilePreviewModal`:
258
264
  <FilePreviewEmbed files={files} width={800} height={500} />
259
265
  ```
260
266
 
261
- ## 💡 Usage Examples
267
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4a1.svg" width="20" height="20" alt="💡" /> Usage Examples
262
268
 
263
269
  ### Preview PowerPoint Files
264
270
 
@@ -311,7 +317,7 @@ const files = [
311
317
  />
312
318
  ```
313
319
 
314
- ## 📖 Supported File Formats
320
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4d6.svg" width="20" height="20" alt="📖" /> Supported File Formats
315
321
 
316
322
  ### Images
317
323
  - **Formats**: JPG, PNG, GIF, WebP, SVG, BMP, ICO
@@ -360,7 +366,56 @@ const files = [
360
366
  ### E-books
361
367
  - **EPUB**: Chapter navigation, pagination
362
368
 
363
- ## 🎮 API Reference
369
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/26a0.svg" width="20" height="20" alt="⚠️" /> Limitations &amp; Performance
370
+
371
+ ### Support Levels
372
+
373
+ **✅ Full Support (Production Ready)**
374
+ - Images (JPG, PNG, GIF, WebP, SVG, BMP, ICO)
375
+ - Videos (MP4, WebM, OGG)
376
+ - Audio (MP3, WAV, OGG)
377
+ - PDF
378
+ - Markdown
379
+ - Code files (40+ languages via Shiki, lazy-loaded)
380
+ - JSON, CSV, XML
381
+
382
+ **⚠️ Partial Support (Preview Only)**
383
+ - **Office (DOCX, XLSX, PPTX)**: Basic layout and text rendering. Complex formatting (charts, macros, embedded objects) may not render accurately.
384
+ - **ZIP**: Directory tree browsing + inline preview for text/code/images. Large archives (&gt;100MB) may cause performance issues.
385
+ - **Fonts (TTF, OTF, WOFF)**: Metadata + character preview. Full font feature testing not supported.
386
+
387
+ **🧪 Experimental**
388
+ - **MSG (Outlook Email)**: Headers and plain text body. Complex HTML body may not render correctly.
389
+ - **EPUB**: Basic chapter navigation. CSS styling may differ from native readers. DRM-protected files not supported.
390
+ - **Subtitle formats (SRT, ASS, TTML, LRC)**: Text display only. No video sync or advanced styling.
391
+
392
+ ### Performance Boundaries
393
+
394
+ | File Size | Status | Notes |
395
+ |-----------|--------|-------|
396
+ | &lt; 50MB | ✅ Recommended | Smooth preview experience |
397
+ | 50-100MB | ⚠️ May lag | UI may become unresponsive during load |
398
+ | &gt; 100MB | ❌ Not recommended | Browser memory limits may be exceeded |
399
+
400
+ **Special Cases:**
401
+ - **ZIP archives**: Performance depends on file count, not just size
402
+ - **Office documents**: Complex files (&gt;200 pages, heavy images) may timeout
403
+ - **Code highlighting**: Files &gt;5MB may take 3-5s to highlight
404
+
405
+ ### Browser Compatibility
406
+
407
+ **Minimum Requirements:**
408
+ - Chrome 90+ / Edge 90+
409
+ - Firefox 88+
410
+ - Safari 14+
411
+
412
+ **Known Limitations:**
413
+ - **Safari iOS**: Video autoplay requires user interaction
414
+ - **Firefox**: AVIF support requires Firefox 93+ (fallback decoder included)
415
+ - **Office formats**: Rendering quality varies across browsers
416
+ - **EPUB**: Some CSS features unsupported in older browsers
417
+
418
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3ae.svg" width="20" height="20" alt="🎮" /> API Reference
364
419
 
365
420
  ### FilePreviewModal Props
366
421
 
@@ -376,6 +431,7 @@ const files = [
376
431
  | `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | Custom translation overrides |
377
432
  | `headless` | `boolean` | ❌ | Headless mode — hides toolbar and navigation arrows |
378
433
  | `theme` | `Theme` | ❌ | Theme mode: `'auto' \| 'dark' \| 'light'` (default `'dark'`) |
434
+ | `showDownload` | `boolean` | ❌ | Whether to show the download button (default `true`) |
379
435
 
380
436
  ### FilePreviewEmbed Props
381
437
 
@@ -393,6 +449,7 @@ const files = [
393
449
  | `messages` | `Partial<Record<Locale, Partial<Messages>>>` | ❌ | - | Custom translation overrides |
394
450
  | `headless` | `boolean` | ❌ | `false` | Headless mode — hides toolbar and navigation arrows |
395
451
  | `theme` | `Theme` | ❌ | `'dark'` | Theme mode: `'auto' \| 'dark' \| 'light'` |
452
+ | `showDownload` | `boolean` | ❌ | `true` | Whether to show the download button |
396
453
 
397
454
  > `FilePreviewEmbed` has no `isOpen` / `onClose`. To hide/show it, conditionally render it from the parent. It also hides the close button in the toolbar.
398
455
 
@@ -512,7 +569,7 @@ const files = [
512
569
  #### E-books
513
570
  - **EPUB**: `application/epub+zip` (.epub)
514
571
 
515
- ## 🌐 Internationalization (i18n)
572
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f310.svg" width="20" height="20" alt="🌐" /> Internationalization (i18n)
516
573
 
517
574
  Built-in support for Chinese (default) and English. Zero external dependencies.
518
575
 
@@ -530,7 +587,353 @@ Built-in support for Chinese (default) and English. Zero external dependencies.
530
587
 
531
588
  Use `useTranslator()` hook in custom renderers to access the translation function.
532
589
 
533
- ## 🎨 Custom Styling
590
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f9e9.svg" width="20" height="20" alt="🧩" /> Custom Renderers
591
+
592
+ The library supports custom renderers for handling file types not built-in. Custom renderers can optionally provide toolbar configurations and integrate with the library's architecture.
593
+
594
+ ### Event-Driven Toolbar Updates
595
+
596
+ Custom renderers can implement real-time toolbar updates using the event-driven mechanism:
597
+
598
+ **Benefits:**
599
+ - **Real-time updates**: Toolbar reflects state changes immediately
600
+ - **Better performance**: No polling overhead or unnecessary re-renders
601
+ - **Type-safe**: Full TypeScript support with proper interfaces
602
+
603
+ **Implementation:**
604
+
605
+ ```tsx
606
+ import { forwardRef, useImperativeHandle, useState, useEffect, useMemo, useCallback } from 'react';
607
+ import { ToolbarEventEmitter } from '@eternalheart/react-file-preview';
608
+ import type { RendererHandle, ToolbarGroup } from '@eternalheart/react-file-preview';
609
+
610
+ interface CustomRendererProps {
611
+ url: string;
612
+ onPageChange?: (current: number, total: number) => void;
613
+ }
614
+
615
+ export const CustomRenderer = forwardRef<RendererHandle, CustomRendererProps>((props, ref) => {
616
+ const [currentPage, setCurrentPage] = useState(1);
617
+ const [totalPages, setTotalPages] = useState(10);
618
+ const emitter = useMemo(() => new ToolbarEventEmitter(), []);
619
+
620
+ // Notify toolbar when state changes
621
+ useEffect(() => {
622
+ emitter.notify();
623
+ props.onPageChange?.(currentPage, totalPages);
624
+ }, [currentPage, totalPages, emitter, props]);
625
+
626
+ const getToolbarGroups = useCallback((): ToolbarGroup[] => [
627
+ {
628
+ items: [
629
+ {
630
+ type: 'button',
631
+ icon: <ChevronLeft className="w-4 h-4" />,
632
+ tooltip: 'Previous Page',
633
+ action: () => setCurrentPage(p => Math.max(1, p - 1)),
634
+ disabled: currentPage <= 1
635
+ },
636
+ {
637
+ type: 'text',
638
+ content: `${currentPage} / ${totalPages}`,
639
+ minWidth: '4rem'
640
+ },
641
+ {
642
+ type: 'button',
643
+ icon: <ChevronRight className="w-4 h-4" />,
644
+ tooltip: 'Next Page',
645
+ action: () => setCurrentPage(p => Math.min(totalPages, p + 1)),
646
+ disabled: currentPage >= totalPages
647
+ }
648
+ ]
649
+ }
650
+ ], [currentPage, totalPages]);
651
+
652
+ useImperativeHandle(ref, () => ({
653
+ getToolbarGroups,
654
+ onToolbarChange: (listener) => emitter.subscribe(listener)
655
+ }), [getToolbarGroups, emitter]);
656
+
657
+ return <div>Your custom renderer UI</div>;
658
+ });
659
+ ```
660
+
661
+ **Main component usage:**
662
+
663
+ ```tsx
664
+ import { CustomRenderer } from './CustomRenderer';
665
+
666
+ <FilePreviewModal
667
+ files={files}
668
+ customRenderers={[
669
+ {
670
+ test: (file) => file.type === 'application/custom',
671
+ component: CustomRenderer
672
+ }
673
+ ]}
674
+ />
675
+ ```
676
+
677
+ The main component automatically detects `onToolbarChange` and subscribes to events. If not implemented, it falls back to polling for backward compatibility.
678
+
679
+ ### Renderer Lazy Loading
680
+
681
+ All built-in renderers use code-splitting via `React.lazy` to minimize the main bundle size and improve initial load performance.
682
+
683
+ **Architecture:**
684
+
685
+ - **Registration**: Renderers register in `src/renderers/lazy.tsx` using named exports wrapped in `React.lazy`
686
+ - **Loading**: Each renderer is a separate chunk, loaded on-demand when needed
687
+ - **Fallback**: `<Suspense>` with `<RendererLoading />` handles the loading state
688
+
689
+ **Bundle Size Impact:**
690
+
691
+ - Main entry point: gzip ≤ 80 KB (strictly enforced by CI)
692
+ - Each renderer: separate async chunk
693
+ - Total library: gzip ≤ 800 KB (all renderers combined)
694
+
695
+ **Implementation Example:**
696
+
697
+ ```tsx
698
+ // src/renderers/lazy.tsx
699
+ import { lazy } from 'react';
700
+ import type { CustomRenderer as CustomRendererImpl } from './Custom';
701
+
702
+ export const CustomRenderer: Lazy<typeof CustomRendererImpl> = lazy(() =>
703
+ import('./Custom').then((m) => ({ default: m.CustomRenderer }))
704
+ );
705
+ ```
706
+
707
+ ```tsx
708
+ // src/FilePreviewContent.tsx
709
+ import { CustomRenderer } from './renderers/lazy'; // ✅ Lazy import
710
+ // NOT: import { CustomRenderer } from './renderers/Custom'; // ❌ Direct import breaks code-splitting
711
+
712
+ <Suspense fallback={<RendererLoading />}>
713
+ {fileType === 'custom' && <CustomRenderer ref={rendererRef} url={currentFile.url} />}
714
+ </Suspense>
715
+ ```
716
+
717
+ **For Custom Renderers:**
718
+
719
+ If you want your custom renderer to benefit from code-splitting, you can use the same pattern:
720
+
721
+ ```tsx
722
+ import { lazy, Suspense } from 'react';
723
+
724
+ const MyCustomRenderer = lazy(() => import('./MyCustomRenderer'));
725
+
726
+ <FilePreviewModal
727
+ files={files}
728
+ customRenderers={[
729
+ {
730
+ test: (file) => file.type === 'application/custom',
731
+ component: (props) => (
732
+ <Suspense fallback={<div>Loading...</div>}>
733
+ <MyCustomRenderer {...props} />
734
+ </Suspense>
735
+ )
736
+ }
737
+ ]}
738
+ />
739
+ ```
740
+
741
+ ### i18n Integration
742
+
743
+ Custom renderers can access the library's i18n system via the `useTranslator()` hook for consistent multilingual support.
744
+
745
+ **Architecture:**
746
+
747
+ - **Dictionary Source**: `file-preview-core/src/i18n/messages/` (zh-CN.ts, en-US.ts)
748
+ - **No Hardcoding**: All user-visible text must use translation keys
749
+ - **Automatic Locale Switching**: Follows the `locale` prop passed to `FilePreviewModal`
750
+
751
+ **Usage in Custom Renderers:**
752
+
753
+ ```tsx
754
+ import { useTranslator } from '@eternalheart/react-file-preview';
755
+
756
+ export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
757
+ const t = useTranslator();
758
+ const [error, setError] = useState<string | null>(null);
759
+
760
+ if (error) {
761
+ return (
762
+ <div className="rfp-text-fg-primary">
763
+ {t('custom.load_failed')}: {error}
764
+ </div>
765
+ );
766
+ }
767
+
768
+ return (
769
+ <div>
770
+ <button>{t('common.download')}</button>
771
+ <span>{t('custom.loading')}</span>
772
+ </div>
773
+ );
774
+ });
775
+ ```
776
+
777
+ **Adding Custom Translation Keys:**
778
+
779
+ For custom renderers, extend translations via the `messages` prop (do NOT modify source files in `node_modules`):
780
+
781
+ ```tsx
782
+ <FilePreviewModal
783
+ files={files}
784
+ locale="en-US"
785
+ messages={{
786
+ 'en-US': {
787
+ 'custom.load_failed': 'Failed to load custom file',
788
+ 'custom.file_size': 'File size: {size} KB'
789
+ },
790
+ 'zh-CN': {
791
+ 'custom.load_failed': '自定义文件加载失败',
792
+ 'custom.file_size': '文件大小: {size} KB'
793
+ }
794
+ }}
795
+ customRenderers={[...]}
796
+ />
797
+ ```
798
+
799
+ **Guidelines:**
800
+ - Use `<scope>.<snake_name>` format (e.g., `custom.load_failed`, `custom.parse_error`)
801
+ - Provide translations for all enabled locales (`zh-CN` and `en-US`)
802
+ - Common keys already available: `common.loading`, `common.download`, `common.close`, `toolbar.*`
803
+
804
+ **Parameterized Translations:**
805
+
806
+ ```tsx
807
+ // Dictionary: 'custom.file_size': '文件大小: {size} KB'
808
+ t('custom.file_size', { size: 1024 }) // → "文件大小: 1024 KB"
809
+ ```
810
+
811
+ **Toolbar Integration:**
812
+
813
+ Toolbar items should also use translated strings:
814
+
815
+ ```tsx
816
+ const getToolbarGroups = useCallback((): ToolbarGroup[] => [
817
+ {
818
+ items: [
819
+ {
820
+ type: 'button',
821
+ icon: <Download className="rfp-w-4 rfp-h-4" />,
822
+ tooltip: t('common.download'), // ✅ Translated
823
+ action: handleDownload
824
+ }
825
+ ]
826
+ }
827
+ ], [t]);
828
+ ```
829
+
830
+ ### Theme Adaptation
831
+
832
+ Custom renderers must use semantic color tokens to support the library's `'auto' | 'dark' | 'light'` theme system.
833
+
834
+ **Semantic Token System:**
835
+
836
+ All colors are defined as CSS variables (`--fp-*`) and exposed via Tailwind classes with the `rfp-` prefix:
837
+
838
+ | Usage | Class | Description |
839
+ |-------|-------|-------------|
840
+ | **Text (fg)** | | |
841
+ | Primary text | `rfp-text-fg-primary` | Highest contrast |
842
+ | Body text | `rfp-text-fg-secondary` | Default text |
843
+ | Secondary text | `rfp-text-fg-tertiary` | Captions, counters |
844
+ | Muted text | `rfp-text-fg-muted` | Placeholders |
845
+ | Disabled text | `rfp-text-fg-disabled` | Disabled buttons |
846
+ | **Background (surface)** | | |
847
+ | Surface 1 | `rfp-bg-surface-1` | Cards, weakest |
848
+ | Surface 2 | `rfp-bg-surface-2` | Hover states |
849
+ | Surface 3 | `rfp-bg-surface-3` | Emphasis |
850
+ | Toolbar | `rfp-bg-surface-toolbar` | Top toolbar |
851
+ | **Borders** | | |
852
+ | Weak border | `rfp-border-line-weak` | Subtle lines |
853
+ | Standard border | `rfp-border-line` | Default borders |
854
+ | Strong border | `rfp-border-line-strong` | Emphasis |
855
+ | **Code** | | |
856
+ | Code background | `rfp-bg-code-bg` | Dark: #1e1e1e / Light: #f6f8fa |
857
+ | Code text | `rfp-text-code-fg` | Follows theme |
858
+ | **Accent** | | |
859
+ | Accent background | `rfp-bg-accent` | Primary buttons |
860
+ | Accent hover | `rfp-bg-accent-hover` | Hover state |
861
+
862
+ **✅ Correct Usage:**
863
+
864
+ ```tsx
865
+ export const CustomRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
866
+ return (
867
+ <div className="rfp-bg-surface-1 rfp-border rfp-border-line-weak rfp-rounded">
868
+ <h2 className="rfp-text-fg-primary rfp-text-lg">Title</h2>
869
+ <p className="rfp-text-fg-secondary">Body text</p>
870
+ <button className="rfp-bg-surface-2 hover:rfp-bg-surface-3 rfp-text-fg-primary">
871
+ Click me
872
+ </button>
873
+ <pre className="rfp-bg-code-bg rfp-text-code-fg">
874
+ {code}
875
+ </pre>
876
+ </div>
877
+ );
878
+ });
879
+ ```
880
+
881
+ **❌ Incorrect Usage (DO NOT USE):**
882
+
883
+ ```tsx
884
+ // ❌ Literal color classes — breaks theme switching
885
+ <div className="rfp-text-white/90 rfp-bg-white/10 rfp-border-white/15">
886
+ <div className="rfp-text-gray-700 rfp-bg-gray-100">
887
+
888
+ // ❌ Inline literal colors
889
+ <div style={{ color: '#ffffff', background: '#1f2937' }}>
890
+
891
+ // ❌ Hardcoded dark-only colors
892
+ <div style={{ background: '#1e1e1e' }}> // Use rfp-bg-code-bg instead
893
+ ```
894
+
895
+ **Theme-Aware Third-Party Libraries:**
896
+
897
+ For libraries with theme props (e.g., `react-syntax-highlighter`), use `useResolvedTheme()`:
898
+
899
+ ```tsx
900
+ import { useResolvedTheme } from '@eternalheart/react-file-preview';
901
+ import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
902
+ import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
903
+
904
+ export const CodeRenderer = forwardRef<RendererHandle, Props>((props, ref) => {
905
+ const resolvedTheme = useResolvedTheme(); // 'dark' | 'light'
906
+
907
+ return (
908
+ <SyntaxHighlighter
909
+ language="javascript"
910
+ style={resolvedTheme === 'light' ? vs : vscDarkPlus}
911
+ >
912
+ {code}
913
+ </SyntaxHighlighter>
914
+ );
915
+ });
916
+ ```
917
+
918
+ **Testing:**
919
+
920
+ Always test your custom renderer in both Light and Dark themes:
921
+
922
+ ```tsx
923
+ <FilePreviewModal
924
+ files={files}
925
+ theme="light" // Switch between 'light', 'dark', 'auto'
926
+ customRenderers={[...]}
927
+ />
928
+ ```
929
+
930
+ Verify:
931
+ - Text is readable in both themes (no white-on-white or black-on-black)
932
+ - Borders and dividers are visible
933
+ - Hover states have sufficient contrast
934
+ - Code blocks follow theme (not always dark)
935
+
936
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f3a8.svg" width="20" height="20" alt="🎨" /> Custom Styling
534
937
 
535
938
  The component is built with Tailwind CSS. You can customize styles by overriding CSS variables:
536
939
 
@@ -542,18 +945,18 @@ The component is built with Tailwind CSS. You can customize styles by overriding
542
945
  }
543
946
  ```
544
947
 
545
- ## ⌨️ Keyboard Shortcuts
948
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/2328.svg" width="20" height="20" alt="⌨️" /> Keyboard Shortcuts
546
949
 
547
950
  - `ESC` - Close preview
548
951
  - `←` - Previous file
549
952
  - `→` - Next file
550
953
  - `Mouse Wheel` - Zoom image (image preview only)
551
954
 
552
- ## 📚 Documentation
955
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4da.svg" width="20" height="20" alt="📚" /> Documentation
553
956
 
554
957
  - [Online Demo](https://wh131462.github.io/file-preview) - Live demo
555
958
 
556
- ## 🤖 Context7 Support
959
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f916.svg" width="20" height="20" alt="🤖" /> Context7 Support
557
960
 
558
961
  This project supports [Context7](https://context7.com) MCP Server. If you are using AI coding assistants (such as Claude Code, Cursor, etc.), you can configure the Context7 MCP Server to get the latest documentation and code examples for `@eternalheart/react-file-preview`, enabling a better AI-assisted development experience.
559
962
 
@@ -565,33 +968,7 @@ This project supports [Context7](https://context7.com) MCP Server. If you are us
565
968
 
566
969
  > For more details on configuring Context7, please visit [Context7 official documentation](https://github.com/upstash/context7).
567
970
 
568
- ## 📦 Package Information
569
-
570
- ### Bundle Size
571
-
572
- - **ESM**: ~54 KB (gzipped: ~12 KB)
573
- - **CJS**: ~37 KB (gzipped: ~11 KB)
574
- - **CSS**: ~56 KB (gzipped: ~14 KB)
575
-
576
- ### Peer Dependencies
577
-
578
- - `react`: ^18.0.0
579
- - `react-dom`: ^18.0.0
580
-
581
- ### Exports
582
-
583
- ```json
584
- {
585
- ".": {
586
- "types": "./lib/index.d.ts",
587
- "import": "./lib/index.mjs",
588
- "require": "./lib/index.cjs"
589
- },
590
- "./style.css": "./lib/index.css"
591
- }
592
- ```
593
-
594
- ## 🛠️ Development
971
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f6e0.svg" width="20" height="20" alt="🛠️" /> Development
595
972
 
596
973
  ### For Library Development
597
974
 
@@ -629,15 +1006,15 @@ react-file-preview/
629
1006
  └── vite.config.lib.ts # Library build config
630
1007
  ```
631
1008
 
632
- ## 📄 License
1009
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f4c4.svg" width="20" height="20" alt="📄" /> License
633
1010
 
634
1011
  [MIT](./LICENSE) © [EternalHeart](https://github.com/wh131462)
635
1012
 
636
- ## 🤝 Contributing
1013
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f91d.svg" width="20" height="20" alt="🤝" /> Contributing
637
1014
 
638
1015
  Issues and Pull Requests are welcome!
639
1016
 
640
- ## 🔗 Links
1017
+ ## <img src="https://cdn.jsdelivr.net/gh/twitter/twemoji@latest/assets/svg/1f517.svg" width="20" height="20" alt="🔗" /> Links
641
1018
 
642
1019
  - [GitHub](https://github.com/wh131462/file-preview)
643
1020
  - [npm](https://www.npmjs.com/package/@eternalheart/react-file-preview)