@plugable-io/react 0.0.6 → 0.0.8

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/dist/index.mjs CHANGED
@@ -1,6 +1,112 @@
1
1
  // src/PlugableProvider.tsx
2
- import { createContext, useContext, useMemo, useCallback, useRef, useState } from "react";
2
+ import { createContext, useContext, useMemo, useCallback, useRef, useState, useEffect } from "react";
3
3
  import { BucketClient } from "@plugable-io/js";
4
+
5
+ // src/utils/theme.ts
6
+ var defaultLightTheme = {
7
+ // Purple to blue accent gradient
8
+ accentPrimary: "#9333ea",
9
+ // purple-600
10
+ accentSecondary: "#2563eb",
11
+ // blue-600
12
+ accentHover: "#7c3aed",
13
+ // purple-700
14
+ // Light backgrounds
15
+ baseBg: "#ffffff",
16
+ baseSurface: "#f8fafc",
17
+ // slate-50
18
+ baseBorder: "#e2e8f0",
19
+ // slate-200
20
+ // Dark text on light
21
+ textPrimary: "#0f172a",
22
+ // slate-900
23
+ textSecondary: "#475569",
24
+ // slate-600
25
+ textMuted: "#94a3b8",
26
+ // slate-400
27
+ // State colors
28
+ success: "#10b981",
29
+ // green-500
30
+ error: "#ef4444",
31
+ // red-500
32
+ warning: "#f59e0b",
33
+ // amber-500
34
+ // Overlay
35
+ overlay: "rgba(0, 0, 0, 0.05)",
36
+ backdropBlur: "blur(12px)"
37
+ };
38
+ var defaultDarkTheme = {
39
+ // Purple to blue accent gradient (same as light)
40
+ accentPrimary: "#9333ea",
41
+ accentSecondary: "#2563eb",
42
+ accentHover: "#a855f7",
43
+ // purple-500 (lighter for dark mode)
44
+ // Dark backgrounds
45
+ baseBg: "#0f172a",
46
+ // slate-900
47
+ baseSurface: "rgba(30, 41, 59, 0.5)",
48
+ // slate-800/50 with transparency
49
+ baseBorder: "rgba(255, 255, 255, 0.1)",
50
+ // Light text on dark
51
+ textPrimary: "#f1f5f9",
52
+ // slate-100
53
+ textSecondary: "#cbd5e1",
54
+ // slate-300
55
+ textMuted: "#64748b",
56
+ // slate-500
57
+ // State colors (slightly adjusted for dark mode)
58
+ success: "#34d399",
59
+ // green-400
60
+ error: "#f87171",
61
+ // red-400
62
+ warning: "#fbbf24",
63
+ // amber-400
64
+ // Overlay
65
+ overlay: "rgba(0, 0, 0, 0.3)",
66
+ backdropBlur: "blur(24px)"
67
+ };
68
+ function getThemeColors(config = {}) {
69
+ const { theme = "dark", accentColor, baseColor } = config;
70
+ const isDark = theme === "dark" || theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
71
+ const colors = isDark ? { ...defaultDarkTheme } : { ...defaultLightTheme };
72
+ if (accentColor) {
73
+ colors.accentPrimary = accentColor;
74
+ colors.accentSecondary = accentColor;
75
+ colors.accentHover = accentColor;
76
+ }
77
+ if (baseColor) {
78
+ colors.baseBg = baseColor;
79
+ colors.baseSurface = baseColor;
80
+ }
81
+ return colors;
82
+ }
83
+ function generateCSSVariables(colors) {
84
+ return {
85
+ "--plugable-accent-primary": colors.accentPrimary,
86
+ "--plugable-accent-secondary": colors.accentSecondary,
87
+ "--plugable-accent-hover": colors.accentHover,
88
+ "--plugable-base-bg": colors.baseBg,
89
+ "--plugable-base-surface": colors.baseSurface,
90
+ "--plugable-base-border": colors.baseBorder,
91
+ "--plugable-text-primary": colors.textPrimary,
92
+ "--plugable-text-secondary": colors.textSecondary,
93
+ "--plugable-text-muted": colors.textMuted,
94
+ "--plugable-success": colors.success,
95
+ "--plugable-error": colors.error,
96
+ "--plugable-warning": colors.warning,
97
+ "--plugable-overlay": colors.overlay,
98
+ "--plugable-backdrop-blur": colors.backdropBlur
99
+ };
100
+ }
101
+ function applyCSSVariables(element, config = {}) {
102
+ const colors = getThemeColors(config);
103
+ const variables = generateCSSVariables(colors);
104
+ Object.entries(variables).forEach(([key, value]) => {
105
+ element.style.setProperty(key, value);
106
+ });
107
+ }
108
+
109
+ // src/PlugableProvider.tsx
4
110
  import { jsx } from "react/jsx-runtime";
5
111
  var PlugableContext = createContext(null);
6
112
  function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
@@ -45,6 +151,13 @@ function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
45
151
  "Firebase not found. Please ensure firebase is installed and initialized, or provide a custom getToken function."
46
152
  );
47
153
  };
154
+ case "generic_jwks":
155
+ case "generic_jwt":
156
+ return async () => {
157
+ throw new Error(
158
+ `Manual token required for ${authProvider}. Please provide a custom getToken function.`
159
+ );
160
+ };
48
161
  default:
49
162
  throw new Error(
50
163
  `Unknown auth provider: ${authProvider}. Please provide either a valid authProvider or a custom getToken function.`
@@ -58,11 +171,15 @@ function PlugableProvider({
58
171
  authProvider,
59
172
  clerkJWTTemplate,
60
173
  baseUrl,
61
- staleTime = 5 * 60 * 1e3
174
+ staleTime = 5 * 60 * 1e3,
62
175
  // Default 5 minutes
176
+ accentColor,
177
+ baseColor,
178
+ theme = "dark"
63
179
  }) {
64
180
  const listenersRef = useRef({});
65
181
  const [cache, setCacheState] = useState(/* @__PURE__ */ new Map());
182
+ const containerRef = useRef(null);
66
183
  const client = useMemo(() => {
67
184
  if (!getToken && !authProvider) {
68
185
  throw new Error(
@@ -132,7 +249,12 @@ function PlugableProvider({
132
249
  }),
133
250
  [client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
134
251
  );
135
- return /* @__PURE__ */ jsx(PlugableContext.Provider, { value, children });
252
+ useEffect(() => {
253
+ if (containerRef.current) {
254
+ applyCSSVariables(containerRef.current, { accentColor, baseColor, theme });
255
+ }
256
+ }, [accentColor, baseColor, theme]);
257
+ return /* @__PURE__ */ jsx(PlugableContext.Provider, { value, children: /* @__PURE__ */ jsx("div", { ref: containerRef, className: "plugable-root", children }) });
136
258
  }
137
259
  function usePlugable() {
138
260
  const context = useContext(PlugableContext);
@@ -277,6 +399,74 @@ function Dropzone({
277
399
  }
278
400
  );
279
401
  }
402
+ const containerStyle = {
403
+ position: "relative",
404
+ border: `2px dashed ${isDragActive ? "var(--plugable-accent-primary)" : "var(--plugable-base-border)"}`,
405
+ borderRadius: "12px",
406
+ padding: "48px 24px",
407
+ textAlign: "center",
408
+ cursor: "pointer",
409
+ background: isDragActive ? "var(--plugable-accent-primary)10" : "var(--plugable-base-surface)",
410
+ backdropFilter: "var(--plugable-backdrop-blur)",
411
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
412
+ ...style
413
+ };
414
+ const iconContainerStyle = {
415
+ width: "48px",
416
+ height: "48px",
417
+ margin: "0 auto 16px",
418
+ borderRadius: "50%",
419
+ display: "flex",
420
+ alignItems: "center",
421
+ justifyContent: "center",
422
+ background: isDragActive ? `linear-gradient(135deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))` : "var(--plugable-overlay)",
423
+ transition: "all 0.3s ease",
424
+ transform: isDragActive ? "scale(1.1)" : "scale(1)"
425
+ };
426
+ const cloudIconStyle = {
427
+ width: "24px",
428
+ height: "24px",
429
+ color: isDragActive ? "#fff" : "var(--plugable-accent-primary)"
430
+ };
431
+ const titleStyle = {
432
+ margin: 0,
433
+ fontWeight: 600,
434
+ fontSize: "16px",
435
+ color: "var(--plugable-text-primary)",
436
+ marginBottom: "8px"
437
+ };
438
+ const subtitleStyle = {
439
+ margin: 0,
440
+ fontSize: "14px",
441
+ color: "var(--plugable-text-secondary)"
442
+ };
443
+ const maxFilesStyle = {
444
+ margin: "4px 0 0 0",
445
+ fontSize: "12px",
446
+ color: "var(--plugable-text-muted)"
447
+ };
448
+ const progressContainerStyle = {
449
+ marginTop: "16px"
450
+ };
451
+ const progressItemStyle = {
452
+ marginBottom: "12px",
453
+ textAlign: "left"
454
+ };
455
+ const progressLabelStyle = {
456
+ fontSize: "14px",
457
+ color: "var(--plugable-text-secondary)",
458
+ marginBottom: "6px",
459
+ display: "flex",
460
+ justifyContent: "space-between",
461
+ alignItems: "center"
462
+ };
463
+ const progressBarBgStyle = {
464
+ width: "100%",
465
+ height: "6px",
466
+ backgroundColor: "var(--plugable-overlay)",
467
+ borderRadius: "3px",
468
+ overflow: "hidden"
469
+ };
280
470
  return /* @__PURE__ */ jsxs(
281
471
  "div",
282
472
  {
@@ -285,16 +475,7 @@ function Dropzone({
285
475
  onDragLeave: handleDragLeave,
286
476
  onClick: openFileDialog,
287
477
  className,
288
- style: {
289
- border: `2px dashed ${isDragActive ? "#0070f3" : "#ccc"}`,
290
- borderRadius: "8px",
291
- padding: "40px 20px",
292
- textAlign: "center",
293
- cursor: "pointer",
294
- backgroundColor: isDragActive ? "#f0f8ff" : "#fafafa",
295
- transition: "all 0.2s ease",
296
- ...style
297
- },
478
+ style: containerStyle,
298
479
  children: [
299
480
  /* @__PURE__ */ jsx2(
300
481
  "input",
@@ -308,42 +489,34 @@ function Dropzone({
308
489
  }
309
490
  ),
310
491
  isUploading ? /* @__PURE__ */ jsxs("div", { children: [
311
- /* @__PURE__ */ jsx2("p", { style: { margin: 0, fontWeight: "bold", color: "#333" }, children: "Uploading..." }),
312
- /* @__PURE__ */ jsx2("div", { style: { marginTop: "16px" }, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ jsxs("div", { style: { marginBottom: "8px" }, children: [
313
- /* @__PURE__ */ jsxs("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
314
- fileName,
315
- ": ",
316
- progress,
317
- "%"
492
+ /* @__PURE__ */ jsx2("div", { style: iconContainerStyle, children: /* @__PURE__ */ jsx2("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }) }),
493
+ /* @__PURE__ */ jsx2("p", { style: titleStyle, children: "Uploading..." }),
494
+ /* @__PURE__ */ jsx2("div", { style: progressContainerStyle, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ jsxs("div", { style: progressItemStyle, children: [
495
+ /* @__PURE__ */ jsxs("div", { style: progressLabelStyle, children: [
496
+ /* @__PURE__ */ jsx2("span", { children: fileName }),
497
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600 }, children: [
498
+ progress,
499
+ "%"
500
+ ] })
318
501
  ] }),
319
- /* @__PURE__ */ jsx2(
502
+ /* @__PURE__ */ jsx2("div", { style: progressBarBgStyle, children: /* @__PURE__ */ jsx2(
320
503
  "div",
321
504
  {
322
505
  style: {
323
- width: "100%",
324
- height: "8px",
325
- backgroundColor: "#e0e0e0",
326
- borderRadius: "4px",
327
- overflow: "hidden"
328
- },
329
- children: /* @__PURE__ */ jsx2(
330
- "div",
331
- {
332
- style: {
333
- width: `${progress}%`,
334
- height: "100%",
335
- backgroundColor: "#0070f3",
336
- transition: "width 0.3s ease"
337
- }
338
- }
339
- )
506
+ width: `${progress}%`,
507
+ height: "100%",
508
+ background: `linear-gradient(90deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))`,
509
+ transition: "width 0.3s ease",
510
+ borderRadius: "3px"
511
+ }
340
512
  }
341
- )
513
+ ) })
342
514
  ] }, fileName)) })
343
515
  ] }) : /* @__PURE__ */ jsxs("div", { children: [
344
- /* @__PURE__ */ jsx2("p", { style: { margin: 0, fontWeight: "bold", fontSize: "16px", color: "#333" }, children: isDragActive ? "Drop files here" : "Drag and drop files here" }),
345
- /* @__PURE__ */ jsx2("p", { style: { margin: "8px 0 0 0", fontSize: "14px", color: "#666" }, children: "or click to select files" }),
346
- maxFiles && maxFiles > 1 && /* @__PURE__ */ jsxs("p", { style: { margin: "4px 0 0 0", fontSize: "12px", color: "#999" }, children: [
516
+ /* @__PURE__ */ jsx2("div", { style: iconContainerStyle, children: /* @__PURE__ */ jsx2("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx2("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 16a4 4 0 01-.88-7.903A5 5 0 1115.9 6L16 6a5 5 0 011 9.9M15 13l-3-3m0 0l-3 3m3-3v12" }) }) }),
517
+ /* @__PURE__ */ jsx2("p", { style: titleStyle, children: isDragActive ? "Drop files here" : "Click to upload or drag and drop" }),
518
+ /* @__PURE__ */ jsx2("p", { style: subtitleStyle, children: accept ? `Accepted: ${accept}` : "Any file type" }),
519
+ maxFiles && maxFiles > 1 && /* @__PURE__ */ jsxs("p", { style: maxFilesStyle, children: [
347
520
  "(Maximum ",
348
521
  maxFiles,
349
522
  " files)"
@@ -355,7 +528,7 @@ function Dropzone({
355
528
  }
356
529
 
357
530
  // src/hooks/useFiles.ts
358
- import { useState as useState3, useCallback as useCallback3, useEffect, useMemo as useMemo2, useRef as useRef2 } from "react";
531
+ import { useState as useState3, useCallback as useCallback3, useEffect as useEffect2, useMemo as useMemo2, useRef as useRef2 } from "react";
359
532
  function useFiles({
360
533
  metadata,
361
534
  startPage = 1,
@@ -440,7 +613,7 @@ function useFiles({
440
613
  setIsLoading(false);
441
614
  }
442
615
  }, [client, stableMetadata, mediaType, perPage, orderBy, orderDirection, getCache, setCache, effectiveStaleTime]);
443
- useEffect(() => {
616
+ useEffect2(() => {
444
617
  const paramsChanged = previousParamsRef.current !== null && previousParamsRef.current !== paramsKeyWithPage;
445
618
  const isInitialMount = isInitialMountRef.current;
446
619
  previousParamsRef.current = paramsKeyWithPage;
@@ -461,7 +634,7 @@ function useFiles({
461
634
  fetchFiles(page, true);
462
635
  }
463
636
  }, [paramsKeyWithPage, autoLoad, getCache, effectiveStaleTime, fetchFiles, page]);
464
- useEffect(() => {
637
+ useEffect2(() => {
465
638
  const unsubscribe = on("file.uploaded", () => {
466
639
  fetchFiles(page, true);
467
640
  });
@@ -523,7 +696,7 @@ function FileList({
523
696
  }
524
697
 
525
698
  // src/components/FileImage.tsx
526
- import { useEffect as useEffect2, useState as useState4, useRef as useRef3 } from "react";
699
+ import { useEffect as useEffect3, useState as useState4, useRef as useRef3 } from "react";
527
700
  import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
528
701
  var imageCache = /* @__PURE__ */ new Map();
529
702
  function FileImage({
@@ -543,7 +716,7 @@ function FileImage({
543
716
  const [isLoading, setIsLoading] = useState4(true);
544
717
  const [error, setError] = useState4(null);
545
718
  const refetchAttemptedRef = useRef3(null);
546
- useEffect2(() => {
719
+ useEffect3(() => {
547
720
  let isMounted = true;
548
721
  let objectUrl = null;
549
722
  const loadImage = async () => {
@@ -558,29 +731,47 @@ function FileImage({
558
731
  return;
559
732
  }
560
733
  if (file.download_url) {
561
- const response = await fetch(file.download_url, {
562
- headers: {
563
- "Cache-Control": "private, max-age=31536000",
564
- "If-None-Match": file.checksum
734
+ let response = null;
735
+ try {
736
+ response = await fetch(file.download_url, {
737
+ headers: {
738
+ "Cache-Control": "private, max-age=31536000",
739
+ "If-None-Match": file.checksum
740
+ }
741
+ });
742
+ if (!response.ok) {
743
+ const downloadUrlKey = `${file.id}-${file.download_url}`;
744
+ if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
745
+ refetchAttemptedRef.current = downloadUrlKey;
746
+ imageCache.delete(cacheKey);
747
+ await onRefetchNeeded();
748
+ return;
749
+ }
750
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
565
751
  }
566
- });
567
- if (!response.ok) {
752
+ const blob = await response.blob();
753
+ objectUrl = URL.createObjectURL(blob);
754
+ imageCache.set(cacheKey, objectUrl);
755
+ if (isMounted) {
756
+ setImageSrc(objectUrl);
757
+ setIsLoading(false);
758
+ refetchAttemptedRef.current = null;
759
+ }
760
+ } catch (fetchError) {
568
761
  const downloadUrlKey = `${file.id}-${file.download_url}`;
569
- if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
762
+ const isNetworkError = !response && (fetchError.message?.includes("Failed to fetch") || fetchError.message?.includes("CORS") || fetchError.name === "TypeError" || fetchError.message?.includes("network") || fetchError.message?.includes("ERR_FAILED"));
763
+ const is403Error = response?.status === 403;
764
+ if ((isNetworkError || is403Error) && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
570
765
  refetchAttemptedRef.current = downloadUrlKey;
571
766
  imageCache.delete(cacheKey);
572
- await onRefetchNeeded();
573
- return;
767
+ try {
768
+ await onRefetchNeeded();
769
+ return;
770
+ } catch (refetchError) {
771
+ console.error("Failed to refetch file:", refetchError);
772
+ }
574
773
  }
575
- throw new Error(`Failed to fetch image: ${response.statusText}`);
576
- }
577
- const blob = await response.blob();
578
- objectUrl = URL.createObjectURL(blob);
579
- imageCache.set(cacheKey, objectUrl);
580
- if (isMounted) {
581
- setImageSrc(objectUrl);
582
- setIsLoading(false);
583
- refetchAttemptedRef.current = null;
774
+ throw fetchError;
584
775
  }
585
776
  } else {
586
777
  throw new Error("No download URL available for file");
@@ -618,20 +809,25 @@ function FileImage({
618
809
  ...style
619
810
  };
620
811
  if (error) {
621
- return /* @__PURE__ */ jsx4(
812
+ return /* @__PURE__ */ jsxs2(
622
813
  "div",
623
814
  {
624
815
  className,
625
816
  style: {
626
817
  ...imageStyle,
627
818
  display: "flex",
819
+ flexDirection: "column",
628
820
  alignItems: "center",
629
821
  justifyContent: "center",
630
- backgroundColor: "#f0f0f0",
631
- color: "#999",
632
- fontSize: "14px"
822
+ backgroundColor: "var(--plugable-base-surface)",
823
+ color: "var(--plugable-text-muted)",
824
+ fontSize: "14px",
825
+ gap: "8px"
633
826
  },
634
- children: "Failed to load image"
827
+ children: [
828
+ /* @__PURE__ */ jsx4("svg", { style: { width: "32px", height: "32px" }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx4("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" }) }),
829
+ /* @__PURE__ */ jsx4("span", { children: "Failed to load" })
830
+ ]
635
831
  }
636
832
  );
637
833
  }
@@ -642,29 +838,29 @@ function FileImage({
642
838
  className,
643
839
  style: {
644
840
  ...imageStyle,
645
- display: "flex",
646
- alignItems: "center",
647
- justifyContent: "center",
648
- backgroundColor: "#f0f0f0"
841
+ position: "relative",
842
+ overflow: "hidden",
843
+ backgroundColor: "var(--plugable-base-surface)"
649
844
  },
650
845
  children: [
651
846
  /* @__PURE__ */ jsx4(
652
847
  "div",
653
848
  {
654
849
  style: {
655
- width: "40px",
656
- height: "40px",
657
- border: "3px solid #e0e0e0",
658
- borderTop: "3px solid #0070f3",
659
- borderRadius: "50%",
660
- animation: "spin 1s linear infinite"
850
+ position: "absolute",
851
+ top: 0,
852
+ left: "-100%",
853
+ width: "100%",
854
+ height: "100%",
855
+ background: "linear-gradient(90deg, transparent, var(--plugable-overlay), transparent)",
856
+ animation: "shimmer 1.5s infinite"
661
857
  }
662
858
  }
663
859
  ),
664
860
  /* @__PURE__ */ jsx4("style", { children: `
665
- @keyframes spin {
666
- 0% { transform: rotate(0deg); }
667
- 100% { transform: rotate(360deg); }
861
+ @keyframes shimmer {
862
+ 0% { left: -100%; }
863
+ 100% { left: 100%; }
668
864
  }
669
865
  ` })
670
866
  ]
@@ -677,7 +873,11 @@ function FileImage({
677
873
  src: imageSrc,
678
874
  alt: alt || file.name,
679
875
  className,
680
- style: imageStyle,
876
+ style: {
877
+ ...imageStyle,
878
+ opacity: isLoading ? 0 : 1,
879
+ transition: "opacity 0.3s ease-in"
880
+ },
681
881
  onLoad: handleLoad,
682
882
  onError: handleError
683
883
  }
@@ -689,12 +889,35 @@ function clearImageCache() {
689
889
  }
690
890
 
691
891
  // src/components/FilePreview.tsx
692
- import { useState as useState5, useCallback as useCallback4, useEffect as useEffect3 } from "react";
693
- import { jsx as jsx5 } from "react/jsx-runtime";
892
+ import { useState as useState5, useCallback as useCallback4, useEffect as useEffect4 } from "react";
893
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
894
+ var fileTypeColors = {
895
+ pdf: { bg: "transparent", text: "#94a3b8" },
896
+ // Slate-400
897
+ doc: { bg: "transparent", text: "#94a3b8" },
898
+ docx: { bg: "transparent", text: "#94a3b8" },
899
+ xls: { bg: "transparent", text: "#94a3b8" },
900
+ xlsx: { bg: "transparent", text: "#94a3b8" },
901
+ csv: { bg: "transparent", text: "#94a3b8" },
902
+ txt: { bg: "transparent", text: "#94a3b8" },
903
+ zip: { bg: "transparent", text: "#94a3b8" },
904
+ rar: { bg: "transparent", text: "#94a3b8" },
905
+ mp4: { bg: "transparent", text: "#94a3b8" },
906
+ mov: { bg: "transparent", text: "#94a3b8" },
907
+ avi: { bg: "transparent", text: "#94a3b8" },
908
+ mp3: { bg: "transparent", text: "#94a3b8" },
909
+ wav: { bg: "transparent", text: "#94a3b8" },
910
+ default: { bg: "transparent", text: "#64748b" }
911
+ // Slate-500
912
+ };
913
+ function getFileTypeColor(filename) {
914
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
915
+ return fileTypeColors[ext] || fileTypeColors.default;
916
+ }
694
917
  function FilePreview({
695
918
  file: initialFile,
696
- width = 80,
697
- height = 80,
919
+ width = 120,
920
+ height = 120,
698
921
  className,
699
922
  style,
700
923
  objectFit = "cover",
@@ -704,7 +927,8 @@ function FilePreview({
704
927
  const { client } = usePlugable();
705
928
  const [file, setFile] = useState5(initialFile);
706
929
  const [isRefetching, setIsRefetching] = useState5(false);
707
- useEffect3(() => {
930
+ const [isHovered, setIsHovered] = useState5(false);
931
+ useEffect4(() => {
708
932
  setFile(initialFile);
709
933
  }, [initialFile.id, initialFile.download_url]);
710
934
  const handleRefetch = useCallback4(async () => {
@@ -718,45 +942,105 @@ function FilePreview({
718
942
  } finally {
719
943
  setIsRefetching(false);
720
944
  }
721
- }, [file.id, client]);
945
+ }, [file.id, client, isRefetching]);
722
946
  const isImage = file.content_type.startsWith("image/");
723
947
  const containerStyle = {
724
948
  width,
725
949
  height,
726
- borderRadius: 4,
950
+ borderRadius: "8px",
727
951
  overflow: "hidden",
728
- backgroundColor: "#f5f5f5",
952
+ position: "relative",
729
953
  display: "flex",
730
954
  alignItems: "center",
731
955
  justifyContent: "center",
732
- border: "1px solid #eee",
956
+ border: isHovered ? "1px solid var(--plugable-accent-primary)" : "1px solid var(--plugable-base-border)",
957
+ background: "var(--plugable-base-surface)",
958
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
959
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
960
+ cursor: "pointer",
733
961
  ...style
734
962
  };
735
963
  if (isImage) {
736
964
  return /* @__PURE__ */ jsx5(
737
- FileImage,
965
+ "div",
738
966
  {
739
- file,
740
- width,
741
- height,
742
- objectFit,
967
+ style: containerStyle,
743
968
  className,
744
- style,
745
- borderRadius: 4,
746
- onRefetchNeeded: handleRefetch
969
+ onMouseEnter: () => setIsHovered(true),
970
+ onMouseLeave: () => setIsHovered(false),
971
+ children: /* @__PURE__ */ jsx5(
972
+ FileImage,
973
+ {
974
+ file,
975
+ width,
976
+ height,
977
+ objectFit,
978
+ style: { borderRadius: "8px" },
979
+ onRefetchNeeded: handleRefetch
980
+ }
981
+ )
747
982
  }
748
983
  );
749
984
  }
750
985
  if (renderNonImage) {
751
- return /* @__PURE__ */ jsx5("div", { className, style: containerStyle, children: renderNonImage(file) });
986
+ return /* @__PURE__ */ jsx5(
987
+ "div",
988
+ {
989
+ className,
990
+ style: containerStyle,
991
+ onMouseEnter: () => setIsHovered(true),
992
+ onMouseLeave: () => setIsHovered(false),
993
+ children: renderNonImage(file)
994
+ }
995
+ );
752
996
  }
753
997
  const extension = file.name.split(".").pop()?.toUpperCase() || "FILE";
754
- return /* @__PURE__ */ jsx5("div", { className, style: containerStyle, children: showExtension && /* @__PURE__ */ jsx5("span", { style: {
755
- fontSize: "12px",
756
- fontWeight: "bold",
757
- color: "#666",
758
- textTransform: "uppercase"
759
- }, children: extension }) });
998
+ const colors = getFileTypeColor(file.name);
999
+ const badgeStyle = {
1000
+ padding: "2px 8px",
1001
+ borderRadius: "4px",
1002
+ border: "1px solid var(--plugable-base-border)",
1003
+ backgroundColor: "var(--plugable-overlay)",
1004
+ display: "inline-flex",
1005
+ alignItems: "center",
1006
+ justifyContent: "center"
1007
+ };
1008
+ const extensionStyle = {
1009
+ fontSize: "11px",
1010
+ fontWeight: 600,
1011
+ color: "var(--plugable-text-secondary)",
1012
+ textTransform: "uppercase",
1013
+ letterSpacing: "0.05em"
1014
+ };
1015
+ const iconStyle = {
1016
+ width: "28px",
1017
+ height: "28px",
1018
+ marginBottom: "6px",
1019
+ color: colors.text
1020
+ };
1021
+ const wrapperStyle = {
1022
+ textAlign: "center",
1023
+ padding: "12px",
1024
+ display: "flex",
1025
+ flexDirection: "column",
1026
+ alignItems: "center",
1027
+ justifyContent: "center",
1028
+ width: "100%",
1029
+ height: "100%"
1030
+ };
1031
+ return /* @__PURE__ */ jsx5(
1032
+ "div",
1033
+ {
1034
+ className,
1035
+ style: containerStyle,
1036
+ onMouseEnter: () => setIsHovered(true),
1037
+ onMouseLeave: () => setIsHovered(false),
1038
+ children: /* @__PURE__ */ jsxs3("div", { style: wrapperStyle, children: [
1039
+ /* @__PURE__ */ jsx5("svg", { style: iconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx5("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z" }) }),
1040
+ showExtension && /* @__PURE__ */ jsx5("div", { style: badgeStyle, children: /* @__PURE__ */ jsx5("span", { style: extensionStyle, children: extension }) })
1041
+ ] })
1042
+ }
1043
+ );
760
1044
  }
761
1045
  export {
762
1046
  Dropzone,
@@ -764,7 +1048,10 @@ export {
764
1048
  FileList,
765
1049
  FilePreview,
766
1050
  PlugableProvider,
1051
+ applyCSSVariables,
767
1052
  clearImageCache,
1053
+ generateCSSVariables,
1054
+ getThemeColors,
768
1055
  useFiles,
769
1056
  usePlugable
770
1057
  };