@unciatech/file-manager 0.0.32 → 0.0.33

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # 🗂️ File Manager
2
2
 
3
- A robust, production-ready React / Next.js file management system designed to mirror the capabilities of professional asset managers (like Google Drive, macOS Finder, or Strapi Media Library).
3
+ A robust, production-ready file management component for any **React** application works with Vite, Next.js, Remix, CRA, and more.
4
4
 
5
5
  It supports deep folder nesting, drag-and-drop file uploads, metadata management for various file types (Images, Videos, Audio, Documents), unified grid layouts, and fully optimized loading states.
6
6
 
@@ -14,7 +14,7 @@ It supports deep folder nesting, drag-and-drop file uploads, metadata management
14
14
  - **Graceful Error Handling**: Resilient `<FileManagerErrorBoundary>` that captures catastrophic failures and allows users to hard-reload safely without app crashes.
15
15
 
16
16
  ## 🛠️ Tech Stack
17
- - **Framework**: Next.js / React
17
+ - **Framework**: React (any — Vite, Next.js, Remix, CRA)
18
18
  - **Styling**: Tailwind CSS
19
19
  - **Icons**: Custom SVG Icons
20
20
  - **Notifications**: Sonner
@@ -22,9 +22,32 @@ It supports deep folder nesting, drag-and-drop file uploads, metadata management
22
22
  > [!WARNING]
23
23
  > This library is currently in **BETA**. Please report any bugs or feature requests on the GitHub issues page.
24
24
 
25
- ## 🚀 How to Install and Use in Your Project
25
+ ## Setup & Requirements
26
26
 
27
- If you want to integrate this File Manager into your own Next.js or React application, follow this step-by-step guide.
27
+ Because this library relies heavily on Tailwind CSS for minimal zero-config styling, ensure your app is configured to scan the library's components for Tailwind utility classes.
28
+
29
+ ### Tailwind v4
30
+ If you are using **Tailwind CSS v4** (like in newer Next.js or Vite projects), add this to your main CSS file (`globals.css` or `index.css`). This pulls in the required theme configuration and ensures the library is scanned:
31
+
32
+ ```css
33
+ @import "tailwindcss";
34
+ @import "@unciatech/file-manager/styles";
35
+ @source "../node_modules/@unciatech/file-manager";
36
+ ```
37
+
38
+ ### Tailwind v3
39
+ If you are still on **Tailwind v3**, add the library path to your `tailwind.config.ts`:
40
+
41
+ ```ts
42
+ content: [
43
+ // ... your other paths
44
+ "./node_modules/@unciatech/file-manager/dist/**/*.{js,ts,jsx,tsx}",
45
+ ],
46
+ ```
47
+
48
+ ## Basic Usage in Your Project
49
+
50
+ If you want to integrate this File Manager into your own React application (Next.js, Vite, Remix, CRA, etc.), follow this step-by-step guide.
28
51
 
29
52
  ### Step 1: Install the Package
30
53
 
@@ -46,30 +69,6 @@ The init script includes this automatically, but if you are installing manually,
46
69
  import '@unciatech/file-manager/styles';
47
70
  ```
48
71
 
49
- **(CRITICAL) Configure Tailwind CSS:**
50
- Because this library uses Tailwind CSS, you MUST tell your Tailwind compiler to scan the library components for utility classes, otherwise it will render with zero styles!
51
-
52
- **For Tailwind v3 (`tailwind.config.ts`):**
53
- ```typescript
54
- import type { Config } from "tailwindcss";
55
-
56
- const config: Config = {
57
- content: [
58
- // Your existing paths...
59
- "./node_modules/@unciatech/file-manager/dist/**/*.js",
60
- "./node_modules/@unciatech/file-manager/dist/**/*.mjs",
61
- ],
62
- // ...
63
- };
64
- export default config;
65
- ```
66
-
67
- **For Tailwind v4 (`globals.css`):**
68
- ```css
69
- @import "tailwindcss";
70
- @source "../node_modules/@unciatech/file-manager/dist";
71
- ```
72
-
73
72
  ### Step 2: Create your Custom API Provider
74
73
 
75
74
  The file manager is completely agnostic to your backend database. You simply need to create a class that implements the `IFileManagerProvider` interface.
@@ -129,10 +128,11 @@ export class MyCustomApiProvider implements IFileManagerProvider {
129
128
  > If you are just prototyping and don't have a backend ready yet, you can skip Step 2 entirely! We included a fully functional `MockProvider` that fakes network latency and stores data in memory. Just import it and use it right away to see the UI in action.
130
129
 
131
130
  // app/media/page.tsx
132
- import { FileManagerProvider } from "@/context/file-manager-context";
133
- import { FileManager } from "@/components/file-manager";
134
- import { MockProvider } from "@/providers/mock-provider";
131
+ import { FileManager, MockProvider } from "@unciatech/file-manager";
132
+ import "@unciatech/file-manager/styles";
135
133
 
134
+ // **Tailwind v4 Users:** Make sure your `globals.css` or `index.css` includes:
135
+ // @source "../node_modules/@unciatech/file-manager";
136
136
  // OR import your real provider
137
137
  import { MyCustomApiProvider } from "@/lib/my-api-provider";
138
138
 
@@ -148,7 +148,7 @@ export default function MediaLibraryPage() {
148
148
  allowedFileTypes={["images", "videos", "audios", "files"]}
149
149
  provider={apiProvider}
150
150
  >
151
- <FileManager />
151
+ <FileManager basePath="/media" />
152
152
  </FileManagerProvider>
153
153
  </div>
154
154
  );
@@ -157,6 +157,99 @@ export default function MediaLibraryPage() {
157
157
 
158
158
  ---
159
159
 
160
+ ## 🔀 Framework Router Integration
161
+
162
+ By default, the file manager uses the browser's native `history.pushState` API for navigation — no full page reloads, no dependencies. This works out of the box in any bare React app (Vite, CRA, etc.).
163
+
164
+ However, if your app already has its own router (React Router, Next.js, TanStack Router), you should pass the `onNavigate` prop so the file manager delegates all navigation to your router instead of calling `history.pushState` directly. This keeps your router's internal state in sync.
165
+
166
+ ### React Router v6
167
+
168
+ ```tsx
169
+ import { useNavigate } from 'react-router-dom';
170
+
171
+ function MyPage() {
172
+ const navigate = useNavigate();
173
+
174
+ return (
175
+ <FileManager
176
+ provider={provider}
177
+ allowedFileTypes={['images', 'videos']}
178
+ onNavigate={(url) => navigate(url)}
179
+ basePath="/media"
180
+ />
181
+ );
182
+ }
183
+ ```
184
+
185
+ ### Next.js (App Router)
186
+
187
+ ```tsx
188
+ 'use client';
189
+ import { useRouter } from 'next/navigation';
190
+
191
+ export default function MediaPage() {
192
+ const router = useRouter();
193
+
194
+ return (
195
+ <FileManager
196
+ provider={provider}
197
+ allowedFileTypes={['images', 'videos']}
198
+ onNavigate={(url) => router.push(url)}
199
+ basePath="/media"
200
+ />
201
+ );
202
+ }
203
+ ```
204
+
205
+ ### TanStack Router
206
+
207
+ ```tsx
208
+ import { useRouter } from '@tanstack/react-router';
209
+
210
+ function MyPage() {
211
+ const router = useRouter();
212
+
213
+ return (
214
+ <FileManager
215
+ provider={provider}
216
+ allowedFileTypes={['images', 'videos']}
217
+ onNavigate={(url) => router.navigate({ href: url })}
218
+ basePath="/media"
219
+ />
220
+ );
221
+ }
222
+ ```
223
+
224
+ ```tsx
225
+ <FileManager
226
+ provider={provider}
227
+ allowedFileTypes={['images', 'videos']}
228
+ basePath="/media"
229
+ // no onNavigate needed
230
+ />
231
+ ```
232
+
233
+ ## 🎮 Playgrounds & Reference Implementations
234
+
235
+ For complete, working examples of how to integrate the File Manager into different application architectures, check out the [playgrounds](file:///Users/avi/Developer/Uncia/file-manager/playgrounds) directory. These are fully functional Vite-based projects that demonstrate real-world integration patterns.
236
+
237
+ - **[React Router v7 Playground](file:///Users/avi/Developer/Uncia/file-manager/playgrounds/test-react-router)**: Demonstrates integration with `react-router-dom` using `useNavigate` and route-based navigation.
238
+ - **[TanStack Router Playground](file:///Users/avi/Developer/Uncia/file-manager/playgrounds/test-tanstack)**: Demonstrates integration with `@tanstack/react-router` using the `router` object and `href` based navigation.
239
+
240
+ These playgrounds are a great starting point for seeing how to handle:
241
+ - Styling with Tailwind CSS v4
242
+ - Mapping `onNavigate` to your framework's router
243
+ - Using the `MockProvider` for rapid prototyping
244
+ - Configuring `FileManagerModal` v/s full-page `FileManager`
245
+
246
+ ---
247
+
248
+ > [!NOTE]
249
+ > The `onNavigate` prop is also available on `<FileManagerModal>` for modal mode.
250
+
251
+ ---
252
+
160
253
  ## 💾 Database Schema Design
161
254
 
162
255
  Because this application relies heavily on tree structures (Folders inside Folders) and varied JSON metadata (Video durations vs Document page counts), using a relational database with JSONB support (like PostgreSQL) is highly recommended.
package/dist/cli.cjs CHANGED
@@ -24,25 +24,27 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // cli.ts
27
- var fs = __toESM(require("fs"), 1);
28
- var path = __toESM(require("path"), 1);
27
+ var import_fs = __toESM(require("fs"), 1);
28
+ var import_path = __toESM(require("path"), 1);
29
29
  var import_child_process = require("child_process");
30
- var readline = __toESM(require("readline"), 1);
30
+ var import_readline = __toESM(require("readline"), 1);
31
31
  var args = process.argv.slice(2);
32
32
  var command = args[0];
33
33
  var projectName = args[1];
34
- var rl = readline.createInterface({
34
+ var rl = import_readline.default.createInterface({
35
35
  input: process.stdin,
36
36
  output: process.stdout
37
37
  });
38
- var askQuestion = (query) => new Promise((resolve) => rl.question(query, resolve));
39
- var TEMPLATE = `"use client";
38
+ var askQuestion = (query) => {
39
+ return new Promise((resolve) => rl.question(query, resolve));
40
+ };
41
+ var getTemplate = (basePath = "/media") => `"use client";
40
42
 
41
- import React, { Suspense, useMemo } from "react";
43
+ import React, { Suspense } from "react";
42
44
  import { FileManager, MockProvider } from "@unciatech/file-manager";
43
45
 
44
46
  export default function FileManagerDemo() {
45
- const provider = useMemo(() => new MockProvider(), []);
47
+ const mockProvider = new MockProvider();
46
48
 
47
49
  return (
48
50
  <div className="h-screen w-full">
@@ -50,7 +52,8 @@ export default function FileManagerDemo() {
50
52
  <FileManager
51
53
  allowedFileTypes={["audios", "videos", "images", "files"]}
52
54
  viewMode="grid"
53
- provider={provider}
55
+ provider={mockProvider}
56
+ basePath="${basePath}"
54
57
  />
55
58
  </Suspense>
56
59
  </div>
@@ -63,30 +66,39 @@ async function main() {
63
66
  process.exit(0);
64
67
  }
65
68
  if (!projectName) {
66
- console.log("\u{1F680} Generating <FileManagerDemo /> component...");
69
+ console.log("\u{1F680} Generating <FileManagerDemo /> component in the current project...");
67
70
  let targetDir2 = process.cwd();
68
- if (fs.existsSync(path.join(process.cwd(), "src/components"))) {
69
- targetDir2 = path.join(process.cwd(), "src/components");
70
- } else if (fs.existsSync(path.join(process.cwd(), "components"))) {
71
- targetDir2 = path.join(process.cwd(), "components");
71
+ if (import_fs.default.existsSync(import_path.default.join(process.cwd(), "components"))) {
72
+ targetDir2 = import_path.default.join(process.cwd(), "components");
73
+ } else if (import_fs.default.existsSync(import_path.default.join(process.cwd(), "src", "components"))) {
74
+ targetDir2 = import_path.default.join(process.cwd(), "src", "components");
75
+ }
76
+ const targetFile = import_path.default.join(targetDir2, "FileManagerDemo.tsx");
77
+ if (import_fs.default.existsSync(targetFile)) {
78
+ console.error(`\u274C Error: ${targetFile} already exists.`);
79
+ process.exit(1);
72
80
  }
73
- const file = path.join(targetDir2, "FileManagerDemo.tsx");
74
- fs.writeFileSync(file, TEMPLATE);
75
- console.log(`\u2705 Created ${file}`);
81
+ import_fs.default.writeFileSync(targetFile, getTemplate("/"), "utf-8");
82
+ console.log(`\u2705 Success! Created ${targetFile}`);
83
+ console.log("");
84
+ console.log("You can now import and render <FileManagerDemo /> anywhere in your application.");
85
+ console.log("Don't forget to configure your Tailwind CSS content to scan the library for styles!");
76
86
  process.exit(0);
77
87
  }
78
88
  console.log(`
79
- \u{1F680} Initializing project: ${projectName}
89
+ \u{1F680} Initializing a new application: ${projectName}
80
90
  `);
81
- console.log("Choose framework:");
82
- console.log("1) Next.js");
83
- console.log("2) Vite (React)");
84
- console.log("3) Cancel");
85
- const choice = await askQuestion("\nSelect option (1-3): ");
86
- rl.close();
87
- const targetDir = path.join(process.cwd(), projectName);
88
- if (fs.existsSync(targetDir)) {
89
- console.error("\u274C Directory already exists");
91
+ console.log("Which framework would you like to use?");
92
+ console.log(" 1) Next.js (App Router, Tailwind v4)");
93
+ console.log(" 2) Vite (React, Tailwind v4)");
94
+ console.log(" 3) Cancel");
95
+ const choice = await askQuestion("\nSelect an option (1-3): ");
96
+ const targetDir = import_path.default.join(process.cwd(), projectName);
97
+ if (import_fs.default.existsSync(targetDir)) {
98
+ console.error(`
99
+ \u274C Error: Directory "${projectName}" already exists in ${process.cwd()}.`);
100
+ console.error(` Please choose a different project name or delete the existing directory first.`);
101
+ rl.close();
90
102
  process.exit(1);
91
103
  }
92
104
  try {
@@ -96,34 +108,40 @@ async function main() {
96
108
  await scaffoldVite(projectName, targetDir);
97
109
  } else {
98
110
  console.log("Canceled.");
111
+ process.exit(0);
99
112
  }
100
- } catch (e) {
101
- console.error("\u274C Error:", e);
113
+ } catch (error) {
114
+ console.error("\n\u274C Scaffolding failed:", error);
115
+ process.exit(1);
102
116
  }
117
+ process.exit(0);
103
118
  }
104
119
  async function scaffoldNextjs(projectName2, targetDir) {
105
- console.log("\n\u{1F4E6} Creating Next.js app...");
106
- (0, import_child_process.execSync)(
107
- `npx create-next-app@latest ${projectName2} --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm`,
108
- { stdio: "inherit" }
109
- );
110
- console.log("\n\u{1F4E6} Installing dependencies...");
111
- (0, import_child_process.execSync)("npm install @unciatech/file-manager tailwindcss-animate", {
112
- cwd: targetDir,
113
- stdio: "inherit"
114
- });
115
- const componentsDir = path.join(targetDir, "src/components");
116
- fs.mkdirSync(componentsDir, { recursive: true });
117
- fs.writeFileSync(
118
- path.join(componentsDir, "FileManagerDemo.tsx"),
119
- TEMPLATE
120
+ console.log("\n\u{1F4E6} Creating Next.js application (this may take a minute)...");
121
+ (0, import_child_process.execSync)(`npx create-next-app@latest ${projectName2} --ts --tailwind --eslint --app --src-dir --import-alias "@/*" --use-npm`, { stdio: "inherit" });
122
+ console.log("\n\u{1F4E6} Installing dependencies (@unciatech/file-manager, tailwindcss-animate)...");
123
+ (0, import_child_process.execSync)("npm install @unciatech/file-manager tailwindcss-animate", { cwd: targetDir, stdio: "inherit" });
124
+ const componentsDir = import_path.default.join(targetDir, "src", "components");
125
+ if (!import_fs.default.existsSync(componentsDir)) import_fs.default.mkdirSync(componentsDir, { recursive: true });
126
+ import_fs.default.writeFileSync(import_path.default.join(componentsDir, "FileManagerDemo.tsx"), getTemplate("/media"), "utf-8");
127
+ const pagePath = import_path.default.join(targetDir, "src", "app", "page.tsx");
128
+ import_fs.default.writeFileSync(pagePath, `import FileManagerDemo from "@/components/FileManagerDemo";
129
+
130
+ export default function Home() {
131
+ return (
132
+ <main className="min-h-screen bg-neutral-50">
133
+ <FileManagerDemo />
134
+ </main>
120
135
  );
121
- const pagePath = path.join(targetDir, "src/app/page.tsx");
122
- fs.writeFileSync(
123
- pagePath,
136
+ }
137
+ `);
138
+ const mediaRouteDir = import_path.default.join(targetDir, "src", "app", "media", "[[...path]]");
139
+ import_fs.default.mkdirSync(mediaRouteDir, { recursive: true });
140
+ import_fs.default.writeFileSync(
141
+ import_path.default.join(mediaRouteDir, "page.tsx"),
124
142
  `import FileManagerDemo from "@/components/FileManagerDemo";
125
143
 
126
- export default function Home() {
144
+ export default function MediaPage() {
127
145
  return (
128
146
  <main className="min-h-screen bg-neutral-50">
129
147
  <FileManagerDemo />
@@ -132,49 +150,61 @@ export default function Home() {
132
150
  }
133
151
  `
134
152
  );
153
+ const layoutPath = import_path.default.join(targetDir, "src", "app", "layout.tsx");
154
+ if (import_fs.default.existsSync(layoutPath)) {
155
+ let layoutContent = import_fs.default.readFileSync(layoutPath, "utf8");
156
+ if (!layoutContent.includes("@unciatech/file-manager/styles")) {
157
+ layoutContent = layoutContent.replace(
158
+ /^(import type)/m,
159
+ `import '@unciatech/file-manager/styles';
160
+ $1`
161
+ );
162
+ import_fs.default.writeFileSync(layoutPath, layoutContent);
163
+ }
164
+ }
165
+ const cssPath = import_path.default.join(targetDir, "src", "app", "globals.css");
166
+ import_fs.default.writeFileSync(cssPath, `@import "tailwindcss";
167
+ @import "@unciatech/file-manager/styles";
168
+ @import "tw-animate-css";
169
+
170
+ @source "../../node_modules/@unciatech/file-manager";
171
+
172
+ @theme {
173
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
174
+ --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
175
+ }
176
+ `);
135
177
  printSuccess(projectName2);
136
178
  }
137
179
  async function scaffoldVite(projectName2, targetDir) {
138
- console.log("\n\u{1F4E6} Creating Vite app...");
139
- (0, import_child_process.execSync)(
140
- `npm create vite@latest ${projectName2} -- --template react-ts`,
141
- { stdio: "inherit" }
142
- );
143
- console.log("\n\u{1F4E6} Installing dependencies...");
180
+ console.log("\n\u{1F4E6} Creating Vite React application...");
181
+ (0, import_child_process.execSync)(`npm create vite@latest ${projectName2} -- --template react-ts`, { stdio: "inherit" });
182
+ console.log("\n\u{1F4E6} Installing dependencies (Tailwind + File Manager)...");
144
183
  (0, import_child_process.execSync)("npm install", { cwd: targetDir, stdio: "inherit" });
145
- (0, import_child_process.execSync)(
146
- "npm install tailwindcss @tailwindcss/vite tw-animate-css @unciatech/file-manager",
147
- { cwd: targetDir, stdio: "inherit" }
148
- );
184
+ (0, import_child_process.execSync)("npm install tailwindcss @tailwindcss/vite @unciatech/file-manager", { cwd: targetDir, stdio: "inherit" });
185
+ const viteConfigPath = import_path.default.join(targetDir, "vite.config.ts");
149
186
  const viteConfig = `import { defineConfig } from 'vite'
150
187
  import react from '@vitejs/plugin-react'
151
188
  import tailwindcss from '@tailwindcss/vite'
152
189
 
153
190
  export default defineConfig({
154
- plugins: [react(), tailwindcss()],
191
+ plugins: [
192
+ react(),
193
+ tailwindcss(),
194
+ ],
155
195
  })
156
196
  `;
157
- fs.writeFileSync(path.join(targetDir, "vite.config.ts"), viteConfig);
158
- const css = `@import "tailwindcss";
159
- @import "tw-animate-css";
160
-
161
- @source "../node_modules/@unciatech/file-manager/dist/**/*.{js,ts,jsx,tsx}";
162
- `;
163
- fs.writeFileSync(path.join(targetDir, "src/index.css"), css);
164
- const mainFile = path.join(targetDir, "src/main.tsx");
165
- let main2 = fs.readFileSync(mainFile, "utf8");
166
- if (!main2.includes("@unciatech/file-manager/styles")) {
167
- main2 = `import "@unciatech/file-manager/styles";
168
- ` + main2;
169
- }
170
- fs.writeFileSync(mainFile, main2);
171
- const compDir = path.join(targetDir, "src/components");
172
- fs.mkdirSync(compDir, { recursive: true });
173
- fs.writeFileSync(
174
- path.join(compDir, "FileManagerDemo.tsx"),
175
- TEMPLATE
176
- );
177
- const app = `import FileManagerDemo from "./components/FileManagerDemo";
197
+ import_fs.default.writeFileSync(viteConfigPath, viteConfig);
198
+ const cssPath = import_path.default.join(targetDir, "src", "index.css");
199
+ import_fs.default.writeFileSync(cssPath, `@import "tailwindcss";
200
+ @import "@unciatech/file-manager/styles";
201
+ @source "../node_modules/@unciatech/file-manager";
202
+ `);
203
+ const componentsDir = import_path.default.join(targetDir, "src", "components");
204
+ if (!import_fs.default.existsSync(componentsDir)) import_fs.default.mkdirSync(componentsDir, { recursive: true });
205
+ import_fs.default.writeFileSync(import_path.default.join(componentsDir, "FileManagerDemo.tsx"), getTemplate("/"), "utf-8");
206
+ const appPath = import_path.default.join(targetDir, "src", "App.tsx");
207
+ import_fs.default.writeFileSync(appPath, `import FileManagerDemo from "./components/FileManagerDemo";
178
208
 
179
209
  function App() {
180
210
  return (
@@ -185,18 +215,16 @@ function App() {
185
215
  }
186
216
 
187
217
  export default App;
188
- `;
189
- fs.writeFileSync(path.join(targetDir, "src/App.tsx"), app);
190
- printSuccess(projectName2);
191
- }
192
- function printSuccess(projectName2) {
193
- console.log("\n=================================");
194
- console.log("\u{1F389} Project ready!");
195
- console.log("=================================");
196
- console.log(`
197
- Next steps:`);
198
- console.log(`cd ${projectName2}`);
199
- console.log(`npm run dev
200
218
  `);
219
+ printSuccess(projectName2, "npm run dev");
220
+ }
221
+ function printSuccess(projectName2, devCmd = "npm run dev") {
222
+ console.log("\n=========================================");
223
+ console.log("\u{1F389} Your Media Library application is ready!");
224
+ console.log("=========================================");
225
+ console.log("\nNext steps:");
226
+ console.log(` cd ${projectName2}`);
227
+ console.log(` ${devCmd}`);
228
+ console.log("\nEnjoy building! \u{1F5C2}\uFE0F\n");
201
229
  }
202
230
  main();