@blitheforge/media-library 1.0.5
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/LICENSE +21 -0
- package/README.md +558 -0
- package/dist/index.cjs +1440 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +177 -0
- package/dist/index.d.ts +177 -0
- package/dist/index.js +1387 -0
- package/dist/index.js.map +1 -0
- package/dist/style.css +2 -0
- package/package.json +74 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1440 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from, except, desc) => {
|
|
14
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/index.ts
|
|
32
|
+
var index_exports = {};
|
|
33
|
+
__export(index_exports, {
|
|
34
|
+
MAX_MEDIA_UPLOAD_BYTES: () => MAX_MEDIA_UPLOAD_BYTES,
|
|
35
|
+
MediaLibraryModal: () => MediaLibraryModal,
|
|
36
|
+
MediaLibraryPanel: () => MediaLibraryPanel,
|
|
37
|
+
MediaLibraryWidget: () => MediaLibraryWidget,
|
|
38
|
+
MediaPicker: () => MediaPicker,
|
|
39
|
+
MediaPickerMulti: () => MediaPickerMulti,
|
|
40
|
+
MediaPreview: () => MediaPreview,
|
|
41
|
+
bfmlRootProps: () => bfmlRootProps,
|
|
42
|
+
createMediaLibraryClient: () => createMediaLibraryClient,
|
|
43
|
+
defaultMediaCapabilities: () => defaultMediaCapabilities,
|
|
44
|
+
defaultMediaLibraryConfig: () => defaultMediaLibraryConfig,
|
|
45
|
+
fileMatchesAccept: () => fileMatchesAccept,
|
|
46
|
+
fileMatchesAcceptForUpload: () => fileMatchesAcceptForUpload,
|
|
47
|
+
fileNameFromPath: () => fileNameFromPath,
|
|
48
|
+
formatUploadSizeLimit: () => formatUploadSizeLimit,
|
|
49
|
+
isFileWithinUploadSizeLimit: () => isFileWithinUploadSizeLimit,
|
|
50
|
+
isImagePath: () => isImagePath,
|
|
51
|
+
resolveThemeMode: () => resolveThemeMode
|
|
52
|
+
});
|
|
53
|
+
module.exports = __toCommonJS(index_exports);
|
|
54
|
+
|
|
55
|
+
// src/types.ts
|
|
56
|
+
var defaultMediaCapabilities = {
|
|
57
|
+
view: true,
|
|
58
|
+
upload: true,
|
|
59
|
+
createFolder: true,
|
|
60
|
+
delete: true,
|
|
61
|
+
rename: true,
|
|
62
|
+
select: true
|
|
63
|
+
};
|
|
64
|
+
var defaultMediaLibraryConfig = {
|
|
65
|
+
listUrl: "/api/media",
|
|
66
|
+
uploadUrl: "/api/media/upload",
|
|
67
|
+
createFolderUrl: "/api/media/folders",
|
|
68
|
+
updateUrl: "/api/media",
|
|
69
|
+
deleteUrl: "/api/media",
|
|
70
|
+
rootLabel: "Root"
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// src/client.ts
|
|
74
|
+
function resolveConfig(config) {
|
|
75
|
+
return { ...defaultMediaLibraryConfig, ...config };
|
|
76
|
+
}
|
|
77
|
+
async function parseResponse(response) {
|
|
78
|
+
const payload = await response.json();
|
|
79
|
+
if (!payload.success) throw new Error(payload.error?.message ?? "Media request failed.");
|
|
80
|
+
return payload.data;
|
|
81
|
+
}
|
|
82
|
+
function createMediaLibraryClient(config) {
|
|
83
|
+
const urls = resolveConfig(config);
|
|
84
|
+
return {
|
|
85
|
+
async list(path = "", q = "") {
|
|
86
|
+
const params = new URLSearchParams();
|
|
87
|
+
if (path) params.set("path", path);
|
|
88
|
+
if (q) params.set("q", q);
|
|
89
|
+
const response = await fetch(`${urls.listUrl}?${params.toString()}`);
|
|
90
|
+
return parseResponse(response);
|
|
91
|
+
},
|
|
92
|
+
async createFolder(path, name, nested = true) {
|
|
93
|
+
const response = await fetch(urls.createFolderUrl, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "content-type": "application/json" },
|
|
96
|
+
body: JSON.stringify({ path, name, nested })
|
|
97
|
+
});
|
|
98
|
+
return parseResponse(response);
|
|
99
|
+
},
|
|
100
|
+
async upload(path, files) {
|
|
101
|
+
const form = new FormData();
|
|
102
|
+
form.set("path", path);
|
|
103
|
+
files.forEach((file) => form.append("files", file));
|
|
104
|
+
const response = await fetch(urls.uploadUrl, { method: "POST", body: form });
|
|
105
|
+
return parseResponse(response);
|
|
106
|
+
},
|
|
107
|
+
async uploadOne(path, file) {
|
|
108
|
+
const uploaded = await this.upload(path, [file]);
|
|
109
|
+
return uploaded[0];
|
|
110
|
+
},
|
|
111
|
+
async rename(path, newName, type) {
|
|
112
|
+
const response = await fetch(urls.updateUrl, {
|
|
113
|
+
method: "PATCH",
|
|
114
|
+
headers: { "content-type": "application/json" },
|
|
115
|
+
body: JSON.stringify({ path, newName, type })
|
|
116
|
+
});
|
|
117
|
+
return parseResponse(response);
|
|
118
|
+
},
|
|
119
|
+
async remove(path, type) {
|
|
120
|
+
const response = await fetch(urls.deleteUrl, {
|
|
121
|
+
method: "DELETE",
|
|
122
|
+
headers: { "content-type": "application/json" },
|
|
123
|
+
body: JSON.stringify({ path, type })
|
|
124
|
+
});
|
|
125
|
+
return parseResponse(response);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
var MAX_MEDIA_UPLOAD_BYTES = 5 * 1024 * 1024;
|
|
130
|
+
function isFileWithinUploadSizeLimit(file, maxBytes = MAX_MEDIA_UPLOAD_BYTES) {
|
|
131
|
+
return file.size <= maxBytes;
|
|
132
|
+
}
|
|
133
|
+
function formatUploadSizeLimit(maxBytes = MAX_MEDIA_UPLOAD_BYTES) {
|
|
134
|
+
return `${Math.round(maxBytes / (1024 * 1024))} MB`;
|
|
135
|
+
}
|
|
136
|
+
function fileMatchesAccept(file, accept) {
|
|
137
|
+
if (!accept || accept.length === 0) return true;
|
|
138
|
+
const isImage = file.mimeType.startsWith("image/");
|
|
139
|
+
const isPdf = file.mimeType === "application/pdf";
|
|
140
|
+
if (accept.includes("image") && isImage) return true;
|
|
141
|
+
if (accept.includes("pdf") && isPdf) return true;
|
|
142
|
+
return false;
|
|
143
|
+
}
|
|
144
|
+
function fileMatchesAcceptForUpload(file, accept) {
|
|
145
|
+
if (!accept || accept.length === 0) return true;
|
|
146
|
+
const isImage = file.type.startsWith("image/");
|
|
147
|
+
const isPdf = file.type === "application/pdf";
|
|
148
|
+
if (accept.includes("image") && isImage) return true;
|
|
149
|
+
if (accept.includes("pdf") && isPdf) return true;
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
function fileNameFromPath(path) {
|
|
153
|
+
return path.split("/").pop() ?? path;
|
|
154
|
+
}
|
|
155
|
+
function isImagePath(path) {
|
|
156
|
+
return /\.(png|jpe?g|webp|gif)$/i.test(path);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/components/media-library-modal.tsx
|
|
160
|
+
var import_react_dom2 = require("react-dom");
|
|
161
|
+
|
|
162
|
+
// src/utils/cn.ts
|
|
163
|
+
var import_clsx = require("clsx");
|
|
164
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
165
|
+
function cn(...inputs) {
|
|
166
|
+
return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/utils/bfml-theme.ts
|
|
170
|
+
function bfmlRootProps(theme = "sync") {
|
|
171
|
+
return {
|
|
172
|
+
className: "bfml-root",
|
|
173
|
+
...theme !== "sync" ? { "data-theme": theme } : {}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function resolveThemeMode(theme) {
|
|
177
|
+
return theme ?? "sync";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/components/media-library-panel.tsx
|
|
181
|
+
var import_react2 = require("react");
|
|
182
|
+
var import_lucide_react3 = require("lucide-react");
|
|
183
|
+
|
|
184
|
+
// src/components/ui/button.tsx
|
|
185
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
186
|
+
var variants = {
|
|
187
|
+
primary: "bg-[var(--bfml-primary)] text-[var(--bfml-primary-foreground)] shadow-[var(--bfml-shadow)] hover:brightness-95",
|
|
188
|
+
secondary: "border border-[var(--bfml-border)] bg-[var(--bfml-surface)] text-[var(--bfml-foreground)] shadow-[var(--bfml-shadow)] hover:bg-[var(--bfml-surface-soft)]",
|
|
189
|
+
danger: "bg-[var(--bfml-destructive)] text-[var(--bfml-primary-foreground)] hover:brightness-95",
|
|
190
|
+
ghost: "bg-transparent text-[var(--bfml-muted-foreground)] hover:bg-[var(--bfml-surface-soft)] hover:text-[var(--bfml-foreground)]"
|
|
191
|
+
};
|
|
192
|
+
function Button({ className, variant = "primary", disabled, children, ...props }) {
|
|
193
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
194
|
+
"button",
|
|
195
|
+
{
|
|
196
|
+
className: cn(
|
|
197
|
+
"inline-flex h-10 items-center justify-center gap-2 rounded-lg px-4 text-sm font-semibold transition disabled:pointer-events-none disabled:opacity-50",
|
|
198
|
+
variants[variant],
|
|
199
|
+
className
|
|
200
|
+
),
|
|
201
|
+
disabled,
|
|
202
|
+
...props,
|
|
203
|
+
children
|
|
204
|
+
}
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// src/components/ui/confirm-dialog.tsx
|
|
209
|
+
var import_react_dom = require("react-dom");
|
|
210
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
211
|
+
function ConfirmDialog({
|
|
212
|
+
open,
|
|
213
|
+
title,
|
|
214
|
+
description,
|
|
215
|
+
confirmLabel = "Delete",
|
|
216
|
+
cancelLabel = "Cancel",
|
|
217
|
+
loading = false,
|
|
218
|
+
theme = "sync",
|
|
219
|
+
onCancel,
|
|
220
|
+
onConfirm
|
|
221
|
+
}) {
|
|
222
|
+
if (!open || typeof document === "undefined") return null;
|
|
223
|
+
const rootProps = bfmlRootProps(theme);
|
|
224
|
+
return (0, import_react_dom.createPortal)(
|
|
225
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
226
|
+
"div",
|
|
227
|
+
{
|
|
228
|
+
...rootProps,
|
|
229
|
+
className: cn(rootProps.className, "fixed inset-0 z-[10001] flex items-end justify-center p-0 backdrop-blur-sm sm:items-center sm:p-4"),
|
|
230
|
+
style: { backgroundColor: "var(--bfml-overlay)" },
|
|
231
|
+
role: "presentation",
|
|
232
|
+
onClick: onCancel,
|
|
233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
234
|
+
"section",
|
|
235
|
+
{
|
|
236
|
+
role: "alertdialog",
|
|
237
|
+
"aria-modal": "true",
|
|
238
|
+
"aria-labelledby": "bfml-confirm-title",
|
|
239
|
+
"aria-describedby": "bfml-confirm-description",
|
|
240
|
+
className: "w-full max-w-md rounded-t-2xl border border-[var(--bfml-border)] bg-[var(--bfml-surface)] p-4 shadow-[var(--bfml-shadow-lg)] sm:rounded-2xl sm:p-6",
|
|
241
|
+
onClick: (event) => event.stopPropagation(),
|
|
242
|
+
children: [
|
|
243
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { id: "bfml-confirm-title", className: "text-lg font-semibold text-[var(--bfml-foreground)]", children: title }),
|
|
244
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { id: "bfml-confirm-description", className: "mt-2 text-sm text-[var(--bfml-muted-foreground)]", children: description }),
|
|
245
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "mt-5 flex flex-col-reverse gap-2 sm:mt-6 sm:flex-row sm:justify-end", children: [
|
|
246
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Button, { type: "button", variant: "secondary", disabled: loading, className: "w-full sm:w-auto", onClick: onCancel, children: cancelLabel }),
|
|
247
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Button, { type: "button", variant: "danger", disabled: loading, className: "w-full sm:w-auto", onClick: onConfirm, children: loading ? "Deleting..." : confirmLabel })
|
|
248
|
+
] })
|
|
249
|
+
]
|
|
250
|
+
}
|
|
251
|
+
)
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
document.body
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// src/components/ui/input.tsx
|
|
259
|
+
var React = __toESM(require("react"), 1);
|
|
260
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
261
|
+
var Input = React.forwardRef(function Input2({ className, ...props }, ref) {
|
|
262
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
263
|
+
"input",
|
|
264
|
+
{
|
|
265
|
+
ref,
|
|
266
|
+
className: cn(
|
|
267
|
+
"h-11 w-full rounded-lg border border-[var(--bfml-border)] bg-[var(--bfml-surface)] px-4 text-sm text-[var(--bfml-foreground)] shadow-[var(--bfml-shadow)] outline-none transition placeholder:text-[var(--bfml-muted-foreground)] focus:border-[var(--bfml-primary-border)] focus:ring-4 focus:ring-[var(--bfml-primary-soft)]",
|
|
268
|
+
className
|
|
269
|
+
),
|
|
270
|
+
...props
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
// src/components/ui/toast-container.tsx
|
|
276
|
+
var import_react = require("react");
|
|
277
|
+
var import_lucide_react = require("lucide-react");
|
|
278
|
+
|
|
279
|
+
// src/utils/toast-store.ts
|
|
280
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
281
|
+
var toasts = [];
|
|
282
|
+
function emit() {
|
|
283
|
+
listeners.forEach((listener) => listener([...toasts]));
|
|
284
|
+
}
|
|
285
|
+
function createId() {
|
|
286
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
287
|
+
return crypto.randomUUID();
|
|
288
|
+
}
|
|
289
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
290
|
+
}
|
|
291
|
+
function dismissToast(id) {
|
|
292
|
+
toasts = toasts.filter((toast) => toast.id !== id);
|
|
293
|
+
emit();
|
|
294
|
+
}
|
|
295
|
+
function showToast(type, message, durationMs = 3500) {
|
|
296
|
+
const id = createId();
|
|
297
|
+
toasts = [...toasts, { id, type, message }];
|
|
298
|
+
emit();
|
|
299
|
+
window.setTimeout(() => dismissToast(id), durationMs);
|
|
300
|
+
}
|
|
301
|
+
function subscribeToasts(listener) {
|
|
302
|
+
listeners.add(listener);
|
|
303
|
+
listener([...toasts]);
|
|
304
|
+
return () => {
|
|
305
|
+
listeners.delete(listener);
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function toastSuccess(message) {
|
|
309
|
+
showToast("success", message);
|
|
310
|
+
}
|
|
311
|
+
function toastError(message) {
|
|
312
|
+
showToast("error", message);
|
|
313
|
+
}
|
|
314
|
+
function toastWarning(message) {
|
|
315
|
+
showToast("warning", message);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/components/ui/toast-container.tsx
|
|
319
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
320
|
+
function ToastCard({ toast }) {
|
|
321
|
+
const isSuccess = toast.type === "success";
|
|
322
|
+
const isWarning = toast.type === "warning";
|
|
323
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
324
|
+
"div",
|
|
325
|
+
{
|
|
326
|
+
role: "status",
|
|
327
|
+
className: cn(
|
|
328
|
+
"pointer-events-auto flex w-full max-w-sm items-start gap-3 rounded-xl border px-4 py-3 shadow-[var(--bfml-shadow-lg)] transition",
|
|
329
|
+
isSuccess ? "border-[var(--bfml-success)]/30 bg-[var(--bfml-success-soft)] text-[var(--bfml-success-foreground)]" : isWarning ? "border-[var(--bfml-warning)]/30 bg-[var(--bfml-warning-soft)] text-[var(--bfml-warning-foreground)]" : "border-[var(--bfml-destructive)]/30 bg-[var(--bfml-destructive-soft)] text-[var(--bfml-foreground)]"
|
|
330
|
+
),
|
|
331
|
+
children: [
|
|
332
|
+
isSuccess ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 text-[var(--bfml-success)]", "aria-hidden": "true" }) : isWarning ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.TriangleAlert, { className: "mt-0.5 h-4 w-4 shrink-0 text-[var(--bfml-warning)]", "aria-hidden": "true" }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.XCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-[var(--bfml-destructive)]", "aria-hidden": "true" }),
|
|
333
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { className: "min-w-0 flex-1 text-sm font-medium leading-5", children: toast.message }),
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
335
|
+
"button",
|
|
336
|
+
{
|
|
337
|
+
type: "button",
|
|
338
|
+
className: "rounded-md p-0.5 opacity-70 transition hover:opacity-100",
|
|
339
|
+
"aria-label": "Dismiss notification",
|
|
340
|
+
onClick: () => dismissToast(toast.id),
|
|
341
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react.X, { className: "h-4 w-4" })
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
]
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
function ToastContainer({ theme = "sync" }) {
|
|
349
|
+
const [items, setItems] = (0, import_react.useState)([]);
|
|
350
|
+
const rootProps = bfmlRootProps(theme);
|
|
351
|
+
(0, import_react.useEffect)(() => {
|
|
352
|
+
return subscribeToasts(setItems);
|
|
353
|
+
}, []);
|
|
354
|
+
if (items.length === 0) return null;
|
|
355
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
356
|
+
"div",
|
|
357
|
+
{
|
|
358
|
+
...rootProps,
|
|
359
|
+
className: cn(rootProps.className, "pointer-events-none absolute right-3 top-3 z-[45] flex w-[min(100%,20rem)] flex-col items-end gap-2 sm:right-4 sm:top-4"),
|
|
360
|
+
children: items.map((toast) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToastCard, { toast }, toast.id))
|
|
361
|
+
}
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/components/upload-preview.tsx
|
|
366
|
+
var import_lucide_react2 = require("lucide-react");
|
|
367
|
+
var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
368
|
+
function UploadPreviewCard({ item }) {
|
|
369
|
+
const isImage = item.file.type.startsWith("image/");
|
|
370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
371
|
+
"div",
|
|
372
|
+
{
|
|
373
|
+
className: cn(
|
|
374
|
+
"relative overflow-hidden rounded-xl border bg-[var(--bfml-surface)] p-2 sm:p-3",
|
|
375
|
+
item.status === "done" ? "border-[var(--bfml-success)]/40" : item.status === "error" ? "border-[var(--bfml-destructive)]/40" : "border-[var(--bfml-border)]"
|
|
376
|
+
),
|
|
377
|
+
children: [
|
|
378
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "relative flex h-20 items-center justify-center overflow-hidden rounded-lg bg-[var(--bfml-surface-soft)] sm:h-28", children: [
|
|
379
|
+
isImage ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: item.previewUrl, alt: item.file.name, className: "h-full w-full object-contain opacity-80" }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "text-xs font-semibold uppercase text-[var(--bfml-muted-foreground)]", children: "PDF" }),
|
|
380
|
+
item.status === "pending" || item.status === "uploading" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(
|
|
381
|
+
"div",
|
|
382
|
+
{
|
|
383
|
+
className: "absolute inset-0 flex flex-col items-center justify-center gap-2 px-2 text-[var(--bfml-primary-foreground)]",
|
|
384
|
+
style: { backgroundColor: "var(--bfml-overlay)" },
|
|
385
|
+
children: [
|
|
386
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.LoaderCircle, { className: "h-6 w-6 animate-spin" }),
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { className: "text-xs font-medium", children: item.status === "uploading" ? "Uploading..." : "Waiting..." })
|
|
388
|
+
]
|
|
389
|
+
}
|
|
390
|
+
) : null,
|
|
391
|
+
item.status === "done" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
392
|
+
"div",
|
|
393
|
+
{
|
|
394
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
395
|
+
style: { backgroundColor: "color-mix(in srgb, var(--bfml-success) 25%, transparent)" },
|
|
396
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.CheckCircle2, { className: "h-8 w-8 text-[var(--bfml-success)]" })
|
|
397
|
+
}
|
|
398
|
+
) : null,
|
|
399
|
+
item.status === "error" ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
|
|
400
|
+
"div",
|
|
401
|
+
{
|
|
402
|
+
className: "absolute inset-0 flex items-center justify-center",
|
|
403
|
+
style: { backgroundColor: "color-mix(in srgb, var(--bfml-destructive) 25%, transparent)" },
|
|
404
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_lucide_react2.XCircle, { className: "h-8 w-8 text-[var(--bfml-destructive)]" })
|
|
405
|
+
}
|
|
406
|
+
) : null
|
|
407
|
+
] }),
|
|
408
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-2 truncate text-sm font-medium text-[var(--bfml-foreground)]", children: item.file.name }),
|
|
409
|
+
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "truncate text-xs text-[var(--bfml-muted-foreground)]", children: item.status === "error" ? item.error ?? "Upload failed" : item.status === "done" ? "Uploaded" : "In queue" })
|
|
410
|
+
]
|
|
411
|
+
}
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// src/utils/media-library-utils.ts
|
|
416
|
+
function parentPath(path) {
|
|
417
|
+
const segments = path.split("/").filter(Boolean);
|
|
418
|
+
segments.pop();
|
|
419
|
+
return segments.join("/");
|
|
420
|
+
}
|
|
421
|
+
function isPathInside(path, folderPath) {
|
|
422
|
+
return path === folderPath || path.startsWith(`${folderPath}/`);
|
|
423
|
+
}
|
|
424
|
+
function buildBreadcrumb(path, rootLabel) {
|
|
425
|
+
const segments = path ? path.split("/").filter(Boolean) : [];
|
|
426
|
+
const crumbs = [{ label: rootLabel, path: "" }];
|
|
427
|
+
segments.forEach((segment, index) => {
|
|
428
|
+
crumbs.push({ label: segment, path: segments.slice(0, index + 1).join("/") });
|
|
429
|
+
});
|
|
430
|
+
return crumbs;
|
|
431
|
+
}
|
|
432
|
+
function createQueueId() {
|
|
433
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
434
|
+
return crypto.randomUUID();
|
|
435
|
+
}
|
|
436
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
437
|
+
}
|
|
438
|
+
function toCssSize(value) {
|
|
439
|
+
if (value === void 0) return void 0;
|
|
440
|
+
if (typeof value === "number") return `${value}px`;
|
|
441
|
+
const trimmed = value.trim();
|
|
442
|
+
if (/^calc\s*\(/i.test(trimmed) || /^var\s*\(/i.test(trimmed)) return trimmed;
|
|
443
|
+
if (/^[\d.]+\s*(vh|vw|vmin|vmax|%|px|rem|em)\s*[-+]\s*[\d.]+/i.test(trimmed)) {
|
|
444
|
+
const normalized = trimmed.replace(/\s*([+-])\s*/g, " $1 ");
|
|
445
|
+
return `calc(${normalized})`;
|
|
446
|
+
}
|
|
447
|
+
return trimmed;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// src/components/media-library-panel.tsx
|
|
451
|
+
var import_jsx_runtime6 = require("react/jsx-runtime");
|
|
452
|
+
function MediaLibraryPanel({
|
|
453
|
+
active = true,
|
|
454
|
+
config,
|
|
455
|
+
theme,
|
|
456
|
+
title = "Media Library",
|
|
457
|
+
description = "Create folders, upload files, and choose media.",
|
|
458
|
+
accept,
|
|
459
|
+
variant = "embedded",
|
|
460
|
+
selectable = false,
|
|
461
|
+
closeOnSelect = true,
|
|
462
|
+
selectionMode = "single",
|
|
463
|
+
maxSelections,
|
|
464
|
+
autoSelectUploads = false,
|
|
465
|
+
onClose,
|
|
466
|
+
onSelect,
|
|
467
|
+
onSelectMany,
|
|
468
|
+
className
|
|
469
|
+
}) {
|
|
470
|
+
const client = (0, import_react2.useMemo)(() => createMediaLibraryClient(config), [config]);
|
|
471
|
+
const resolved = (0, import_react2.useMemo)(() => ({ ...defaultMediaLibraryConfig, ...config }), [config]);
|
|
472
|
+
const themeMode = resolveThemeMode(theme ?? resolved.theme);
|
|
473
|
+
const rootProps = bfmlRootProps(themeMode);
|
|
474
|
+
const uploadInputRef = (0, import_react2.useRef)(null);
|
|
475
|
+
const dragCounterRef = (0, import_react2.useRef)(0);
|
|
476
|
+
const [currentPath, setCurrentPath] = (0, import_react2.useState)("");
|
|
477
|
+
const [search, setSearch] = (0, import_react2.useState)("");
|
|
478
|
+
const [folderName, setFolderName] = (0, import_react2.useState)("");
|
|
479
|
+
const [nestedFolder, setNestedFolder] = (0, import_react2.useState)(true);
|
|
480
|
+
const [loading, setLoading] = (0, import_react2.useState)(false);
|
|
481
|
+
const [uploading, setUploading] = (0, import_react2.useState)(false);
|
|
482
|
+
const [dragActive, setDragActive] = (0, import_react2.useState)(false);
|
|
483
|
+
const [capabilities, setCapabilities] = (0, import_react2.useState)(defaultMediaCapabilities);
|
|
484
|
+
const [uploadQueue, setUploadQueue] = (0, import_react2.useState)([]);
|
|
485
|
+
const [folders, setFolders] = (0, import_react2.useState)([]);
|
|
486
|
+
const [files, setFiles] = (0, import_react2.useState)([]);
|
|
487
|
+
const [selected, setSelected] = (0, import_react2.useState)(null);
|
|
488
|
+
const [selectedFiles, setSelectedFiles] = (0, import_react2.useState)([]);
|
|
489
|
+
const [deleteTarget, setDeleteTarget] = (0, import_react2.useState)(null);
|
|
490
|
+
const [deleting, setDeleting] = (0, import_react2.useState)(false);
|
|
491
|
+
const [sidebarOpen, setSidebarOpen] = (0, import_react2.useState)(false);
|
|
492
|
+
async function load(path = currentPath, q = search, options) {
|
|
493
|
+
if (!options?.silent) setLoading(true);
|
|
494
|
+
try {
|
|
495
|
+
const listing = await client.list(path, q);
|
|
496
|
+
setCurrentPath(listing.path);
|
|
497
|
+
setFolders(listing.folders);
|
|
498
|
+
setFiles(listing.files.filter((file) => fileMatchesAccept(file, accept)));
|
|
499
|
+
setCapabilities({ ...defaultMediaCapabilities, ...listing.capabilities });
|
|
500
|
+
} catch (caught) {
|
|
501
|
+
toastError(caught instanceof Error ? caught.message : "Failed to load media.");
|
|
502
|
+
} finally {
|
|
503
|
+
if (!options?.silent) setLoading(false);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
function clearUploadQueue() {
|
|
507
|
+
setUploadQueue((current) => {
|
|
508
|
+
current.forEach((item) => URL.revokeObjectURL(item.previewUrl));
|
|
509
|
+
return [];
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
(0, import_react2.useEffect)(() => {
|
|
513
|
+
if (!active) return;
|
|
514
|
+
setSelected(null);
|
|
515
|
+
setSelectedFiles([]);
|
|
516
|
+
setDeleteTarget(null);
|
|
517
|
+
setSidebarOpen(false);
|
|
518
|
+
clearUploadQueue();
|
|
519
|
+
setDragActive(false);
|
|
520
|
+
dragCounterRef.current = 0;
|
|
521
|
+
setSearch("");
|
|
522
|
+
void load("", "");
|
|
523
|
+
}, [active]);
|
|
524
|
+
(0, import_react2.useEffect)(() => {
|
|
525
|
+
if (!active || variant !== "modal" || !onClose) return;
|
|
526
|
+
const handleClose = onClose;
|
|
527
|
+
function onKeyDown(event) {
|
|
528
|
+
if (event.key !== "Escape") return;
|
|
529
|
+
if (deleteTarget && !deleting) {
|
|
530
|
+
setDeleteTarget(null);
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
handleClose();
|
|
534
|
+
}
|
|
535
|
+
document.body.style.overflow = "hidden";
|
|
536
|
+
window.addEventListener("keydown", onKeyDown);
|
|
537
|
+
return () => {
|
|
538
|
+
document.body.style.overflow = "";
|
|
539
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
540
|
+
};
|
|
541
|
+
}, [active, deleteTarget, deleting, onClose, variant]);
|
|
542
|
+
async function createFolder() {
|
|
543
|
+
if (!capabilities.createFolder) {
|
|
544
|
+
toastError("You do not have permission to create folders.");
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const name = folderName.trim();
|
|
548
|
+
if (!name) {
|
|
549
|
+
toastError("Enter a folder name.");
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
try {
|
|
553
|
+
await client.createFolder(currentPath, name, nestedFolder);
|
|
554
|
+
setFolderName("");
|
|
555
|
+
await load(currentPath, search);
|
|
556
|
+
toastSuccess(`Folder "${name}" created.`);
|
|
557
|
+
} catch (caught) {
|
|
558
|
+
toastError(caught instanceof Error ? caught.message : "Failed to create folder.");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
async function processFiles(incoming) {
|
|
562
|
+
if (!capabilities.upload || incoming.length === 0 || uploading) return;
|
|
563
|
+
const sizeLimit = formatUploadSizeLimit();
|
|
564
|
+
const accepted = [];
|
|
565
|
+
let invalidTypeCount = 0;
|
|
566
|
+
for (const file of incoming) {
|
|
567
|
+
if (!fileMatchesAcceptForUpload(file, accept)) {
|
|
568
|
+
invalidTypeCount += 1;
|
|
569
|
+
continue;
|
|
570
|
+
}
|
|
571
|
+
if (!isFileWithinUploadSizeLimit(file)) {
|
|
572
|
+
toastWarning(`"${file.name}" exceeds ${sizeLimit} and was skipped.`);
|
|
573
|
+
continue;
|
|
574
|
+
}
|
|
575
|
+
accepted.push(file);
|
|
576
|
+
}
|
|
577
|
+
if (invalidTypeCount > 0) {
|
|
578
|
+
toastError(`${invalidTypeCount} file(s) were skipped due to type restrictions.`);
|
|
579
|
+
}
|
|
580
|
+
if (accepted.length === 0) return;
|
|
581
|
+
const queue = accepted.map((file) => ({
|
|
582
|
+
id: createQueueId(),
|
|
583
|
+
file,
|
|
584
|
+
previewUrl: URL.createObjectURL(file),
|
|
585
|
+
status: "pending"
|
|
586
|
+
}));
|
|
587
|
+
setUploadQueue(queue);
|
|
588
|
+
setUploading(true);
|
|
589
|
+
const uploadedFiles = [];
|
|
590
|
+
let successCount = 0;
|
|
591
|
+
for (const item of queue) {
|
|
592
|
+
setUploadQueue(
|
|
593
|
+
(current) => current.map((entry) => entry.id === item.id ? { ...entry, status: "uploading" } : entry)
|
|
594
|
+
);
|
|
595
|
+
try {
|
|
596
|
+
const uploaded = await client.uploadOne(currentPath, item.file);
|
|
597
|
+
uploadedFiles.push(uploaded);
|
|
598
|
+
successCount += 1;
|
|
599
|
+
URL.revokeObjectURL(item.previewUrl);
|
|
600
|
+
setUploadQueue((current) => current.filter((entry) => entry.id !== item.id));
|
|
601
|
+
await load(currentPath, search, { silent: true });
|
|
602
|
+
} catch (caught) {
|
|
603
|
+
const message = caught instanceof Error ? caught.message : "Upload failed.";
|
|
604
|
+
URL.revokeObjectURL(item.previewUrl);
|
|
605
|
+
setUploadQueue((current) => current.filter((entry) => entry.id !== item.id));
|
|
606
|
+
if (/5\s*mb|too large|file size/i.test(message)) {
|
|
607
|
+
toastWarning(`"${item.file.name}" exceeds ${sizeLimit} and was skipped.`);
|
|
608
|
+
} else {
|
|
609
|
+
toastError(`${item.file.name}: ${message}`);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
if (successCount > 0) {
|
|
614
|
+
toastSuccess(successCount === 1 ? "File uploaded successfully." : `${successCount} files uploaded successfully.`);
|
|
615
|
+
if (autoSelectUploads && onSelectMany && uploadedFiles.length > 0) {
|
|
616
|
+
const limit = maxSelections ?? uploadedFiles.length;
|
|
617
|
+
onSelectMany(uploadedFiles.slice(0, limit));
|
|
618
|
+
onClose?.();
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
setUploading(false);
|
|
622
|
+
if (uploadInputRef.current) uploadInputRef.current.value = "";
|
|
623
|
+
}
|
|
624
|
+
async function uploadFiles(fileList) {
|
|
625
|
+
if (!fileList?.length) return;
|
|
626
|
+
await processFiles(Array.from(fileList));
|
|
627
|
+
}
|
|
628
|
+
function handleDragEnter(event) {
|
|
629
|
+
event.preventDefault();
|
|
630
|
+
if (!capabilities.upload) return;
|
|
631
|
+
dragCounterRef.current += 1;
|
|
632
|
+
setDragActive(true);
|
|
633
|
+
}
|
|
634
|
+
function handleDragLeave(event) {
|
|
635
|
+
event.preventDefault();
|
|
636
|
+
dragCounterRef.current -= 1;
|
|
637
|
+
if (dragCounterRef.current <= 0) {
|
|
638
|
+
dragCounterRef.current = 0;
|
|
639
|
+
setDragActive(false);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
function handleDragOver(event) {
|
|
643
|
+
event.preventDefault();
|
|
644
|
+
}
|
|
645
|
+
function handleDrop(event) {
|
|
646
|
+
event.preventDefault();
|
|
647
|
+
dragCounterRef.current = 0;
|
|
648
|
+
setDragActive(false);
|
|
649
|
+
if (!capabilities.upload) return;
|
|
650
|
+
void processFiles(Array.from(event.dataTransfer.files ?? []));
|
|
651
|
+
}
|
|
652
|
+
async function confirmDelete() {
|
|
653
|
+
if (!deleteTarget) return;
|
|
654
|
+
setDeleting(true);
|
|
655
|
+
try {
|
|
656
|
+
await client.remove(deleteTarget.path, deleteTarget.type);
|
|
657
|
+
if (deleteTarget.type === "file" && selected?.path === deleteTarget.path) {
|
|
658
|
+
setSelected(null);
|
|
659
|
+
}
|
|
660
|
+
const reloadPath = deleteTarget.type === "folder" && isPathInside(currentPath, deleteTarget.path) ? parentPath(deleteTarget.path) : currentPath;
|
|
661
|
+
const deletedName = deleteTarget.name;
|
|
662
|
+
const deletedType = deleteTarget.type;
|
|
663
|
+
setDeleteTarget(null);
|
|
664
|
+
await load(reloadPath, search);
|
|
665
|
+
toastSuccess(deletedType === "folder" ? `Folder "${deletedName}" deleted.` : `File "${deletedName}" deleted.`);
|
|
666
|
+
} catch (caught) {
|
|
667
|
+
toastError(caught instanceof Error ? caught.message : "Delete failed.");
|
|
668
|
+
} finally {
|
|
669
|
+
setDeleting(false);
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
function requestDelete(target, event) {
|
|
673
|
+
if (!capabilities.delete) {
|
|
674
|
+
toastError("You do not have permission to delete media.");
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
event?.preventDefault();
|
|
678
|
+
event?.stopPropagation();
|
|
679
|
+
setDeleteTarget(target);
|
|
680
|
+
}
|
|
681
|
+
function navigateTo(path) {
|
|
682
|
+
void load(path, search);
|
|
683
|
+
setSidebarOpen(false);
|
|
684
|
+
}
|
|
685
|
+
function toggleFileSelection(file) {
|
|
686
|
+
setSelectedFiles((current) => {
|
|
687
|
+
const exists = current.some((item) => item.path === file.path);
|
|
688
|
+
if (exists) {
|
|
689
|
+
return current.filter((item) => item.path !== file.path);
|
|
690
|
+
}
|
|
691
|
+
const limit = maxSelections ?? Number.POSITIVE_INFINITY;
|
|
692
|
+
if (current.length >= limit) {
|
|
693
|
+
toastWarning(`You can add up to ${limit} file${limit === 1 ? "" : "s"} at a time.`);
|
|
694
|
+
return current;
|
|
695
|
+
}
|
|
696
|
+
return [...current, file];
|
|
697
|
+
});
|
|
698
|
+
setSelected(file);
|
|
699
|
+
}
|
|
700
|
+
function confirmSelection() {
|
|
701
|
+
if (selectionMode === "multi" && onSelectMany) {
|
|
702
|
+
if (selectedFiles.length === 0) return;
|
|
703
|
+
onSelectMany(selectedFiles);
|
|
704
|
+
onClose?.();
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
if (!selected) return;
|
|
708
|
+
onSelect?.(selected);
|
|
709
|
+
if (closeOnSelect) onClose?.();
|
|
710
|
+
else setSelected(null);
|
|
711
|
+
}
|
|
712
|
+
function handleFileClick(file) {
|
|
713
|
+
if (selectionMode === "multi" && onSelectMany) {
|
|
714
|
+
toggleFileSelection(file);
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
setSelected(file);
|
|
718
|
+
if (!selectable && onSelect) {
|
|
719
|
+
onSelect(file);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
function handleFileDoubleClick(file) {
|
|
723
|
+
if (selectionMode === "multi" && onSelectMany) {
|
|
724
|
+
onSelectMany([file]);
|
|
725
|
+
onClose?.();
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
setSelected(file);
|
|
729
|
+
if (selectable) {
|
|
730
|
+
onSelect?.(file);
|
|
731
|
+
if (closeOnSelect) onClose?.();
|
|
732
|
+
else setSelected(null);
|
|
733
|
+
} else if (onSelect) {
|
|
734
|
+
onSelect(file);
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (!active) return null;
|
|
738
|
+
const crumbs = buildBreadcrumb(currentPath, resolved.rootLabel ?? "Root");
|
|
739
|
+
const sidebarFolders = [{ name: resolved.rootLabel ?? "Root", path: "" }, ...folders];
|
|
740
|
+
const showFooter = selectable;
|
|
741
|
+
const isMultiSelect = selectionMode === "multi" && Boolean(onSelectMany);
|
|
742
|
+
const footerSelectionCount = isMultiSelect ? selectedFiles.length : selected ? 1 : 0;
|
|
743
|
+
const footerCanConfirm = isMultiSelect ? selectedFiles.length > 0 : Boolean(selected);
|
|
744
|
+
const showCloseButton = variant === "modal" && Boolean(onClose);
|
|
745
|
+
const sidebarFolderList = /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "mt-4 space-y-1 lg:mt-5", children: sidebarFolders.map((folder) => {
|
|
746
|
+
const folderActive = folder.path === currentPath;
|
|
747
|
+
const canDelete = Boolean(folder.path) && capabilities.delete;
|
|
748
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
749
|
+
"div",
|
|
750
|
+
{
|
|
751
|
+
className: cn(
|
|
752
|
+
"flex items-center gap-1 rounded-lg transition",
|
|
753
|
+
folderActive ? "bg-[var(--bfml-primary)]" : "hover:bg-[var(--bfml-surface)]"
|
|
754
|
+
),
|
|
755
|
+
children: [
|
|
756
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
757
|
+
"button",
|
|
758
|
+
{
|
|
759
|
+
type: "button",
|
|
760
|
+
onClick: () => navigateTo(folder.path),
|
|
761
|
+
className: cn(
|
|
762
|
+
"flex min-w-0 flex-1 items-center gap-2 px-3 py-2.5 text-left text-sm font-medium transition",
|
|
763
|
+
folderActive ? "text-[var(--bfml-primary-foreground)]" : "text-[var(--bfml-foreground)]"
|
|
764
|
+
),
|
|
765
|
+
children: [
|
|
766
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Folder, { className: "h-4 w-4 shrink-0", "aria-hidden": "true" }),
|
|
767
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "truncate", children: folder.name })
|
|
768
|
+
]
|
|
769
|
+
}
|
|
770
|
+
),
|
|
771
|
+
canDelete ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
772
|
+
"button",
|
|
773
|
+
{
|
|
774
|
+
type: "button",
|
|
775
|
+
title: "Delete folder",
|
|
776
|
+
className: cn(
|
|
777
|
+
"mr-1 rounded-md p-1.5 transition",
|
|
778
|
+
folderActive ? "text-[var(--bfml-primary-foreground)] hover:bg-[var(--bfml-primary-foreground)]/15" : "text-[var(--bfml-destructive)] hover:bg-[var(--bfml-destructive-soft)]"
|
|
779
|
+
),
|
|
780
|
+
onClick: (event) => requestDelete({ path: folder.path, name: folder.name, type: "folder" }, event),
|
|
781
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Trash2, { className: "h-3.5 w-3.5" })
|
|
782
|
+
}
|
|
783
|
+
) : null
|
|
784
|
+
]
|
|
785
|
+
},
|
|
786
|
+
folder.path || "root"
|
|
787
|
+
);
|
|
788
|
+
}) });
|
|
789
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
790
|
+
"section",
|
|
791
|
+
{
|
|
792
|
+
...rootProps,
|
|
793
|
+
className: cn(
|
|
794
|
+
rootProps.className,
|
|
795
|
+
"relative flex h-full min-h-0 flex-col overflow-hidden bg-[var(--bfml-surface)]",
|
|
796
|
+
variant === "modal" && "h-[100dvh] w-full max-w-none border-0 shadow-[var(--bfml-shadow-lg)] sm:h-[min(92vh,760px)] sm:max-w-6xl sm:rounded-2xl sm:border sm:border-[var(--bfml-border)]",
|
|
797
|
+
variant === "embedded" && "rounded-none border-0 shadow-none",
|
|
798
|
+
className
|
|
799
|
+
),
|
|
800
|
+
children: [
|
|
801
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("header", { className: "flex shrink-0 items-start justify-between gap-3 border-b border-[var(--bfml-border)] px-4 py-3 sm:gap-4 sm:px-6 sm:py-5", children: [
|
|
802
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "min-w-0 pr-2", children: [
|
|
803
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("h2", { className: "truncate text-base font-semibold text-[var(--bfml-foreground)] sm:text-lg", children: title }),
|
|
804
|
+
description ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-1 hidden text-sm text-[var(--bfml-muted-foreground)] sm:block", children: description }) : null
|
|
805
|
+
] }),
|
|
806
|
+
showCloseButton ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { type: "button", variant: "ghost", className: "h-10 w-10 shrink-0 px-0", onClick: onClose, "aria-label": "Close media library", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "h-4 w-4", "aria-hidden": "true" }) }) : null
|
|
807
|
+
] }),
|
|
808
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(ToastContainer, { theme: themeMode }),
|
|
809
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative flex min-h-0 flex-1 flex-col overflow-hidden lg:grid lg:h-full lg:min-h-0 lg:grid-cols-[minmax(0,240px)_1fr] lg:grid-rows-1", children: [
|
|
810
|
+
sidebarOpen ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
811
|
+
"button",
|
|
812
|
+
{
|
|
813
|
+
type: "button",
|
|
814
|
+
className: "absolute inset-0 z-20 lg:hidden",
|
|
815
|
+
style: { backgroundColor: "var(--bfml-overlay)" },
|
|
816
|
+
"aria-label": "Close folders panel",
|
|
817
|
+
onClick: () => setSidebarOpen(false)
|
|
818
|
+
}
|
|
819
|
+
) : null,
|
|
820
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
821
|
+
"aside",
|
|
822
|
+
{
|
|
823
|
+
className: cn(
|
|
824
|
+
"absolute inset-y-0 left-0 z-30 flex w-[min(88vw,280px)] flex-col overflow-y-auto border-r border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)] p-4 shadow-xl transition-transform duration-200 ease-out lg:static lg:z-auto lg:h-full lg:min-h-0 lg:w-auto lg:translate-x-0 lg:shadow-none",
|
|
825
|
+
sidebarOpen ? "translate-x-0" : "-translate-x-full lg:translate-x-0"
|
|
826
|
+
),
|
|
827
|
+
children: [
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "mb-3 flex items-center justify-between lg:hidden", children: [
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm font-semibold text-[var(--bfml-foreground)]", children: "Folders" }),
|
|
830
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { type: "button", variant: "ghost", className: "h-9 w-9 px-0", onClick: () => setSidebarOpen(false), "aria-label": "Close folders panel", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.X, { className: "h-4 w-4" }) })
|
|
831
|
+
] }),
|
|
832
|
+
capabilities.createFolder ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "space-y-3", children: [
|
|
833
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col gap-2 sm:flex-row", children: [
|
|
834
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
835
|
+
Input,
|
|
836
|
+
{
|
|
837
|
+
value: folderName,
|
|
838
|
+
onChange: (event) => setFolderName(event.target.value),
|
|
839
|
+
placeholder: "new-folder",
|
|
840
|
+
className: "min-w-0"
|
|
841
|
+
}
|
|
842
|
+
),
|
|
843
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { type: "button", variant: "secondary", className: "shrink-0 sm:px-4", onClick: createFolder, children: "Add" })
|
|
844
|
+
] }),
|
|
845
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("label", { className: "flex items-start gap-2 text-xs leading-5 text-[var(--bfml-muted-foreground)]", children: [
|
|
846
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
847
|
+
"input",
|
|
848
|
+
{
|
|
849
|
+
type: "checkbox",
|
|
850
|
+
className: "mt-0.5",
|
|
851
|
+
checked: nestedFolder,
|
|
852
|
+
onChange: (event) => setNestedFolder(event.target.checked)
|
|
853
|
+
}
|
|
854
|
+
),
|
|
855
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { children: "Create as nested folder of current path" })
|
|
856
|
+
] })
|
|
857
|
+
] }) : null,
|
|
858
|
+
sidebarFolderList
|
|
859
|
+
]
|
|
860
|
+
}
|
|
861
|
+
),
|
|
862
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden p-3 sm:p-5 lg:h-full lg:min-h-0", children: [
|
|
863
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-wrap items-center gap-2 sm:gap-3", children: [
|
|
864
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
865
|
+
Button,
|
|
866
|
+
{
|
|
867
|
+
type: "button",
|
|
868
|
+
variant: "secondary",
|
|
869
|
+
className: "shrink-0 lg:hidden",
|
|
870
|
+
onClick: () => setSidebarOpen(true),
|
|
871
|
+
"aria-label": "Open folders panel",
|
|
872
|
+
children: [
|
|
873
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.PanelLeft, { className: "h-4 w-4" }),
|
|
874
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "hidden sm:inline", children: "Folders" })
|
|
875
|
+
]
|
|
876
|
+
}
|
|
877
|
+
),
|
|
878
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "relative min-w-0 flex-1 basis-[180px]", children: [
|
|
879
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Search, { className: "pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-[var(--bfml-muted-foreground)]" }),
|
|
880
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
881
|
+
Input,
|
|
882
|
+
{
|
|
883
|
+
className: "pl-9",
|
|
884
|
+
value: search,
|
|
885
|
+
onChange: (event) => setSearch(event.target.value),
|
|
886
|
+
onKeyDown: (event) => {
|
|
887
|
+
if (event.key === "Enter") void load(currentPath, search);
|
|
888
|
+
},
|
|
889
|
+
placeholder: "Search files"
|
|
890
|
+
}
|
|
891
|
+
)
|
|
892
|
+
] }),
|
|
893
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
894
|
+
"input",
|
|
895
|
+
{
|
|
896
|
+
ref: uploadInputRef,
|
|
897
|
+
type: "file",
|
|
898
|
+
multiple: true,
|
|
899
|
+
className: "hidden",
|
|
900
|
+
accept: accept?.includes("pdf") ? "image/*,application/pdf" : "image/*",
|
|
901
|
+
onChange: (event) => void uploadFiles(event.target.files)
|
|
902
|
+
}
|
|
903
|
+
),
|
|
904
|
+
capabilities.upload ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
905
|
+
Button,
|
|
906
|
+
{
|
|
907
|
+
type: "button",
|
|
908
|
+
disabled: uploading,
|
|
909
|
+
className: "w-full shrink-0 sm:w-auto",
|
|
910
|
+
onClick: () => uploadInputRef.current?.click(),
|
|
911
|
+
"aria-label": "Upload files",
|
|
912
|
+
children: [
|
|
913
|
+
uploading ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.LoaderCircle, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Upload, { className: "h-4 w-4" }),
|
|
914
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { className: "sm:inline", children: "Upload" })
|
|
915
|
+
]
|
|
916
|
+
}
|
|
917
|
+
) : null
|
|
918
|
+
] }),
|
|
919
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "-mx-1 mt-3 flex items-center gap-1 overflow-x-auto px-1 pb-1 text-sm sm:mt-4", children: crumbs.map((crumb, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex shrink-0 items-center gap-1", children: [
|
|
920
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
921
|
+
"button",
|
|
922
|
+
{
|
|
923
|
+
type: "button",
|
|
924
|
+
className: cn(
|
|
925
|
+
"max-w-[9rem] truncate rounded px-1.5 py-1 font-medium transition sm:max-w-none",
|
|
926
|
+
index === crumbs.length - 1 ? "text-[var(--bfml-primary)]" : "text-[var(--bfml-muted-foreground)] hover:text-[var(--bfml-foreground)]"
|
|
927
|
+
),
|
|
928
|
+
onClick: () => navigateTo(crumb.path),
|
|
929
|
+
children: crumb.label
|
|
930
|
+
}
|
|
931
|
+
),
|
|
932
|
+
index < crumbs.length - 1 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ChevronRight, { className: "h-4 w-4 shrink-0 text-[var(--bfml-muted-foreground)]" }) : null
|
|
933
|
+
] }, crumb.path)) }),
|
|
934
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
935
|
+
"div",
|
|
936
|
+
{
|
|
937
|
+
className: cn(
|
|
938
|
+
"relative mt-3 min-h-0 flex-1 overflow-y-auto sm:mt-4",
|
|
939
|
+
dragActive && capabilities.upload && "rounded-xl ring-2 ring-[var(--bfml-primary)] ring-offset-2 ring-offset-[var(--bfml-surface)]"
|
|
940
|
+
),
|
|
941
|
+
onDragEnter: handleDragEnter,
|
|
942
|
+
onDragLeave: handleDragLeave,
|
|
943
|
+
onDragOver: handleDragOver,
|
|
944
|
+
onDrop: handleDrop,
|
|
945
|
+
children: [
|
|
946
|
+
dragActive && capabilities.upload ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "pointer-events-none absolute inset-0 z-10 flex items-center justify-center rounded-xl border-2 border-dashed border-[var(--bfml-primary)] bg-[var(--bfml-primary-soft)]/80 px-4 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { children: [
|
|
947
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Upload, { className: "mx-auto mb-2 h-8 w-8 text-[var(--bfml-primary)]" }),
|
|
948
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm font-semibold text-[var(--bfml-foreground)]", children: "Drop files to upload" }),
|
|
949
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-1 text-xs text-[var(--bfml-muted-foreground)]", children: "Files upload one by one" })
|
|
950
|
+
] }) }) : null,
|
|
951
|
+
loading && uploadQueue.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex h-32 items-center justify-center text-sm text-[var(--bfml-muted-foreground)] sm:h-40", children: [
|
|
952
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.LoaderCircle, { className: "mr-2 h-4 w-4 animate-spin" }),
|
|
953
|
+
"Loading media..."
|
|
954
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "grid grid-cols-2 gap-3 sm:grid-cols-3 sm:gap-4 lg:grid-cols-3 xl:grid-cols-4", children: [
|
|
955
|
+
uploadQueue.map((item) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(UploadPreviewCard, { item }, item.id)),
|
|
956
|
+
folders.map((folder) => /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
957
|
+
"div",
|
|
958
|
+
{
|
|
959
|
+
className: "group relative rounded-xl border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)] transition hover:border-[var(--bfml-primary-border)]",
|
|
960
|
+
children: [
|
|
961
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
962
|
+
"button",
|
|
963
|
+
{
|
|
964
|
+
type: "button",
|
|
965
|
+
onDoubleClick: () => navigateTo(folder.path),
|
|
966
|
+
onClick: () => navigateTo(folder.path),
|
|
967
|
+
className: "block w-full p-3 text-left sm:p-4",
|
|
968
|
+
children: [
|
|
969
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Folder, { className: "h-7 w-7 text-[var(--bfml-primary)] sm:h-8 sm:w-8" }),
|
|
970
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-2 truncate text-sm font-medium text-[var(--bfml-foreground)] sm:mt-3", children: folder.name }),
|
|
971
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-xs text-[var(--bfml-muted-foreground)]", children: "Folder" })
|
|
972
|
+
]
|
|
973
|
+
}
|
|
974
|
+
),
|
|
975
|
+
capabilities.delete ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
976
|
+
"button",
|
|
977
|
+
{
|
|
978
|
+
type: "button",
|
|
979
|
+
title: "Delete folder",
|
|
980
|
+
className: "absolute right-1.5 top-1.5 rounded-md border border-[var(--bfml-border)] bg-[var(--bfml-surface)] p-1.5 shadow-sm transition hover:bg-[var(--bfml-destructive-soft)] sm:right-2 sm:top-2",
|
|
981
|
+
onClick: (event) => requestDelete({ path: folder.path, name: folder.name, type: "folder" }, event),
|
|
982
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Trash2, { className: "h-3.5 w-3.5 text-[var(--bfml-destructive)] sm:h-4 sm:w-4" })
|
|
983
|
+
}
|
|
984
|
+
) : null
|
|
985
|
+
]
|
|
986
|
+
},
|
|
987
|
+
folder.path
|
|
988
|
+
)),
|
|
989
|
+
files.map((file) => {
|
|
990
|
+
const fileActive = isMultiSelect ? selectedFiles.some((item) => item.path === file.path) : selected?.path === file.path;
|
|
991
|
+
const isImage = file.mimeType.startsWith("image/");
|
|
992
|
+
return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
993
|
+
"div",
|
|
994
|
+
{
|
|
995
|
+
className: cn(
|
|
996
|
+
"group relative overflow-hidden rounded-xl border bg-[var(--bfml-surface)] transition",
|
|
997
|
+
fileActive && selectable ? "border-[var(--bfml-primary)] ring-2 ring-[var(--bfml-primary-soft)]" : "border-[var(--bfml-border)] hover:border-[var(--bfml-primary-border)]"
|
|
998
|
+
),
|
|
999
|
+
children: [
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
|
|
1001
|
+
"button",
|
|
1002
|
+
{
|
|
1003
|
+
type: "button",
|
|
1004
|
+
className: "block w-full p-2 text-left sm:p-3",
|
|
1005
|
+
onClick: () => handleFileClick(file),
|
|
1006
|
+
onDoubleClick: () => handleFileDoubleClick(file),
|
|
1007
|
+
children: [
|
|
1008
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "flex h-20 items-center justify-center overflow-hidden rounded-lg bg-[var(--bfml-surface-soft)] sm:h-28", children: isImage ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("img", { src: file.url, alt: file.name, className: "h-full w-full object-contain" }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "text-xs font-semibold uppercase text-[var(--bfml-muted-foreground)]", children: "PDF" }) }),
|
|
1009
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-2 truncate text-sm font-medium text-[var(--bfml-foreground)] sm:mt-3", children: file.name }),
|
|
1010
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-xs text-[var(--bfml-muted-foreground)]", children: "File" })
|
|
1011
|
+
]
|
|
1012
|
+
}
|
|
1013
|
+
),
|
|
1014
|
+
capabilities.delete ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1015
|
+
"button",
|
|
1016
|
+
{
|
|
1017
|
+
type: "button",
|
|
1018
|
+
title: "Delete file",
|
|
1019
|
+
className: "absolute right-1.5 top-1.5 rounded-md border border-[var(--bfml-border)] bg-[var(--bfml-surface)] p-1.5 shadow-sm transition hover:bg-[var(--bfml-destructive-soft)] sm:right-2 sm:top-2",
|
|
1020
|
+
onClick: (event) => requestDelete({ path: file.path, name: file.name, type: "file" }, event),
|
|
1021
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.Trash2, { className: "h-3.5 w-3.5 text-[var(--bfml-destructive)] sm:h-4 sm:w-4" })
|
|
1022
|
+
}
|
|
1023
|
+
) : null
|
|
1024
|
+
]
|
|
1025
|
+
},
|
|
1026
|
+
file.path
|
|
1027
|
+
);
|
|
1028
|
+
})
|
|
1029
|
+
] }),
|
|
1030
|
+
!loading && folders.length === 0 && files.length === 0 && uploadQueue.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex h-32 flex-col items-center justify-center rounded-xl border border-dashed border-[var(--bfml-border)] px-4 text-center text-sm text-[var(--bfml-muted-foreground)] sm:h-40", children: [
|
|
1031
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_lucide_react3.ImagePlus, { className: "mb-2 h-6 w-6" }),
|
|
1032
|
+
"No files in this folder yet.",
|
|
1033
|
+
capabilities.upload ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "mt-2 text-xs", children: "Drag and drop files here or use Upload." }) : null
|
|
1034
|
+
] }) : null
|
|
1035
|
+
]
|
|
1036
|
+
}
|
|
1037
|
+
)
|
|
1038
|
+
] })
|
|
1039
|
+
] }),
|
|
1040
|
+
showFooter ? /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("footer", { className: "flex shrink-0 flex-col-reverse gap-2 border-t border-[var(--bfml-border)] px-4 py-3 sm:flex-row sm:items-center sm:justify-between sm:gap-4 sm:px-6 sm:py-4", children: [
|
|
1041
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "truncate text-center text-sm text-[var(--bfml-muted-foreground)] sm:text-left", children: isMultiSelect ? footerSelectionCount === 0 ? "Select one or more files" : `${footerSelectionCount} file${footerSelectionCount === 1 ? "" : "s"} selected` : `Selected: ${selected ? selected.name : "None"}` }),
|
|
1042
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(Button, { type: "button", className: "w-full sm:w-auto", disabled: !footerCanConfirm, onClick: confirmSelection, children: isMultiSelect ? footerSelectionCount > 0 ? `Add ${footerSelectionCount} file${footerSelectionCount === 1 ? "" : "s"}` : "Add files" : closeOnSelect ? "Done" : "Add" })
|
|
1043
|
+
] }) : null,
|
|
1044
|
+
/* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
|
|
1045
|
+
ConfirmDialog,
|
|
1046
|
+
{
|
|
1047
|
+
open: Boolean(deleteTarget),
|
|
1048
|
+
title: deleteTarget?.type === "folder" ? "Delete folder?" : "Delete file?",
|
|
1049
|
+
description: deleteTarget ? deleteTarget.type === "folder" ? `Are you sure you want to delete the folder "${deleteTarget.name}" and everything inside it? This action cannot be undone.` : `Are you sure you want to delete "${deleteTarget.name}"? This action cannot be undone.` : "",
|
|
1050
|
+
confirmLabel: "Delete",
|
|
1051
|
+
loading: deleting,
|
|
1052
|
+
onCancel: () => {
|
|
1053
|
+
if (!deleting) setDeleteTarget(null);
|
|
1054
|
+
},
|
|
1055
|
+
onConfirm: () => void confirmDelete(),
|
|
1056
|
+
theme: themeMode
|
|
1057
|
+
}
|
|
1058
|
+
)
|
|
1059
|
+
]
|
|
1060
|
+
}
|
|
1061
|
+
);
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// src/components/media-library-modal.tsx
|
|
1065
|
+
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
1066
|
+
function MediaLibraryModal({
|
|
1067
|
+
open,
|
|
1068
|
+
onClose,
|
|
1069
|
+
onSelect,
|
|
1070
|
+
onSelectMany,
|
|
1071
|
+
closeOnSelect = true,
|
|
1072
|
+
selectionMode = "single",
|
|
1073
|
+
maxSelections,
|
|
1074
|
+
autoSelectUploads = false,
|
|
1075
|
+
config,
|
|
1076
|
+
theme,
|
|
1077
|
+
title = "Media Library",
|
|
1078
|
+
description = "Create folders, upload files, and choose media.",
|
|
1079
|
+
accept
|
|
1080
|
+
}) {
|
|
1081
|
+
const resolved = { ...defaultMediaLibraryConfig, ...config };
|
|
1082
|
+
const themeMode = resolveThemeMode(theme ?? resolved.theme);
|
|
1083
|
+
const rootProps = bfmlRootProps(themeMode);
|
|
1084
|
+
if (!open || typeof document === "undefined") return null;
|
|
1085
|
+
return (0, import_react_dom2.createPortal)(
|
|
1086
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1087
|
+
"div",
|
|
1088
|
+
{
|
|
1089
|
+
...rootProps,
|
|
1090
|
+
className: cn(
|
|
1091
|
+
rootProps.className,
|
|
1092
|
+
"fixed inset-0 z-[9999] flex items-stretch justify-center p-0 backdrop-blur-sm sm:items-center sm:p-2 md:p-4"
|
|
1093
|
+
),
|
|
1094
|
+
style: { backgroundColor: "var(--bfml-overlay)" },
|
|
1095
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
|
|
1096
|
+
MediaLibraryPanel,
|
|
1097
|
+
{
|
|
1098
|
+
active: open,
|
|
1099
|
+
variant: "modal",
|
|
1100
|
+
selectable: true,
|
|
1101
|
+
config,
|
|
1102
|
+
theme,
|
|
1103
|
+
title,
|
|
1104
|
+
description,
|
|
1105
|
+
accept,
|
|
1106
|
+
onClose,
|
|
1107
|
+
onSelect,
|
|
1108
|
+
onSelectMany,
|
|
1109
|
+
closeOnSelect,
|
|
1110
|
+
selectionMode,
|
|
1111
|
+
maxSelections,
|
|
1112
|
+
autoSelectUploads
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
}
|
|
1116
|
+
),
|
|
1117
|
+
document.body
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
function MediaPreview({ path, alt }) {
|
|
1121
|
+
const isImage = /\.(png|jpe?g|webp|gif)$/i.test(path);
|
|
1122
|
+
if (!path) {
|
|
1123
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex h-32 items-center justify-center rounded-xl border border-dashed border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)] px-4 text-center text-sm text-[var(--bfml-muted-foreground)] sm:h-40", children: "No media selected" });
|
|
1124
|
+
}
|
|
1125
|
+
if (isImage) {
|
|
1126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "overflow-hidden rounded-xl border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)]", children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("img", { src: path, alt: alt ?? fileNameFromPath(path), className: "h-32 w-full object-contain sm:h-40" }) });
|
|
1127
|
+
}
|
|
1128
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: "flex h-32 items-center justify-center rounded-xl border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)] px-4 text-center text-sm font-medium text-[var(--bfml-foreground)] sm:h-40", children: fileNameFromPath(path) });
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/components/media-library-widget.tsx
|
|
1132
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
1133
|
+
function MediaLibraryWidget({
|
|
1134
|
+
width = "100%",
|
|
1135
|
+
height = 640,
|
|
1136
|
+
config,
|
|
1137
|
+
theme,
|
|
1138
|
+
title = "Media Library",
|
|
1139
|
+
description = "Create folders, upload files, and manage media.",
|
|
1140
|
+
accept,
|
|
1141
|
+
selectable = false,
|
|
1142
|
+
onSelect,
|
|
1143
|
+
className
|
|
1144
|
+
}) {
|
|
1145
|
+
const resolved = { ...defaultMediaLibraryConfig, ...config };
|
|
1146
|
+
const themeMode = resolveThemeMode(theme ?? resolved.theme);
|
|
1147
|
+
const rootProps = bfmlRootProps(themeMode);
|
|
1148
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1149
|
+
"div",
|
|
1150
|
+
{
|
|
1151
|
+
...rootProps,
|
|
1152
|
+
className: cn(
|
|
1153
|
+
rootProps.className,
|
|
1154
|
+
"overflow-hidden rounded-2xl border border-[var(--bfml-border)] bg-[var(--bfml-surface)] shadow-[var(--bfml-shadow-lg)]",
|
|
1155
|
+
className
|
|
1156
|
+
),
|
|
1157
|
+
style: {
|
|
1158
|
+
width: toCssSize(width),
|
|
1159
|
+
height: toCssSize(height)
|
|
1160
|
+
},
|
|
1161
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
|
|
1162
|
+
MediaLibraryPanel,
|
|
1163
|
+
{
|
|
1164
|
+
active: true,
|
|
1165
|
+
variant: "embedded",
|
|
1166
|
+
config,
|
|
1167
|
+
theme,
|
|
1168
|
+
title,
|
|
1169
|
+
description,
|
|
1170
|
+
accept,
|
|
1171
|
+
selectable,
|
|
1172
|
+
onSelect,
|
|
1173
|
+
className: "h-full"
|
|
1174
|
+
}
|
|
1175
|
+
)
|
|
1176
|
+
}
|
|
1177
|
+
);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
// src/components/media-picker.tsx
|
|
1181
|
+
var import_react3 = require("react");
|
|
1182
|
+
var import_lucide_react5 = require("lucide-react");
|
|
1183
|
+
|
|
1184
|
+
// src/components/picker-thumbnail.tsx
|
|
1185
|
+
var import_lucide_react4 = require("lucide-react");
|
|
1186
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
1187
|
+
var sizeClasses = {
|
|
1188
|
+
sm: "h-9 w-9",
|
|
1189
|
+
md: "h-10 w-10 sm:h-12 sm:w-12",
|
|
1190
|
+
grid: "h-full w-full"
|
|
1191
|
+
};
|
|
1192
|
+
function PickerThumbnail({
|
|
1193
|
+
path,
|
|
1194
|
+
alt,
|
|
1195
|
+
size = "md",
|
|
1196
|
+
shape = "circle",
|
|
1197
|
+
className
|
|
1198
|
+
}) {
|
|
1199
|
+
const isImage = Boolean(path && isImagePath(path));
|
|
1200
|
+
const rounded = shape === "circle" ? "rounded-full" : "rounded-lg";
|
|
1201
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1202
|
+
"span",
|
|
1203
|
+
{
|
|
1204
|
+
className: cn(
|
|
1205
|
+
"flex shrink-0 items-center justify-center overflow-hidden border border-[var(--bfml-border)] bg-[var(--bfml-surface)]",
|
|
1206
|
+
size !== "grid" && sizeClasses[size],
|
|
1207
|
+
rounded,
|
|
1208
|
+
!path && "text-[var(--bfml-primary)]",
|
|
1209
|
+
className
|
|
1210
|
+
),
|
|
1211
|
+
children: !path ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ImagePlus, { className: size === "sm" ? "h-4 w-4" : "h-5 w-5", "aria-hidden": "true" }) : isImage ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("img", { src: path, alt: alt ?? "Selected media", className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.FileText, { className: size === "sm" ? "h-4 w-4" : "h-5 w-5", "aria-hidden": "true" })
|
|
1212
|
+
}
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
function PickerThumbnailStack({ paths }) {
|
|
1216
|
+
if (paths.length === 0) {
|
|
1217
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("span", { className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-full bg-[var(--bfml-surface)] text-[var(--bfml-primary)] sm:h-12 sm:w-12", children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_lucide_react4.ImagePlus, { className: "h-5 w-5", "aria-hidden": "true" }) });
|
|
1218
|
+
}
|
|
1219
|
+
if (paths.length === 1) {
|
|
1220
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PickerThumbnail, { path: paths[0], alt: "Selected media" });
|
|
1221
|
+
}
|
|
1222
|
+
const visible = paths.slice(0, 3);
|
|
1223
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "relative flex h-10 w-10 shrink-0 items-center sm:h-12 sm:w-12", children: [
|
|
1224
|
+
visible.map((path, index) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1225
|
+
"span",
|
|
1226
|
+
{
|
|
1227
|
+
className: cn(
|
|
1228
|
+
"absolute overflow-hidden rounded-full border-2 border-[var(--bfml-surface-soft)] bg-[var(--bfml-surface)]",
|
|
1229
|
+
index === 0 && "left-0 top-0 z-30 h-7 w-7 sm:h-8 sm:w-8",
|
|
1230
|
+
index === 1 && "left-3 top-1 z-20 h-7 w-7 sm:left-4 sm:h-8 sm:w-8",
|
|
1231
|
+
index === 2 && "left-1 top-3 z-10 h-7 w-7 sm:top-4 sm:h-8 sm:w-8"
|
|
1232
|
+
),
|
|
1233
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PickerThumbnail, { path, size: "grid", shape: "circle", className: "h-full w-full border-0" })
|
|
1234
|
+
},
|
|
1235
|
+
`${path}-${index}`
|
|
1236
|
+
)),
|
|
1237
|
+
paths.length > 3 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "absolute -bottom-0.5 -right-0.5 z-40 rounded-full bg-[var(--bfml-primary)] px-1.5 py-0.5 text-[10px] font-semibold leading-none text-[var(--bfml-primary-foreground)]", children: [
|
|
1238
|
+
"+",
|
|
1239
|
+
paths.length - 3
|
|
1240
|
+
] }) : null
|
|
1241
|
+
] });
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
// src/components/media-picker.tsx
|
|
1245
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1246
|
+
function MediaPicker({
|
|
1247
|
+
name,
|
|
1248
|
+
label = "Choose image",
|
|
1249
|
+
title = "Media Library",
|
|
1250
|
+
description = "Create folders, upload files, and choose media.",
|
|
1251
|
+
value,
|
|
1252
|
+
defaultValue = "",
|
|
1253
|
+
onChange,
|
|
1254
|
+
config,
|
|
1255
|
+
theme,
|
|
1256
|
+
accept = ["image"],
|
|
1257
|
+
className
|
|
1258
|
+
}) {
|
|
1259
|
+
const themeMode = resolveThemeMode(theme ?? config?.theme);
|
|
1260
|
+
const rootProps = bfmlRootProps(themeMode);
|
|
1261
|
+
const [open, setOpen] = (0, import_react3.useState)(false);
|
|
1262
|
+
const [selectedPath, setSelectedPath] = (0, import_react3.useState)(value ?? defaultValue);
|
|
1263
|
+
(0, import_react3.useEffect)(() => {
|
|
1264
|
+
if (value !== void 0) setSelectedPath(value);
|
|
1265
|
+
}, [value]);
|
|
1266
|
+
function handleSelect(file) {
|
|
1267
|
+
setSelectedPath(file.url);
|
|
1268
|
+
onChange?.(file.url);
|
|
1269
|
+
setOpen(false);
|
|
1270
|
+
}
|
|
1271
|
+
const currentValue = value ?? selectedPath;
|
|
1272
|
+
const fileName = currentValue ? fileNameFromPath(currentValue) : null;
|
|
1273
|
+
return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { ...rootProps, className: cn(rootProps.className, "space-y-2", className), children: [
|
|
1274
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("label", { className: "text-sm font-medium text-[var(--bfml-foreground)]", children: label }) : null,
|
|
1275
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
|
|
1276
|
+
"button",
|
|
1277
|
+
{
|
|
1278
|
+
type: "button",
|
|
1279
|
+
onClick: () => setOpen(true),
|
|
1280
|
+
className: "flex w-full items-center gap-3 rounded-xl border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)] px-3 py-3 text-left transition hover:border-[var(--bfml-primary-border)] hover:bg-[var(--bfml-surface)] sm:gap-4 sm:px-4 sm:py-4",
|
|
1281
|
+
children: [
|
|
1282
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PickerThumbnail, { path: currentValue, alt: fileName ?? label }),
|
|
1283
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
1284
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "block truncate text-sm font-semibold text-[var(--bfml-foreground)]", children: label }),
|
|
1285
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "mt-0.5 block truncate text-xs text-[var(--bfml-muted-foreground)]", children: fileName ?? "Select from folders or upload new" })
|
|
1286
|
+
] }),
|
|
1287
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_lucide_react5.Upload, { className: "hidden h-5 w-5 shrink-0 text-[var(--bfml-muted-foreground)] sm:block", "aria-hidden": "true" })
|
|
1288
|
+
]
|
|
1289
|
+
}
|
|
1290
|
+
),
|
|
1291
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)("input", { type: "hidden", name, value: currentValue }),
|
|
1292
|
+
/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
|
|
1293
|
+
MediaLibraryModal,
|
|
1294
|
+
{
|
|
1295
|
+
open,
|
|
1296
|
+
onClose: () => setOpen(false),
|
|
1297
|
+
onSelect: handleSelect,
|
|
1298
|
+
config,
|
|
1299
|
+
theme: themeMode,
|
|
1300
|
+
title,
|
|
1301
|
+
description,
|
|
1302
|
+
accept
|
|
1303
|
+
}
|
|
1304
|
+
)
|
|
1305
|
+
] });
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// src/components/media-picker-multi.tsx
|
|
1309
|
+
var import_react4 = require("react");
|
|
1310
|
+
var import_lucide_react6 = require("lucide-react");
|
|
1311
|
+
var import_jsx_runtime11 = require("react/jsx-runtime");
|
|
1312
|
+
function MediaPickerMulti({
|
|
1313
|
+
name,
|
|
1314
|
+
label = "Choose attachments",
|
|
1315
|
+
title = "Media Library",
|
|
1316
|
+
description = "Create folders, upload files, and choose one or more attachments.",
|
|
1317
|
+
max = 10,
|
|
1318
|
+
values,
|
|
1319
|
+
defaultValues = [],
|
|
1320
|
+
onChange,
|
|
1321
|
+
config,
|
|
1322
|
+
theme,
|
|
1323
|
+
accept = ["image", "pdf"],
|
|
1324
|
+
className
|
|
1325
|
+
}) {
|
|
1326
|
+
const themeMode = resolveThemeMode(theme ?? config?.theme);
|
|
1327
|
+
const rootProps = bfmlRootProps(themeMode);
|
|
1328
|
+
const [open, setOpen] = (0, import_react4.useState)(false);
|
|
1329
|
+
const [selectedPaths, setSelectedPaths] = (0, import_react4.useState)(values ?? defaultValues);
|
|
1330
|
+
(0, import_react4.useEffect)(() => {
|
|
1331
|
+
if (values !== void 0) setSelectedPaths(values);
|
|
1332
|
+
}, [values]);
|
|
1333
|
+
const currentValues = values ?? selectedPaths;
|
|
1334
|
+
const atMax = currentValues.length >= max;
|
|
1335
|
+
function handleSelectMany(files) {
|
|
1336
|
+
setSelectedPaths((current) => {
|
|
1337
|
+
const next = [...current];
|
|
1338
|
+
for (const file of files) {
|
|
1339
|
+
if (next.includes(file.url) || next.length >= max) continue;
|
|
1340
|
+
next.push(file.url);
|
|
1341
|
+
}
|
|
1342
|
+
onChange?.(next);
|
|
1343
|
+
return next;
|
|
1344
|
+
});
|
|
1345
|
+
setOpen(false);
|
|
1346
|
+
}
|
|
1347
|
+
function removeAt(index) {
|
|
1348
|
+
setSelectedPaths((current) => {
|
|
1349
|
+
const next = current.filter((_, itemIndex) => itemIndex !== index);
|
|
1350
|
+
onChange?.(next);
|
|
1351
|
+
return next;
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { ...rootProps, className: cn(rootProps.className, "space-y-2", className), children: [
|
|
1355
|
+
label ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "text-sm font-medium text-[var(--bfml-foreground)]", children: label }) : null,
|
|
1356
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "overflow-hidden rounded-xl border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)]", children: [
|
|
1357
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1358
|
+
"button",
|
|
1359
|
+
{
|
|
1360
|
+
type: "button",
|
|
1361
|
+
onClick: () => setOpen(true),
|
|
1362
|
+
disabled: atMax,
|
|
1363
|
+
className: "flex w-full items-center gap-3 px-3 py-3 text-left transition hover:bg-[var(--bfml-surface)] disabled:cursor-not-allowed disabled:opacity-60 sm:gap-4 sm:px-4 sm:py-4",
|
|
1364
|
+
children: [
|
|
1365
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(PickerThumbnailStack, { paths: currentValues }),
|
|
1366
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { className: "min-w-0 flex-1", children: [
|
|
1367
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "block truncate text-sm font-semibold text-[var(--bfml-foreground)]", children: label }),
|
|
1368
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "mt-0.5 block text-xs text-[var(--bfml-muted-foreground)]", children: currentValues.length > 0 ? `${currentValues.length} selected \xB7 click to add more (${currentValues.length}/${max})` : `Select from folders or upload new (0/${max})` })
|
|
1369
|
+
] }),
|
|
1370
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react6.Upload, { className: "hidden h-5 w-5 shrink-0 text-[var(--bfml-muted-foreground)] sm:block", "aria-hidden": "true" })
|
|
1371
|
+
]
|
|
1372
|
+
}
|
|
1373
|
+
),
|
|
1374
|
+
currentValues.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "grid grid-cols-3 gap-2 border-t border-[var(--bfml-border)] bg-[var(--bfml-surface)] p-3 sm:grid-cols-4 sm:p-4", children: currentValues.map((path, index) => {
|
|
1375
|
+
const fileName = fileNameFromPath(path);
|
|
1376
|
+
const isImage = isImagePath(path);
|
|
1377
|
+
return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
|
|
1378
|
+
"div",
|
|
1379
|
+
{
|
|
1380
|
+
className: "group relative aspect-square overflow-hidden rounded-lg border border-[var(--bfml-border)] bg-[var(--bfml-surface-soft)]",
|
|
1381
|
+
children: [
|
|
1382
|
+
isImage ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("img", { src: path, alt: fileName, className: "h-full w-full object-cover" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1 px-1 text-center text-[var(--bfml-muted-foreground)]", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-[10px] font-semibold uppercase", children: "PDF" }) }),
|
|
1383
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1384
|
+
"button",
|
|
1385
|
+
{
|
|
1386
|
+
type: "button",
|
|
1387
|
+
className: "absolute right-1 top-1 rounded-md border border-[var(--bfml-border)] bg-[var(--bfml-surface)] p-1 shadow-sm transition hover:bg-[var(--bfml-destructive-soft)]",
|
|
1388
|
+
onClick: () => removeAt(index),
|
|
1389
|
+
title: "Remove attachment",
|
|
1390
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_lucide_react6.X, { className: "h-3 w-3 text-[var(--bfml-destructive)]" })
|
|
1391
|
+
}
|
|
1392
|
+
),
|
|
1393
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "absolute inset-x-0 bottom-0 bg-[var(--bfml-overlay)] px-1.5 py-1", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { className: "truncate text-[10px] font-medium text-[var(--bfml-primary-foreground)]", children: fileName }) })
|
|
1394
|
+
]
|
|
1395
|
+
},
|
|
1396
|
+
`${path}-${index}`
|
|
1397
|
+
);
|
|
1398
|
+
}) }) : null
|
|
1399
|
+
] }),
|
|
1400
|
+
currentValues.map((path, index) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("input", { type: "hidden", name: `${name}[${index}]`, value: path }, `${path}-${index}`)),
|
|
1401
|
+
/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
|
|
1402
|
+
MediaLibraryModal,
|
|
1403
|
+
{
|
|
1404
|
+
open,
|
|
1405
|
+
onClose: () => setOpen(false),
|
|
1406
|
+
onSelectMany: handleSelectMany,
|
|
1407
|
+
selectionMode: "multi",
|
|
1408
|
+
maxSelections: Math.max(0, max - currentValues.length),
|
|
1409
|
+
autoSelectUploads: true,
|
|
1410
|
+
config,
|
|
1411
|
+
theme: themeMode,
|
|
1412
|
+
title,
|
|
1413
|
+
description,
|
|
1414
|
+
accept
|
|
1415
|
+
}
|
|
1416
|
+
)
|
|
1417
|
+
] });
|
|
1418
|
+
}
|
|
1419
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1420
|
+
0 && (module.exports = {
|
|
1421
|
+
MAX_MEDIA_UPLOAD_BYTES,
|
|
1422
|
+
MediaLibraryModal,
|
|
1423
|
+
MediaLibraryPanel,
|
|
1424
|
+
MediaLibraryWidget,
|
|
1425
|
+
MediaPicker,
|
|
1426
|
+
MediaPickerMulti,
|
|
1427
|
+
MediaPreview,
|
|
1428
|
+
bfmlRootProps,
|
|
1429
|
+
createMediaLibraryClient,
|
|
1430
|
+
defaultMediaCapabilities,
|
|
1431
|
+
defaultMediaLibraryConfig,
|
|
1432
|
+
fileMatchesAccept,
|
|
1433
|
+
fileMatchesAcceptForUpload,
|
|
1434
|
+
fileNameFromPath,
|
|
1435
|
+
formatUploadSizeLimit,
|
|
1436
|
+
isFileWithinUploadSizeLimit,
|
|
1437
|
+
isImagePath,
|
|
1438
|
+
resolveThemeMode
|
|
1439
|
+
});
|
|
1440
|
+
//# sourceMappingURL=index.cjs.map
|