@plugable-io/react 0.0.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 (2) hide show
  1. package/README.md +527 -0
  2. package/package.json +50 -0
package/README.md ADDED
@@ -0,0 +1,527 @@
1
+ # @plugable-io/react
2
+
3
+ React components and hooks for the Plugable File Management API.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @plugable-io/react
9
+ ```
10
+
11
+ ## Components & Hooks
12
+
13
+ | Export | Type | Description |
14
+ |--------|------|-------------|
15
+ | `PlugableProvider` | Component | Context provider for configuration |
16
+ | `Dropzone` | Component | Drag & drop file uploader |
17
+ | `FilePreview` | Component | Universal file preview (images + file type icons) |
18
+ | `FileImage` | Component | Image display with caching |
19
+ | `useFiles` | Hook | File fetching with pagination |
20
+ | `usePlugable` | Hook | Access client and context |
21
+
22
+ ## Quick Start
23
+
24
+ ### Provider Setup
25
+
26
+ Wrap your app with `PlugableProvider`. You can provide a `getToken` function or use a supported auth provider.
27
+
28
+ #### With Clerk
29
+
30
+ ```tsx
31
+ import { PlugableProvider } from '@plugable-io/react';
32
+
33
+ function App() {
34
+ return (
35
+ <PlugableProvider
36
+ bucketId="your-bucket-id"
37
+ authProvider="clerk"
38
+ clerkJWTTemplate="plugable" // optional
39
+ >
40
+ <YourApp />
41
+ </PlugableProvider>
42
+ );
43
+ }
44
+ ```
45
+
46
+ #### With Supabase
47
+
48
+ ```tsx
49
+ <PlugableProvider
50
+ bucketId="your-bucket-id"
51
+ authProvider="supabase"
52
+ >
53
+ <YourApp />
54
+ </PlugableProvider>
55
+ ```
56
+
57
+ #### With Custom getToken
58
+
59
+ ```tsx
60
+ <PlugableProvider
61
+ bucketId="your-bucket-id"
62
+ getToken={async () => {
63
+ return await getAuthToken();
64
+ }}
65
+ >
66
+ <YourApp />
67
+ </PlugableProvider>
68
+ ```
69
+
70
+ ---
71
+
72
+ ## Dropzone
73
+
74
+ File uploader with drag-and-drop support. Works with default UI or custom render function.
75
+
76
+ ### Default UI
77
+
78
+ ```tsx
79
+ import { Dropzone } from '@plugable-io/react';
80
+
81
+ function ProfileUploader() {
82
+ return (
83
+ <Dropzone
84
+ metadata={{ category: 'avatar', userId: '123' }}
85
+ accept="image/*"
86
+ maxFiles={1}
87
+ onUploadComplete={(files) => {
88
+ console.log('Avatar uploaded:', files[0]);
89
+ }}
90
+ onUploadError={(error) => {
91
+ console.error('Upload failed:', error);
92
+ }}
93
+ />
94
+ );
95
+ }
96
+ ```
97
+
98
+ ### Custom UI with Render Props
99
+
100
+ ```tsx
101
+ import { Dropzone } from '@plugable-io/react';
102
+
103
+ function DocumentUploader() {
104
+ return (
105
+ <Dropzone
106
+ metadata={{ type: 'document' }}
107
+ accept=".pdf,.doc,.docx"
108
+ maxFiles={10}
109
+ onUploadComplete={(files) => console.log('Uploaded:', files)}
110
+ >
111
+ {({ isDragActive, isUploading, uploadProgress, openFileDialog, uploadedFiles }) => (
112
+ <div
113
+ onClick={openFileDialog}
114
+ className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer
115
+ ${isDragActive ? 'border-blue-500 bg-blue-50' : 'border-gray-300'}`}
116
+ >
117
+ {isUploading ? (
118
+ <div className="space-y-2">
119
+ <p className="font-medium">Uploading...</p>
120
+ {Object.entries(uploadProgress).map(([name, progress]) => (
121
+ <div key={name} className="text-sm">
122
+ <span>{name}</span>
123
+ <div className="w-full bg-gray-200 rounded h-2 mt-1">
124
+ <div
125
+ className="bg-blue-500 h-2 rounded transition-all"
126
+ style={{ width: `${progress}%` }}
127
+ />
128
+ </div>
129
+ </div>
130
+ ))}
131
+ </div>
132
+ ) : (
133
+ <>
134
+ <p className="font-medium">
135
+ {isDragActive ? 'Drop files here' : 'Drag & drop documents'}
136
+ </p>
137
+ <p className="text-sm text-gray-500 mt-1">or click to browse</p>
138
+ </>
139
+ )}
140
+
141
+ {uploadedFiles.length > 0 && (
142
+ <div className="mt-4 text-sm text-green-600">
143
+ ✓ {uploadedFiles.length} file(s) uploaded
144
+ </div>
145
+ )}
146
+ </div>
147
+ )}
148
+ </Dropzone>
149
+ );
150
+ }
151
+ ```
152
+
153
+ ### Props
154
+
155
+ | Prop | Type | Description |
156
+ |------|------|-------------|
157
+ | `bucketId` | `string` | Override default bucket ID |
158
+ | `metadata` | `Record<string, any>` | Metadata to attach to uploaded files |
159
+ | `accept` | `string` | File types to accept (e.g., `"image/*"`, `".pdf,.doc"`) |
160
+ | `maxFiles` | `number` | Maximum number of files |
161
+ | `onUploadComplete` | `(files: FileObject[]) => void` | Called when uploads complete |
162
+ | `onUploadError` | `(error: Error) => void` | Called on upload error |
163
+ | `onProgressUpdate` | `(fileName: string, progress: number) => void` | Progress callback |
164
+ | `children` | `(props: DropzoneRenderProps) => ReactNode` | Custom render function |
165
+ | `className` | `string` | CSS class name |
166
+ | `style` | `CSSProperties` | Inline styles |
167
+
168
+ ---
169
+
170
+ ## useFiles Hook
171
+
172
+ Fetch and paginate files with automatic refresh on uploads.
173
+
174
+ ### Basic Usage
175
+
176
+ ```tsx
177
+ import { useFiles, FilePreview } from '@plugable-io/react';
178
+
179
+ function InvoiceList() {
180
+ const { files, isLoading, pagination, refresh } = useFiles({
181
+ metadata: { type: 'invoice' },
182
+ perPage: 10,
183
+ });
184
+
185
+ if (isLoading) return <div>Loading...</div>;
186
+
187
+ return (
188
+ <div>
189
+ <div className="grid grid-cols-4 gap-4">
190
+ {files.map((file) => (
191
+ <div key={file.id} className="border rounded p-3">
192
+ <FilePreview file={file} width={60} height={60} />
193
+ <p className="text-sm mt-2 truncate">{file.name}</p>
194
+ <div className="flex gap-2 mt-2">
195
+ <button onClick={() => file.delete()}>Delete</button>
196
+ <a href={file.download_url} download>Download</a>
197
+ </div>
198
+ </div>
199
+ ))}
200
+ </div>
201
+
202
+ <div className="flex gap-2 mt-4">
203
+ <button
204
+ onClick={pagination.loadPreviousPage}
205
+ disabled={!pagination.hasPrevious}
206
+ >
207
+ Previous
208
+ </button>
209
+ <span>Page {pagination.current}</span>
210
+ <button
211
+ onClick={pagination.loadNextPage}
212
+ disabled={!pagination.hasNext}
213
+ >
214
+ Next
215
+ </button>
216
+ </div>
217
+ </div>
218
+ );
219
+ }
220
+ ```
221
+
222
+ ### Filter by Media Type
223
+
224
+ ```tsx
225
+ function ImageGallery() {
226
+ const { files, isLoading } = useFiles({
227
+ mediaType: 'image',
228
+ perPage: 20,
229
+ });
230
+
231
+ return (
232
+ <div className="grid grid-cols-3 gap-4">
233
+ {files.map((file) => (
234
+ <FilePreview key={file.id} file={file} width="100%" height={200} objectFit="cover" />
235
+ ))}
236
+ </div>
237
+ );
238
+ }
239
+ ```
240
+
241
+ ### Manual Loading
242
+
243
+ ```tsx
244
+ function LazyFileList() {
245
+ const { files, isLoading, refresh } = useFiles({
246
+ metadata: { folder: 'reports' },
247
+ autoLoad: false, // Don't load on mount
248
+ });
249
+
250
+ return (
251
+ <div>
252
+ <button onClick={refresh}>Load Files</button>
253
+ {isLoading && <span>Loading...</span>}
254
+ {files.map((file) => (
255
+ <div key={file.id}>{file.name}</div>
256
+ ))}
257
+ </div>
258
+ );
259
+ }
260
+ ```
261
+
262
+ ### Options
263
+
264
+ | Option | Type | Default | Description |
265
+ |--------|------|---------|-------------|
266
+ | `metadata` | `Record<string, any>` | - | Filter files by metadata |
267
+ | `mediaType` | `string` | - | Filter by media type (`image`, `video`, etc.) |
268
+ | `perPage` | `number` | `20` | Files per page |
269
+ | `startPage` | `number` | `1` | Initial page number |
270
+ | `autoLoad` | `boolean` | `true` | Fetch on mount |
271
+
272
+ ### Return Value
273
+
274
+ | Property | Type | Description |
275
+ |----------|------|-------------|
276
+ | `files` | `FileObject[]` | Array of files |
277
+ | `isLoading` | `boolean` | Loading state |
278
+ | `pagination.current` | `number` | Current page |
279
+ | `pagination.hasNext` | `boolean` | Has next page |
280
+ | `pagination.hasPrevious` | `boolean` | Has previous page |
281
+ | `pagination.loadNextPage` | `() => void` | Go to next page |
282
+ | `pagination.loadPreviousPage` | `() => void` | Go to previous page |
283
+ | `setPage` | `(page: number) => void` | Jump to specific page |
284
+ | `refresh` | `() => Promise<void>` | Refresh current page |
285
+
286
+ ---
287
+
288
+ ## FilePreview
289
+
290
+ Universal file preview component. Displays images inline and shows file type icons for other files.
291
+
292
+ ### Basic Usage
293
+
294
+ ```tsx
295
+ import { FilePreview } from '@plugable-io/react';
296
+
297
+ function FileCard({ file }) {
298
+ return (
299
+ <div className="flex items-center gap-3 p-3 border rounded">
300
+ <FilePreview file={file} width={48} height={48} />
301
+ <div>
302
+ <p className="font-medium">{file.name}</p>
303
+ <p className="text-sm text-gray-500">{file.content_type}</p>
304
+ </div>
305
+ </div>
306
+ );
307
+ }
308
+ ```
309
+
310
+ ### Custom Non-Image Rendering
311
+
312
+ ```tsx
313
+ import { FilePreview } from '@plugable-io/react';
314
+
315
+ function CustomFilePreview({ file }) {
316
+ return (
317
+ <FilePreview
318
+ file={file}
319
+ width={80}
320
+ height={80}
321
+ objectFit="cover"
322
+ renderNonImage={(file) => (
323
+ <div className="flex flex-col items-center justify-center text-gray-500">
324
+ <span className="text-2xl">📄</span>
325
+ <span className="text-xs mt-1">{file.name.split('.').pop()}</span>
326
+ </div>
327
+ )}
328
+ />
329
+ );
330
+ }
331
+ ```
332
+
333
+ ### Props
334
+
335
+ | Prop | Type | Default | Description |
336
+ |------|------|---------|-------------|
337
+ | `file` | `FileObject` | required | File to preview |
338
+ | `width` | `number \| string` | `80` | Preview width |
339
+ | `height` | `number \| string` | `80` | Preview height |
340
+ | `objectFit` | `'contain' \| 'cover' \| 'fill' \| 'none' \| 'scale-down'` | `'cover'` | Image fit mode |
341
+ | `className` | `string` | - | CSS class name |
342
+ | `style` | `CSSProperties` | - | Inline styles |
343
+ | `showExtension` | `boolean` | `true` | Show file extension for non-images |
344
+ | `renderNonImage` | `(file: FileObject) => ReactNode` | - | Custom renderer for non-image files |
345
+
346
+ ---
347
+
348
+ ## FileImage
349
+
350
+ Display images with automatic URL fetching and caching.
351
+
352
+ ```tsx
353
+ import { FileImage } from '@plugable-io/react';
354
+
355
+ function Avatar({ file }) {
356
+ return (
357
+ <FileImage
358
+ file={file}
359
+ width={100}
360
+ height={100}
361
+ objectFit="cover"
362
+ borderRadius={50}
363
+ alt="User avatar"
364
+ onLoad={() => console.log('Loaded')}
365
+ />
366
+ );
367
+ }
368
+ ```
369
+
370
+ ---
371
+
372
+ ## File Object Methods
373
+
374
+ Each `FileObject` provides built-in methods for updating, downloading, and deleting files:
375
+
376
+ ```tsx
377
+ // Update file metadata or name
378
+ await file.update(
379
+ { tags: ['important', 'reviewed'] }, // metadata (optional)
380
+ 'new-filename.txt' // name (optional)
381
+ );
382
+
383
+ // Download the file (initiates browser download)
384
+ await file.download();
385
+
386
+ // Delete the file
387
+ await file.delete();
388
+
389
+ // Access properties
390
+ console.log(file.id); // Unique ID
391
+ console.log(file.name); // Filename
392
+ console.log(file.content_type); // MIME type
393
+ console.log(file.download_url); // Signed download URL
394
+ console.log(file.metadata); // Custom metadata
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Full Example
400
+
401
+ Complete file manager with upload, list, and preview:
402
+
403
+ ```tsx
404
+ import {
405
+ PlugableProvider,
406
+ Dropzone,
407
+ useFiles,
408
+ FilePreview
409
+ } from '@plugable-io/react';
410
+
411
+ function FileManager() {
412
+ const { files, isLoading, pagination, refresh } = useFiles({
413
+ perPage: 12,
414
+ });
415
+
416
+ return (
417
+ <div className="max-w-4xl mx-auto p-6">
418
+ <h1 className="text-2xl font-bold mb-6">File Manager</h1>
419
+
420
+ {/* Upload Section */}
421
+ <Dropzone
422
+ maxFiles={5}
423
+ onUploadComplete={(uploaded) => {
424
+ console.log('Uploaded:', uploaded);
425
+ // Files list auto-refreshes via event system
426
+ }}
427
+ className="mb-8"
428
+ />
429
+
430
+ {/* Files Grid */}
431
+ {isLoading ? (
432
+ <div className="text-center py-8">Loading...</div>
433
+ ) : (
434
+ <div className="grid grid-cols-4 gap-4">
435
+ {files.map((file) => (
436
+ <div key={file.id} className="border rounded-lg p-3">
437
+ <FilePreview
438
+ file={file}
439
+ width="100%"
440
+ height={120}
441
+ objectFit="cover"
442
+ />
443
+ <p className="text-sm mt-2 truncate" title={file.name}>
444
+ {file.name}
445
+ </p>
446
+ <div className="flex gap-2 mt-2">
447
+ <a
448
+ href={file.download_url}
449
+ download
450
+ className="text-blue-500 text-sm"
451
+ >
452
+ Download
453
+ </a>
454
+ <button
455
+ onClick={() => file.delete()}
456
+ className="text-red-500 text-sm"
457
+ >
458
+ Delete
459
+ </button>
460
+ </div>
461
+ </div>
462
+ ))}
463
+ </div>
464
+ )}
465
+
466
+ {/* Pagination */}
467
+ <div className="flex justify-center gap-4 mt-6">
468
+ <button
469
+ onClick={pagination.loadPreviousPage}
470
+ disabled={!pagination.hasPrevious}
471
+ className="px-4 py-2 border rounded disabled:opacity-50"
472
+ >
473
+ Previous
474
+ </button>
475
+ <span className="py-2">Page {pagination.current}</span>
476
+ <button
477
+ onClick={pagination.loadNextPage}
478
+ disabled={!pagination.hasNext}
479
+ className="px-4 py-2 border rounded disabled:opacity-50"
480
+ >
481
+ Next
482
+ </button>
483
+ </div>
484
+ </div>
485
+ );
486
+ }
487
+
488
+ // App wrapper
489
+ function App() {
490
+ return (
491
+ <PlugableProvider
492
+ bucketId="your-bucket-id"
493
+ authProvider="clerk"
494
+ >
495
+ <FileManager />
496
+ </PlugableProvider>
497
+ );
498
+ }
499
+ ```
500
+
501
+ ---
502
+
503
+ ## TypeScript
504
+
505
+ All types are exported:
506
+
507
+ ```tsx
508
+ import type {
509
+ PlugableProviderProps,
510
+ AuthProvider,
511
+ DropzoneProps,
512
+ DropzoneRenderProps,
513
+ FilePreviewProps,
514
+ FileImageProps,
515
+ FileListProps,
516
+ FileListRenderProps,
517
+ UseFilesOptions,
518
+ UseFilesResult,
519
+ FileObject,
520
+ SearchOptions,
521
+ UpdateOptions,
522
+ } from '@plugable-io/react';
523
+ ```
524
+
525
+ ## License
526
+
527
+ MIT
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@plugable-io/react",
3
+ "version": "0.0.1",
4
+ "description": "React components and hooks for Plugable File Management API",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom",
13
+ "dev": "tsup src/index.ts --format cjs,esm --dts --watch --external react --external react-dom",
14
+ "lint": "tsc --noEmit"
15
+ },
16
+ "keywords": [
17
+ "react",
18
+ "file-upload",
19
+ "dropzone",
20
+ "storage",
21
+ "file-management",
22
+ "cloud-storage",
23
+ "typescript",
24
+ "hooks",
25
+ "components",
26
+ "plugable"
27
+ ],
28
+ "author": "",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/Plugable-IO/react.git"
33
+ },
34
+ "peer Dependencies": {
35
+ "react": ">=16.8.0",
36
+ "react-dom": ">=16.8.0"
37
+ },
38
+ "dependencies": {
39
+ "@plugable-io/js": "^0.0.8"
40
+ },
41
+ "devDependencies": {
42
+ "@types/node": "^20.0.0",
43
+ "@types/react": "^18.0.0",
44
+ "@types/react-dom": "^18.0.0",
45
+ "react": "^18.0.0",
46
+ "react-dom": "^18.0.0",
47
+ "tsup": "^8.0.0",
48
+ "typescript": "^5.0.0"
49
+ }
50
+ }