@vryzel/file-next-ui 0.4.0 → 0.4.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.
- package/README.md +196 -11
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,39 +1,224 @@
|
|
|
1
1
|
# @vryzel/file-next-ui
|
|
2
2
|
|
|
3
|
-
Ready-
|
|
3
|
+
Ready-made file explorer for Next.js. Import `FileExplorer`, pass server callbacks, done. Or compose the pieces.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
pnpm add @vryzel/file-next-ui
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Scan the package
|
|
9
|
+
Scan the package so Tailwind keeps the classes:
|
|
10
10
|
|
|
11
11
|
```js
|
|
12
|
-
|
|
12
|
+
// tailwind.config.ts
|
|
13
|
+
content: [
|
|
14
|
+
"./app/**/*.{ts,tsx}",
|
|
15
|
+
"./node_modules/@vryzel/file-next-ui/dist/**/*.{js,mjs}",
|
|
16
|
+
]
|
|
13
17
|
```
|
|
14
18
|
|
|
15
|
-
|
|
19
|
+
Your theme must define the usual shadcn CSS variables (`--background`, `--foreground`, `--border`, `--primary`, `--muted`, `--destructive`, `--card`, `--ring`, …).
|
|
20
|
+
|
|
21
|
+
## Quick path
|
|
16
22
|
|
|
17
23
|
```tsx
|
|
24
|
+
"use client";
|
|
18
25
|
import { FileExplorer } from "@vryzel/file-next-ui";
|
|
19
26
|
|
|
20
27
|
<FileExplorer
|
|
21
|
-
className="h-[70vh] rounded-[10px] border border-border"
|
|
28
|
+
className="h-[70vh] overflow-hidden rounded-[10px] border border-border bg-card"
|
|
22
29
|
tenantId="acme"
|
|
23
|
-
parentId={
|
|
30
|
+
parentId={folderId}
|
|
24
31
|
listFiles={listFiles}
|
|
25
32
|
searchFiles={searchFiles}
|
|
26
33
|
listTrash={listTrash}
|
|
27
34
|
requestUpload={requestUpload}
|
|
28
|
-
actions={
|
|
29
|
-
|
|
35
|
+
actions={{
|
|
36
|
+
deleteFile,
|
|
37
|
+
moveFile,
|
|
38
|
+
copyFile,
|
|
39
|
+
renameFile,
|
|
40
|
+
createFolder,
|
|
41
|
+
restoreNode,
|
|
42
|
+
createShare,
|
|
43
|
+
}}
|
|
44
|
+
onOpenFolder={(folder) => setFolderId(folder.id)}
|
|
45
|
+
breadcrumbs={crumbs}
|
|
46
|
+
onBreadcrumbNavigate={(seg) => setFolderId(seg.id === "root" ? null : seg.id)}
|
|
47
|
+
/>
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
`listFiles` / `searchFiles` / `listTrash` return:
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
Promise<
|
|
54
|
+
| { ok: true; value: { items: FileNode[]; nextCursor?: string } }
|
|
55
|
+
| { ok: false; error: { code: string; message: string } }
|
|
56
|
+
>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Revive dates if they crossed the server action boundary (`new Date(node.updatedAt)`). `FileExplorer` also revives them.
|
|
60
|
+
|
|
61
|
+
Keep callback identities stable (`useCallback` or module scope).
|
|
62
|
+
|
|
63
|
+
## Use cases
|
|
64
|
+
|
|
65
|
+
### 1. Default explorer (batteries)
|
|
66
|
+
|
|
67
|
+
What you get with `FileExplorer`:
|
|
68
|
+
|
|
69
|
+
- List / grid, sort, persisted view (`persistViewKey`)
|
|
70
|
+
- Search, trash, upload (multi-file + OS drop)
|
|
71
|
+
- New folder / rename dialogs (not `window.prompt`)
|
|
72
|
+
- Multi-select, shift-click, ⌘A, copy/paste, drag onto folders
|
|
73
|
+
- Pagination (`limit` 50 + Load more)
|
|
74
|
+
- Quota footer, selection toast, upload queue
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<FileExplorer
|
|
78
|
+
persistViewKey="my-app.files.view"
|
|
79
|
+
usedBytes={used}
|
|
80
|
+
quotaBytes={quota}
|
|
81
|
+
refreshKey={refreshKey}
|
|
82
|
+
onPreview={openPreview}
|
|
83
|
+
onDownload={download}
|
|
84
|
+
extraFileAction={{ label: "Create quote", onSelect: (f) => router.push(`/quotes/new?file=${f.id}`) }}
|
|
85
|
+
protectedIds={linkedIds} // cannot move to trash
|
|
86
|
+
labels={{ search: "Buscar", emptyFolder: "Carpeta vacía" }}
|
|
87
|
+
{...props}
|
|
88
|
+
/>
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Uploads that survive navigation
|
|
92
|
+
|
|
93
|
+
Put the provider on a layout. Unmounting the files page will not kill XHR.
|
|
94
|
+
|
|
95
|
+
```tsx
|
|
96
|
+
// app/(dashboard)/layout.tsx
|
|
97
|
+
import { UploadQueueProvider } from "@vryzel/file-next-ui";
|
|
98
|
+
|
|
99
|
+
<UploadQueueProvider requestUpload={requestUpload} confirmUpload={onUploaded}>
|
|
100
|
+
{children}
|
|
101
|
+
</UploadQueueProvider>
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`FileExplorer` uses the nearest provider; if none exists it creates its own.
|
|
105
|
+
|
|
106
|
+
### 3. Preview dialog
|
|
107
|
+
|
|
108
|
+
```tsx
|
|
109
|
+
import { FileExplorer, FilePreviewDialog, canPreviewFile } from "@vryzel/file-next-ui";
|
|
110
|
+
|
|
111
|
+
const [preview, setPreview] = useState<{ file: FileNode; src: string } | null>(null);
|
|
112
|
+
|
|
113
|
+
<FileExplorer
|
|
114
|
+
onPreview={async (file) => {
|
|
115
|
+
const src = await signedUrl(file);
|
|
116
|
+
if (canPreviewFile(file)) setPreview({ file, src });
|
|
117
|
+
else window.open(src, "_blank");
|
|
118
|
+
}}
|
|
119
|
+
onDownload={async (file) => window.open(await signedUrl(file), "_blank")}
|
|
120
|
+
/>
|
|
121
|
+
<FilePreviewDialog
|
|
122
|
+
file={preview?.file ?? null}
|
|
123
|
+
src={preview?.src ?? ""}
|
|
124
|
+
onClose={() => setPreview(null)}
|
|
125
|
+
onDownload={(file) => void signedUrl(file).then((src) => window.open(src, "_blank"))}
|
|
30
126
|
/>
|
|
31
127
|
```
|
|
32
128
|
|
|
33
|
-
|
|
129
|
+
`src` is yours (presigned GET). The dialog does not call S3.
|
|
130
|
+
|
|
131
|
+
### 4. Compose your own shell
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
import {
|
|
135
|
+
Breadcrumbs,
|
|
136
|
+
ExplorerToolbar,
|
|
137
|
+
ExplorerListView,
|
|
138
|
+
ExplorerGridView,
|
|
139
|
+
EmptyState,
|
|
140
|
+
ErrorState,
|
|
141
|
+
FileIcon,
|
|
142
|
+
} from "@vryzel/file-next-ui";
|
|
143
|
+
import { useFileExplorer } from "@vryzel/file-next-headless";
|
|
144
|
+
|
|
145
|
+
const explorer = useFileExplorer({ listFiles, tenantId, parentId });
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<div className="flex h-[70vh] flex-col rounded-[10px] border">
|
|
149
|
+
<Breadcrumbs segments={crumbs} onNavigate={go} className="px-4 py-3" />
|
|
150
|
+
<ExplorerToolbar
|
|
151
|
+
view={explorer.view}
|
|
152
|
+
onViewChange={explorer.setView}
|
|
153
|
+
query={query}
|
|
154
|
+
onQueryChange={setQuery}
|
|
155
|
+
sortKey={sortKey}
|
|
156
|
+
sortDirection={sortDir}
|
|
157
|
+
onSortChange={setSort}
|
|
158
|
+
/>
|
|
159
|
+
{explorer.status === "error" ? (
|
|
160
|
+
<ErrorState error={explorer.error!} onRetry={explorer.refetch} />
|
|
161
|
+
) : explorer.files.length === 0 ? (
|
|
162
|
+
<EmptyState title="Empty folder" description="Drop files here." />
|
|
163
|
+
) : explorer.view === "list" ? (
|
|
164
|
+
<ExplorerListView files={explorer.files} {...selectionAndDrag} />
|
|
165
|
+
) : (
|
|
166
|
+
<ExplorerGridView files={explorer.files} {...selectionAndDrag} />
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Every piece takes `className`.
|
|
34
173
|
|
|
35
|
-
|
|
174
|
+
### 5. Copy / i18n
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
<FileExplorer
|
|
178
|
+
labels={{
|
|
179
|
+
search: "Buscar",
|
|
180
|
+
upload: "Subir",
|
|
181
|
+
newFolder: "Nueva carpeta",
|
|
182
|
+
emptyFolder: "Esta carpeta está vacía",
|
|
183
|
+
moveToTrash: "Mover a la papelera",
|
|
184
|
+
}}
|
|
185
|
+
/>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Partial overlay on English defaults. Or wrap several pieces:
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
import { ExplorerLabelsProvider, defaultLabels } from "@vryzel/file-next-ui";
|
|
192
|
+
|
|
193
|
+
<ExplorerLabelsProvider labels={{ search: "Buscar" }}>
|
|
194
|
+
<ExplorerToolbar ... />
|
|
195
|
+
</ExplorerLabelsProvider>
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### 6. Icons only
|
|
199
|
+
|
|
200
|
+
```tsx
|
|
201
|
+
import { FileIcon } from "@vryzel/file-next-ui";
|
|
202
|
+
<FileIcon kind={file.kind} mimeType={file.mimeType} className="size-6" />
|
|
203
|
+
```
|
|
36
204
|
|
|
37
205
|
## Pieces
|
|
38
206
|
|
|
39
|
-
|
|
207
|
+
| Export | Role |
|
|
208
|
+
|---|---|
|
|
209
|
+
| `FileExplorer` | Default composed explorer |
|
|
210
|
+
| `ExplorerToolbar` | Search, upload, new folder, sort, trash, list/grid |
|
|
211
|
+
| `ExplorerListView` / `ExplorerGridView` | Rows / cards + drag |
|
|
212
|
+
| `ExplorerContextMenu` | Right-click / overflow menu |
|
|
213
|
+
| `Breadcrumbs` | Path |
|
|
214
|
+
| `EmptyState` / `ErrorState` | Empty / error |
|
|
215
|
+
| `FileIcon` | Mime icon |
|
|
216
|
+
| `FilePreviewDialog` | Image/pdf/video/text overlay |
|
|
217
|
+
| `CreateFolderDialog` / `ConfirmDeleteDialog` | Modals |
|
|
218
|
+
| `ExplorerSelectionToast` / `ExplorerClipboardToast` | Multi-select / copy |
|
|
219
|
+
| `UploadQueueProvider` / `ExplorerUploadStatus` | Upload queue |
|
|
220
|
+
| `defaultLabels` | English copy |
|
|
221
|
+
|
|
222
|
+
## Not this package
|
|
223
|
+
|
|
224
|
+
Server wiring → `@vryzel/file-next`. Hooks without UI → `@vryzel/file-next-headless`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vryzel/file-next-ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "Ready-to-use file explorer UI for file-next. Tailwind classes, composable pieces, and a default FileExplorer.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"tsup": "^8.3.5",
|
|
49
49
|
"typescript": "^5.6.3",
|
|
50
50
|
"vitest": "^2.1.5",
|
|
51
|
-
"@vryzel/file-next": "0.4.
|
|
52
|
-
"@vryzel/file-next
|
|
51
|
+
"@vryzel/file-next-headless": "0.4.1",
|
|
52
|
+
"@vryzel/file-next": "0.4.1"
|
|
53
53
|
},
|
|
54
54
|
"keywords": [
|
|
55
55
|
"react",
|