@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.js CHANGED
@@ -35,7 +35,10 @@ __export(index_exports, {
35
35
  FileList: () => FileList,
36
36
  FilePreview: () => FilePreview,
37
37
  PlugableProvider: () => PlugableProvider,
38
+ applyCSSVariables: () => applyCSSVariables,
38
39
  clearImageCache: () => clearImageCache,
40
+ generateCSSVariables: () => generateCSSVariables,
41
+ getThemeColors: () => getThemeColors,
39
42
  useFiles: () => useFiles,
40
43
  usePlugable: () => usePlugable
41
44
  });
@@ -44,6 +47,112 @@ module.exports = __toCommonJS(index_exports);
44
47
  // src/PlugableProvider.tsx
45
48
  var import_react = require("react");
46
49
  var import_js = require("@plugable-io/js");
50
+
51
+ // src/utils/theme.ts
52
+ var defaultLightTheme = {
53
+ // Purple to blue accent gradient
54
+ accentPrimary: "#9333ea",
55
+ // purple-600
56
+ accentSecondary: "#2563eb",
57
+ // blue-600
58
+ accentHover: "#7c3aed",
59
+ // purple-700
60
+ // Light backgrounds
61
+ baseBg: "#ffffff",
62
+ baseSurface: "#f8fafc",
63
+ // slate-50
64
+ baseBorder: "#e2e8f0",
65
+ // slate-200
66
+ // Dark text on light
67
+ textPrimary: "#0f172a",
68
+ // slate-900
69
+ textSecondary: "#475569",
70
+ // slate-600
71
+ textMuted: "#94a3b8",
72
+ // slate-400
73
+ // State colors
74
+ success: "#10b981",
75
+ // green-500
76
+ error: "#ef4444",
77
+ // red-500
78
+ warning: "#f59e0b",
79
+ // amber-500
80
+ // Overlay
81
+ overlay: "rgba(0, 0, 0, 0.05)",
82
+ backdropBlur: "blur(12px)"
83
+ };
84
+ var defaultDarkTheme = {
85
+ // Purple to blue accent gradient (same as light)
86
+ accentPrimary: "#9333ea",
87
+ accentSecondary: "#2563eb",
88
+ accentHover: "#a855f7",
89
+ // purple-500 (lighter for dark mode)
90
+ // Dark backgrounds
91
+ baseBg: "#0f172a",
92
+ // slate-900
93
+ baseSurface: "rgba(30, 41, 59, 0.5)",
94
+ // slate-800/50 with transparency
95
+ baseBorder: "rgba(255, 255, 255, 0.1)",
96
+ // Light text on dark
97
+ textPrimary: "#f1f5f9",
98
+ // slate-100
99
+ textSecondary: "#cbd5e1",
100
+ // slate-300
101
+ textMuted: "#64748b",
102
+ // slate-500
103
+ // State colors (slightly adjusted for dark mode)
104
+ success: "#34d399",
105
+ // green-400
106
+ error: "#f87171",
107
+ // red-400
108
+ warning: "#fbbf24",
109
+ // amber-400
110
+ // Overlay
111
+ overlay: "rgba(0, 0, 0, 0.3)",
112
+ backdropBlur: "blur(24px)"
113
+ };
114
+ function getThemeColors(config = {}) {
115
+ const { theme = "dark", accentColor, baseColor } = config;
116
+ const isDark = theme === "dark" || theme === "auto" && window.matchMedia("(prefers-color-scheme: dark)").matches;
117
+ const colors = isDark ? { ...defaultDarkTheme } : { ...defaultLightTheme };
118
+ if (accentColor) {
119
+ colors.accentPrimary = accentColor;
120
+ colors.accentSecondary = accentColor;
121
+ colors.accentHover = accentColor;
122
+ }
123
+ if (baseColor) {
124
+ colors.baseBg = baseColor;
125
+ colors.baseSurface = baseColor;
126
+ }
127
+ return colors;
128
+ }
129
+ function generateCSSVariables(colors) {
130
+ return {
131
+ "--plugable-accent-primary": colors.accentPrimary,
132
+ "--plugable-accent-secondary": colors.accentSecondary,
133
+ "--plugable-accent-hover": colors.accentHover,
134
+ "--plugable-base-bg": colors.baseBg,
135
+ "--plugable-base-surface": colors.baseSurface,
136
+ "--plugable-base-border": colors.baseBorder,
137
+ "--plugable-text-primary": colors.textPrimary,
138
+ "--plugable-text-secondary": colors.textSecondary,
139
+ "--plugable-text-muted": colors.textMuted,
140
+ "--plugable-success": colors.success,
141
+ "--plugable-error": colors.error,
142
+ "--plugable-warning": colors.warning,
143
+ "--plugable-overlay": colors.overlay,
144
+ "--plugable-backdrop-blur": colors.backdropBlur
145
+ };
146
+ }
147
+ function applyCSSVariables(element, config = {}) {
148
+ const colors = getThemeColors(config);
149
+ const variables = generateCSSVariables(colors);
150
+ Object.entries(variables).forEach(([key, value]) => {
151
+ element.style.setProperty(key, value);
152
+ });
153
+ }
154
+
155
+ // src/PlugableProvider.tsx
47
156
  var import_jsx_runtime = require("react/jsx-runtime");
48
157
  var PlugableContext = (0, import_react.createContext)(null);
49
158
  function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
@@ -88,6 +197,13 @@ function createAuthTokenGetter(authProvider, clerkJWTTemplate) {
88
197
  "Firebase not found. Please ensure firebase is installed and initialized, or provide a custom getToken function."
89
198
  );
90
199
  };
200
+ case "generic_jwks":
201
+ case "generic_jwt":
202
+ return async () => {
203
+ throw new Error(
204
+ `Manual token required for ${authProvider}. Please provide a custom getToken function.`
205
+ );
206
+ };
91
207
  default:
92
208
  throw new Error(
93
209
  `Unknown auth provider: ${authProvider}. Please provide either a valid authProvider or a custom getToken function.`
@@ -101,11 +217,15 @@ function PlugableProvider({
101
217
  authProvider,
102
218
  clerkJWTTemplate,
103
219
  baseUrl,
104
- staleTime = 5 * 60 * 1e3
220
+ staleTime = 5 * 60 * 1e3,
105
221
  // Default 5 minutes
222
+ accentColor,
223
+ baseColor,
224
+ theme = "dark"
106
225
  }) {
107
226
  const listenersRef = (0, import_react.useRef)({});
108
227
  const [cache, setCacheState] = (0, import_react.useState)(/* @__PURE__ */ new Map());
228
+ const containerRef = (0, import_react.useRef)(null);
109
229
  const client = (0, import_react.useMemo)(() => {
110
230
  if (!getToken && !authProvider) {
111
231
  throw new Error(
@@ -175,7 +295,12 @@ function PlugableProvider({
175
295
  }),
176
296
  [client, bucketId, on, emit, baseUrl, staleTime, getCache, setCache, invalidateCache]
177
297
  );
178
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlugableContext.Provider, { value, children });
298
+ (0, import_react.useEffect)(() => {
299
+ if (containerRef.current) {
300
+ applyCSSVariables(containerRef.current, { accentColor, baseColor, theme });
301
+ }
302
+ }, [accentColor, baseColor, theme]);
303
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlugableContext.Provider, { value, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: containerRef, className: "plugable-root", children }) });
179
304
  }
180
305
  function usePlugable() {
181
306
  const context = (0, import_react.useContext)(PlugableContext);
@@ -320,6 +445,74 @@ function Dropzone({
320
445
  }
321
446
  );
322
447
  }
448
+ const containerStyle = {
449
+ position: "relative",
450
+ border: `2px dashed ${isDragActive ? "var(--plugable-accent-primary)" : "var(--plugable-base-border)"}`,
451
+ borderRadius: "12px",
452
+ padding: "48px 24px",
453
+ textAlign: "center",
454
+ cursor: "pointer",
455
+ background: isDragActive ? "var(--plugable-accent-primary)10" : "var(--plugable-base-surface)",
456
+ backdropFilter: "var(--plugable-backdrop-blur)",
457
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
458
+ ...style
459
+ };
460
+ const iconContainerStyle = {
461
+ width: "48px",
462
+ height: "48px",
463
+ margin: "0 auto 16px",
464
+ borderRadius: "50%",
465
+ display: "flex",
466
+ alignItems: "center",
467
+ justifyContent: "center",
468
+ background: isDragActive ? `linear-gradient(135deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))` : "var(--plugable-overlay)",
469
+ transition: "all 0.3s ease",
470
+ transform: isDragActive ? "scale(1.1)" : "scale(1)"
471
+ };
472
+ const cloudIconStyle = {
473
+ width: "24px",
474
+ height: "24px",
475
+ color: isDragActive ? "#fff" : "var(--plugable-accent-primary)"
476
+ };
477
+ const titleStyle = {
478
+ margin: 0,
479
+ fontWeight: 600,
480
+ fontSize: "16px",
481
+ color: "var(--plugable-text-primary)",
482
+ marginBottom: "8px"
483
+ };
484
+ const subtitleStyle = {
485
+ margin: 0,
486
+ fontSize: "14px",
487
+ color: "var(--plugable-text-secondary)"
488
+ };
489
+ const maxFilesStyle = {
490
+ margin: "4px 0 0 0",
491
+ fontSize: "12px",
492
+ color: "var(--plugable-text-muted)"
493
+ };
494
+ const progressContainerStyle = {
495
+ marginTop: "16px"
496
+ };
497
+ const progressItemStyle = {
498
+ marginBottom: "12px",
499
+ textAlign: "left"
500
+ };
501
+ const progressLabelStyle = {
502
+ fontSize: "14px",
503
+ color: "var(--plugable-text-secondary)",
504
+ marginBottom: "6px",
505
+ display: "flex",
506
+ justifyContent: "space-between",
507
+ alignItems: "center"
508
+ };
509
+ const progressBarBgStyle = {
510
+ width: "100%",
511
+ height: "6px",
512
+ backgroundColor: "var(--plugable-overlay)",
513
+ borderRadius: "3px",
514
+ overflow: "hidden"
515
+ };
323
516
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
324
517
  "div",
325
518
  {
@@ -328,16 +521,7 @@ function Dropzone({
328
521
  onDragLeave: handleDragLeave,
329
522
  onClick: openFileDialog,
330
523
  className,
331
- style: {
332
- border: `2px dashed ${isDragActive ? "#0070f3" : "#ccc"}`,
333
- borderRadius: "8px",
334
- padding: "40px 20px",
335
- textAlign: "center",
336
- cursor: "pointer",
337
- backgroundColor: isDragActive ? "#f0f8ff" : "#fafafa",
338
- transition: "all 0.2s ease",
339
- ...style
340
- },
524
+ style: containerStyle,
341
525
  children: [
342
526
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
343
527
  "input",
@@ -351,42 +535,34 @@ function Dropzone({
351
535
  }
352
536
  ),
353
537
  isUploading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
354
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: 0, fontWeight: "bold", color: "#333" }, children: "Uploading..." }),
355
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: { marginTop: "16px" }, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { marginBottom: "8px" }, children: [
356
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: { fontSize: "14px", color: "#666", marginBottom: "4px" }, children: [
357
- fileName,
358
- ": ",
359
- progress,
360
- "%"
538
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: iconContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) }) }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: titleStyle, children: "Uploading..." }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: progressContainerStyle, children: Object.entries(uploadProgress).map(([fileName, progress]) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: progressItemStyle, children: [
541
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { style: progressLabelStyle, children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: fileName }),
543
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { style: { fontWeight: 600 }, children: [
544
+ progress,
545
+ "%"
546
+ ] })
361
547
  ] }),
362
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
548
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: progressBarBgStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
363
549
  "div",
364
550
  {
365
551
  style: {
366
- width: "100%",
367
- height: "8px",
368
- backgroundColor: "#e0e0e0",
369
- borderRadius: "4px",
370
- overflow: "hidden"
371
- },
372
- children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
373
- "div",
374
- {
375
- style: {
376
- width: `${progress}%`,
377
- height: "100%",
378
- backgroundColor: "#0070f3",
379
- transition: "width 0.3s ease"
380
- }
381
- }
382
- )
552
+ width: `${progress}%`,
553
+ height: "100%",
554
+ background: `linear-gradient(90deg, var(--plugable-accent-primary), var(--plugable-accent-secondary))`,
555
+ transition: "width 0.3s ease",
556
+ borderRadius: "3px"
557
+ }
383
558
  }
384
- )
559
+ ) })
385
560
  ] }, fileName)) })
386
561
  ] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
387
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: 0, fontWeight: "bold", fontSize: "16px", color: "#333" }, children: isDragActive ? "Drop files here" : "Drag and drop files here" }),
388
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: { margin: "8px 0 0 0", fontSize: "14px", color: "#666" }, children: "or click to select files" }),
389
- maxFiles && maxFiles > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: { margin: "4px 0 0 0", fontSize: "12px", color: "#999" }, children: [
562
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { style: iconContainerStyle, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { style: cloudIconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("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" }) }) }),
563
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: titleStyle, children: isDragActive ? "Drop files here" : "Click to upload or drag and drop" }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { style: subtitleStyle, children: accept ? `Accepted: ${accept}` : "Any file type" }),
565
+ maxFiles && maxFiles > 1 && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { style: maxFilesStyle, children: [
390
566
  "(Maximum ",
391
567
  maxFiles,
392
568
  " files)"
@@ -601,29 +777,47 @@ function FileImage({
601
777
  return;
602
778
  }
603
779
  if (file.download_url) {
604
- const response = await fetch(file.download_url, {
605
- headers: {
606
- "Cache-Control": "private, max-age=31536000",
607
- "If-None-Match": file.checksum
780
+ let response = null;
781
+ try {
782
+ response = await fetch(file.download_url, {
783
+ headers: {
784
+ "Cache-Control": "private, max-age=31536000",
785
+ "If-None-Match": file.checksum
786
+ }
787
+ });
788
+ if (!response.ok) {
789
+ const downloadUrlKey = `${file.id}-${file.download_url}`;
790
+ if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
791
+ refetchAttemptedRef.current = downloadUrlKey;
792
+ imageCache.delete(cacheKey);
793
+ await onRefetchNeeded();
794
+ return;
795
+ }
796
+ throw new Error(`Failed to fetch image: ${response.statusText}`);
608
797
  }
609
- });
610
- if (!response.ok) {
798
+ const blob = await response.blob();
799
+ objectUrl = URL.createObjectURL(blob);
800
+ imageCache.set(cacheKey, objectUrl);
801
+ if (isMounted) {
802
+ setImageSrc(objectUrl);
803
+ setIsLoading(false);
804
+ refetchAttemptedRef.current = null;
805
+ }
806
+ } catch (fetchError) {
611
807
  const downloadUrlKey = `${file.id}-${file.download_url}`;
612
- if (response.status === 403 && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
808
+ 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"));
809
+ const is403Error = response?.status === 403;
810
+ if ((isNetworkError || is403Error) && onRefetchNeeded && refetchAttemptedRef.current !== downloadUrlKey) {
613
811
  refetchAttemptedRef.current = downloadUrlKey;
614
812
  imageCache.delete(cacheKey);
615
- await onRefetchNeeded();
616
- return;
813
+ try {
814
+ await onRefetchNeeded();
815
+ return;
816
+ } catch (refetchError) {
817
+ console.error("Failed to refetch file:", refetchError);
818
+ }
617
819
  }
618
- throw new Error(`Failed to fetch image: ${response.statusText}`);
619
- }
620
- const blob = await response.blob();
621
- objectUrl = URL.createObjectURL(blob);
622
- imageCache.set(cacheKey, objectUrl);
623
- if (isMounted) {
624
- setImageSrc(objectUrl);
625
- setIsLoading(false);
626
- refetchAttemptedRef.current = null;
820
+ throw fetchError;
627
821
  }
628
822
  } else {
629
823
  throw new Error("No download URL available for file");
@@ -661,20 +855,25 @@ function FileImage({
661
855
  ...style
662
856
  };
663
857
  if (error) {
664
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
858
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
665
859
  "div",
666
860
  {
667
861
  className,
668
862
  style: {
669
863
  ...imageStyle,
670
864
  display: "flex",
865
+ flexDirection: "column",
671
866
  alignItems: "center",
672
867
  justifyContent: "center",
673
- backgroundColor: "#f0f0f0",
674
- color: "#999",
675
- fontSize: "14px"
868
+ backgroundColor: "var(--plugable-base-surface)",
869
+ color: "var(--plugable-text-muted)",
870
+ fontSize: "14px",
871
+ gap: "8px"
676
872
  },
677
- children: "Failed to load image"
873
+ children: [
874
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("svg", { style: { width: "32px", height: "32px" }, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("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" }) }),
875
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { children: "Failed to load" })
876
+ ]
678
877
  }
679
878
  );
680
879
  }
@@ -685,29 +884,29 @@ function FileImage({
685
884
  className,
686
885
  style: {
687
886
  ...imageStyle,
688
- display: "flex",
689
- alignItems: "center",
690
- justifyContent: "center",
691
- backgroundColor: "#f0f0f0"
887
+ position: "relative",
888
+ overflow: "hidden",
889
+ backgroundColor: "var(--plugable-base-surface)"
692
890
  },
693
891
  children: [
694
892
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
695
893
  "div",
696
894
  {
697
895
  style: {
698
- width: "40px",
699
- height: "40px",
700
- border: "3px solid #e0e0e0",
701
- borderTop: "3px solid #0070f3",
702
- borderRadius: "50%",
703
- animation: "spin 1s linear infinite"
896
+ position: "absolute",
897
+ top: 0,
898
+ left: "-100%",
899
+ width: "100%",
900
+ height: "100%",
901
+ background: "linear-gradient(90deg, transparent, var(--plugable-overlay), transparent)",
902
+ animation: "shimmer 1.5s infinite"
704
903
  }
705
904
  }
706
905
  ),
707
906
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: `
708
- @keyframes spin {
709
- 0% { transform: rotate(0deg); }
710
- 100% { transform: rotate(360deg); }
907
+ @keyframes shimmer {
908
+ 0% { left: -100%; }
909
+ 100% { left: 100%; }
711
910
  }
712
911
  ` })
713
912
  ]
@@ -720,7 +919,11 @@ function FileImage({
720
919
  src: imageSrc,
721
920
  alt: alt || file.name,
722
921
  className,
723
- style: imageStyle,
922
+ style: {
923
+ ...imageStyle,
924
+ opacity: isLoading ? 0 : 1,
925
+ transition: "opacity 0.3s ease-in"
926
+ },
724
927
  onLoad: handleLoad,
725
928
  onError: handleError
726
929
  }
@@ -734,10 +937,33 @@ function clearImageCache() {
734
937
  // src/components/FilePreview.tsx
735
938
  var import_react5 = require("react");
736
939
  var import_jsx_runtime5 = require("react/jsx-runtime");
940
+ var fileTypeColors = {
941
+ pdf: { bg: "transparent", text: "#94a3b8" },
942
+ // Slate-400
943
+ doc: { bg: "transparent", text: "#94a3b8" },
944
+ docx: { bg: "transparent", text: "#94a3b8" },
945
+ xls: { bg: "transparent", text: "#94a3b8" },
946
+ xlsx: { bg: "transparent", text: "#94a3b8" },
947
+ csv: { bg: "transparent", text: "#94a3b8" },
948
+ txt: { bg: "transparent", text: "#94a3b8" },
949
+ zip: { bg: "transparent", text: "#94a3b8" },
950
+ rar: { bg: "transparent", text: "#94a3b8" },
951
+ mp4: { bg: "transparent", text: "#94a3b8" },
952
+ mov: { bg: "transparent", text: "#94a3b8" },
953
+ avi: { bg: "transparent", text: "#94a3b8" },
954
+ mp3: { bg: "transparent", text: "#94a3b8" },
955
+ wav: { bg: "transparent", text: "#94a3b8" },
956
+ default: { bg: "transparent", text: "#64748b" }
957
+ // Slate-500
958
+ };
959
+ function getFileTypeColor(filename) {
960
+ const ext = filename.split(".").pop()?.toLowerCase() || "";
961
+ return fileTypeColors[ext] || fileTypeColors.default;
962
+ }
737
963
  function FilePreview({
738
964
  file: initialFile,
739
- width = 80,
740
- height = 80,
965
+ width = 120,
966
+ height = 120,
741
967
  className,
742
968
  style,
743
969
  objectFit = "cover",
@@ -747,6 +973,7 @@ function FilePreview({
747
973
  const { client } = usePlugable();
748
974
  const [file, setFile] = (0, import_react5.useState)(initialFile);
749
975
  const [isRefetching, setIsRefetching] = (0, import_react5.useState)(false);
976
+ const [isHovered, setIsHovered] = (0, import_react5.useState)(false);
750
977
  (0, import_react5.useEffect)(() => {
751
978
  setFile(initialFile);
752
979
  }, [initialFile.id, initialFile.download_url]);
@@ -761,45 +988,105 @@ function FilePreview({
761
988
  } finally {
762
989
  setIsRefetching(false);
763
990
  }
764
- }, [file.id, client]);
991
+ }, [file.id, client, isRefetching]);
765
992
  const isImage = file.content_type.startsWith("image/");
766
993
  const containerStyle = {
767
994
  width,
768
995
  height,
769
- borderRadius: 4,
996
+ borderRadius: "8px",
770
997
  overflow: "hidden",
771
- backgroundColor: "#f5f5f5",
998
+ position: "relative",
772
999
  display: "flex",
773
1000
  alignItems: "center",
774
1001
  justifyContent: "center",
775
- border: "1px solid #eee",
1002
+ border: isHovered ? "1px solid var(--plugable-accent-primary)" : "1px solid var(--plugable-base-border)",
1003
+ background: "var(--plugable-base-surface)",
1004
+ transition: "all 0.3s cubic-bezier(0.4, 0, 0.2, 1)",
1005
+ boxShadow: "0 2px 4px rgba(0, 0, 0, 0.05)",
1006
+ cursor: "pointer",
776
1007
  ...style
777
1008
  };
778
1009
  if (isImage) {
779
1010
  return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
780
- FileImage,
1011
+ "div",
781
1012
  {
782
- file,
783
- width,
784
- height,
785
- objectFit,
1013
+ style: containerStyle,
786
1014
  className,
787
- style,
788
- borderRadius: 4,
789
- onRefetchNeeded: handleRefetch
1015
+ onMouseEnter: () => setIsHovered(true),
1016
+ onMouseLeave: () => setIsHovered(false),
1017
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1018
+ FileImage,
1019
+ {
1020
+ file,
1021
+ width,
1022
+ height,
1023
+ objectFit,
1024
+ style: { borderRadius: "8px" },
1025
+ onRefetchNeeded: handleRefetch
1026
+ }
1027
+ )
790
1028
  }
791
1029
  );
792
1030
  }
793
1031
  if (renderNonImage) {
794
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style: containerStyle, children: renderNonImage(file) });
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1033
+ "div",
1034
+ {
1035
+ className,
1036
+ style: containerStyle,
1037
+ onMouseEnter: () => setIsHovered(true),
1038
+ onMouseLeave: () => setIsHovered(false),
1039
+ children: renderNonImage(file)
1040
+ }
1041
+ );
795
1042
  }
796
1043
  const extension = file.name.split(".").pop()?.toUpperCase() || "FILE";
797
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, style: containerStyle, children: showExtension && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: {
798
- fontSize: "12px",
799
- fontWeight: "bold",
800
- color: "#666",
801
- textTransform: "uppercase"
802
- }, children: extension }) });
1044
+ const colors = getFileTypeColor(file.name);
1045
+ const badgeStyle = {
1046
+ padding: "2px 8px",
1047
+ borderRadius: "4px",
1048
+ border: "1px solid var(--plugable-base-border)",
1049
+ backgroundColor: "var(--plugable-overlay)",
1050
+ display: "inline-flex",
1051
+ alignItems: "center",
1052
+ justifyContent: "center"
1053
+ };
1054
+ const extensionStyle = {
1055
+ fontSize: "11px",
1056
+ fontWeight: 600,
1057
+ color: "var(--plugable-text-secondary)",
1058
+ textTransform: "uppercase",
1059
+ letterSpacing: "0.05em"
1060
+ };
1061
+ const iconStyle = {
1062
+ width: "28px",
1063
+ height: "28px",
1064
+ marginBottom: "6px",
1065
+ color: colors.text
1066
+ };
1067
+ const wrapperStyle = {
1068
+ textAlign: "center",
1069
+ padding: "12px",
1070
+ display: "flex",
1071
+ flexDirection: "column",
1072
+ alignItems: "center",
1073
+ justifyContent: "center",
1074
+ width: "100%",
1075
+ height: "100%"
1076
+ };
1077
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1078
+ "div",
1079
+ {
1080
+ className,
1081
+ style: containerStyle,
1082
+ onMouseEnter: () => setIsHovered(true),
1083
+ onMouseLeave: () => setIsHovered(false),
1084
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { style: wrapperStyle, children: [
1085
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { style: iconStyle, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("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" }) }),
1086
+ showExtension && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { style: badgeStyle, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { style: extensionStyle, children: extension }) })
1087
+ ] })
1088
+ }
1089
+ );
803
1090
  }
804
1091
  // Annotate the CommonJS export names for ESM import in node:
805
1092
  0 && (module.exports = {
@@ -808,7 +1095,10 @@ function FilePreview({
808
1095
  FileList,
809
1096
  FilePreview,
810
1097
  PlugableProvider,
1098
+ applyCSSVariables,
811
1099
  clearImageCache,
1100
+ generateCSSVariables,
1101
+ getThemeColors,
812
1102
  useFiles,
813
1103
  usePlugable
814
1104
  });