react-sharesheet 1.8.0 → 1.9.0

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.
@@ -17,7 +17,7 @@ declare const CSS_VAR_UI_DEFAULTS: {
17
17
  readonly "--sharesheet-overlay-bg": "rgba(0, 0, 0, 0.7)";
18
18
  readonly "--sharesheet-drawer-bg": "#09090b";
19
19
  readonly "--sharesheet-drawer-border": "#27272a";
20
- readonly "--sharesheet-handle-bg": "#27272a";
20
+ readonly "--sharesheet-handle-bg": "rgba(255, 255, 255, 0.2)";
21
21
  readonly "--sharesheet-title-color": "#ffffff";
22
22
  readonly "--sharesheet-subtitle-color": "#a1a1aa";
23
23
  readonly "--sharesheet-button-label-color": "#ffffff";
@@ -85,6 +85,12 @@ interface ShareSheetContentProps {
85
85
  downloadUrl?: string | null;
86
86
  /** Filename for downloaded file */
87
87
  downloadFilename?: string;
88
+ /** Custom preview image URL (skips OG fetching if provided) */
89
+ previewImage?: string | null;
90
+ /** File to share via native share (data URL or Blob) - takes priority over URL sharing */
91
+ shareFile?: string | Blob | null;
92
+ /** Filename for the shared file */
93
+ shareFilename?: string;
88
94
  /** Custom class name for the container (shorthand for classNames.root) */
89
95
  className?: string;
90
96
  /** Override class names for sub-components */
@@ -99,6 +105,8 @@ interface ShareSheetContentProps {
99
105
  onCopy?: () => void;
100
106
  /** Called when download starts */
101
107
  onDownload?: () => void;
108
+ /** Called on every share option click, with the option id (analytics-friendly) */
109
+ onShare?: (option: ShareOption) => void;
102
110
  /** Hide specific share options */
103
111
  hide?: ShareOption[];
104
112
  /** Show only these platforms, in this exact order */
@@ -199,7 +207,7 @@ declare const CSS_VAR_DEFAULTS: {
199
207
  readonly "--sharesheet-overlay-bg": "rgba(0, 0, 0, 0.7)";
200
208
  readonly "--sharesheet-drawer-bg": "#09090b";
201
209
  readonly "--sharesheet-drawer-border": "#27272a";
202
- readonly "--sharesheet-handle-bg": "#27272a";
210
+ readonly "--sharesheet-handle-bg": "rgba(255, 255, 255, 0.2)";
203
211
  readonly "--sharesheet-title-color": "#ffffff";
204
212
  readonly "--sharesheet-subtitle-color": "#a1a1aa";
205
213
  readonly "--sharesheet-button-label-color": "#ffffff";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-sharesheet",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "A mobile-first share sheet for React with native share support, Open Graph previews, 15+ social platforms, and headless APIs. Tailwind-styled by default, fully customizable.",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -35,6 +35,21 @@
35
35
  "require": "./dist/headless.js"
36
36
  }
37
37
  },
38
+ "scripts": {
39
+ "build": "tsup",
40
+ "dev": "tsup --watch",
41
+ "test": "vitest run",
42
+ "test:watch": "vitest",
43
+ "test:coverage": "vitest run --coverage",
44
+ "prepack": "npm run clean && npm run build",
45
+ "prepublishOnly": "pnpm run type-check && pnpm run test && pnpm run build",
46
+ "publish-patch": "npm version patch && npm publish",
47
+ "publish-minor": "npm version minor && npm publish",
48
+ "publish-major": "npm version major && npm publish",
49
+ "release": "npm publish",
50
+ "type-check": "tsc --noEmit",
51
+ "clean": "rm -rf dist"
52
+ },
38
53
  "repository": {
39
54
  "type": "git",
40
55
  "url": "https://github.com/gwendall/react-sharesheet.git"
@@ -85,18 +100,5 @@
85
100
  "dist",
86
101
  "src",
87
102
  "README.md"
88
- ],
89
- "scripts": {
90
- "build": "tsup",
91
- "dev": "tsup --watch",
92
- "test": "vitest run",
93
- "test:watch": "vitest",
94
- "test:coverage": "vitest run --coverage",
95
- "publish-patch": "npm version patch && npm publish",
96
- "publish-minor": "npm version minor && npm publish",
97
- "publish-major": "npm version major && npm publish",
98
- "release": "npm publish",
99
- "type-check": "tsc --noEmit",
100
- "clean": "rm -rf dist"
101
- }
102
- }
103
+ ]
104
+ }
@@ -28,9 +28,9 @@ const defaultClasses = {
28
28
  header: "text-center mb-2",
29
29
  title: "text-2xl font-black",
30
30
  subtitle: "mt-1 text-sm",
31
- preview: "flex justify-center mb-4 px-4",
31
+ preview: "flex justify-center my-4 px-4",
32
32
  previewSkeleton: "rounded-xl overflow-hidden",
33
- previewImage: "border border-white/10",
33
+ previewImage: "border border-white/10 rounded-xl",
34
34
  previewMeta: "",
35
35
  grid: "px-2 py-6 flex flex-row items-start gap-4 gap-y-6 flex-wrap justify-center",
36
36
  button: "flex flex-col items-center gap-0 text-xs w-[60px] outline-none cursor-pointer group",
@@ -64,12 +64,16 @@ export function ShareSheetContent({
64
64
  shareText,
65
65
  downloadUrl,
66
66
  downloadFilename,
67
+ previewImage,
68
+ shareFile,
69
+ shareFilename,
67
70
  className,
68
71
  classNames = {},
69
72
  buttonSize = DEFAULT_BUTTON_SIZE,
70
73
  iconSize = DEFAULT_ICON_SIZE,
71
74
  onNativeShare,
72
75
  onCopy,
76
+ onShare,
73
77
  onDownload,
74
78
  hide = [],
75
79
  show,
@@ -79,8 +83,8 @@ export function ShareSheetContent({
79
83
  const [imageLoaded, setImageLoaded] = useState(false);
80
84
  const [imageError, setImageError] = useState(false);
81
85
 
82
- // Fetch OG data automatically from shareUrl
83
- const { ogData, loading: ogLoading } = useOGData(shareUrl);
86
+ // Only fetch OG data if previewImage is not provided
87
+ const { ogData, loading: ogLoading } = useOGData(previewImage ? undefined : shareUrl);
84
88
 
85
89
  const handleImageLoad = useCallback(() => {
86
90
  setImageLoaded(true);
@@ -96,6 +100,8 @@ export function ShareSheetContent({
96
100
  downloadUrl,
97
101
  downloadFilename,
98
102
  emailSubject: shareText,
103
+ shareFile,
104
+ shareFilename,
99
105
  onNativeShare,
100
106
  onCopy,
101
107
  onDownload,
@@ -151,11 +157,14 @@ export function ShareSheetContent({
151
157
  // Use CSS var with fallback to platform color
152
158
  bgColor: cssVar(PLATFORM_CSS_VARS[id], PLATFORM_COLORS[id].bg),
153
159
  textColor: PLATFORM_COLORS[id].text,
154
- onClick: shareActions[id],
160
+ onClick: () => {
161
+ onShare?.(id);
162
+ shareActions[id]();
163
+ },
155
164
  condition,
156
165
  };
157
166
  });
158
- }, [iconSize, labels, icons, dynamicLabels, shareActions, shareSheet.canNativeShare, shareSheet.platformAvailability, downloadUrl]);
167
+ }, [iconSize, labels, icons, dynamicLabels, shareActions, onShare, shareSheet.canNativeShare, shareSheet.platformAvailability, downloadUrl]);
159
168
 
160
169
  const visibleButtons = useMemo(() => {
161
170
  // If show is provided, use its order
@@ -184,11 +193,13 @@ export function ShareSheetContent({
184
193
 
185
194
  // Render OG preview
186
195
  const renderPreview = () => {
187
- const ogImage = ogData?.image;
196
+ // Use previewImage if provided, otherwise use OG data
197
+ const ogImage = previewImage || ogData?.image;
188
198
  const hasImage = ogImage && !imageError;
199
+ const isLoading = !previewImage && ogLoading;
189
200
 
190
- // Loading state
191
- if (ogLoading) {
201
+ // Loading state (only when fetching OG data, not when previewImage is provided)
202
+ if (isLoading) {
192
203
  return (
193
204
  <div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
194
205
  <div
@@ -221,8 +232,8 @@ export function ShareSheetContent({
221
232
  );
222
233
  }
223
234
 
224
- // No OG data or no image - show link placeholder
225
- if (!ogData || !hasImage) {
235
+ // No image available - show link placeholder (skip if previewImage was provided)
236
+ if (!hasImage) {
226
237
  return (
227
238
  <div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
228
239
  <div
@@ -274,7 +285,27 @@ export function ShareSheetContent({
274
285
  );
275
286
  }
276
287
 
277
- // Image loading state
288
+ // If previewImage is a data URL, show it directly (no preloading needed)
289
+ const isDataUrl = typeof ogImage === "string" && ogImage.startsWith("data:");
290
+ if (isDataUrl) {
291
+ return (
292
+ <div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
293
+ {/* eslint-disable-next-line @next/next/no-img-element */}
294
+ <img
295
+ src={ogImage}
296
+ alt="Preview"
297
+ className={cn(defaultClasses.previewImage, classNames.previewImage)}
298
+ style={{
299
+ width: "100%",
300
+ maxWidth: "320px",
301
+ height: "auto",
302
+ }}
303
+ />
304
+ </div>
305
+ );
306
+ }
307
+
308
+ // Image loading state (for remote URLs)
278
309
  if (!imageLoaded) {
279
310
  return (
280
311
  <div style={{ display: "flex", flexDirection: "column", alignItems: "center", width: "100%" }}>
@@ -308,7 +339,7 @@ export function ShareSheetContent({
308
339
  {/* eslint-disable-next-line @next/next/no-img-element */}
309
340
  <img
310
341
  src={ogImage}
311
- alt={ogData.title || "Preview"}
342
+ alt={ogData?.title || "Preview"}
312
343
  onLoad={handleImageLoad}
313
344
  onError={handleImageError}
314
345
  style={{ display: "none" }}
@@ -323,13 +354,12 @@ export function ShareSheetContent({
323
354
  {/* eslint-disable-next-line @next/next/no-img-element */}
324
355
  <img
325
356
  src={ogImage}
326
- alt={ogData.title || "Preview"}
357
+ alt={ogData?.title || "Preview"}
327
358
  className={cn(defaultClasses.previewImage, classNames.previewImage, "sharesheet-fadein")}
328
359
  style={{
329
360
  width: "100%",
330
361
  maxWidth: "320px",
331
362
  height: "auto",
332
- borderRadius: "12px",
333
363
  }}
334
364
  />
335
365
  </div>
@@ -40,6 +40,9 @@ export function ShareSheetDrawer({
40
40
  shareText,
41
41
  downloadUrl,
42
42
  downloadFilename,
43
+ previewImage,
44
+ shareFile,
45
+ shareFilename,
43
46
  disabled,
44
47
  children,
45
48
  open: controlledOpen,
@@ -111,6 +114,9 @@ export function ShareSheetDrawer({
111
114
  shareText={shareText}
112
115
  downloadUrl={downloadUrl}
113
116
  downloadFilename={downloadFilename}
117
+ previewImage={previewImage}
118
+ shareFile={shareFile}
119
+ shareFilename={shareFilename}
114
120
  className={className}
115
121
  classNames={classNames}
116
122
  buttonSize={buttonSize}
package/src/hooks.ts CHANGED
@@ -50,6 +50,10 @@ export interface UseShareSheetOptions {
50
50
  downloadFilename?: string;
51
51
  /** Email subject (optional) */
52
52
  emailSubject?: string;
53
+ /** File to share via native share (data URL or Blob) - takes priority over URL sharing */
54
+ shareFile?: string | Blob | null;
55
+ /** Filename for the shared file */
56
+ shareFilename?: string;
53
57
  /** Callback after native share */
54
58
  onNativeShare?: () => void;
55
59
  /** Callback after copy */
@@ -68,6 +72,8 @@ export function useShareSheet({
68
72
  downloadUrl,
69
73
  downloadFilename,
70
74
  emailSubject = "Share",
75
+ shareFile,
76
+ shareFilename = "share.png",
71
77
  onNativeShare,
72
78
  onCopy,
73
79
  onDownload,
@@ -105,12 +111,64 @@ export function useShareSheet({
105
111
  }, [safeUrl, onCopy]);
106
112
 
107
113
  const nativeShare = useCallback(async () => {
108
- if (!safeUrl) return;
109
114
  const nav = navigator as Navigator & {
110
115
  share?: (data: ShareData) => Promise<void>;
116
+ canShare?: (data: ShareData) => boolean;
111
117
  };
112
118
  if (!("share" in nav) || typeof nav.share !== "function") return;
119
+
113
120
  try {
121
+ // If shareFile is provided, try to share as a file
122
+ if (shareFile) {
123
+ let blob: Blob;
124
+
125
+ // Convert data URL to Blob if needed
126
+ if (typeof shareFile === "string" && shareFile.startsWith("data:")) {
127
+ const response = await fetch(shareFile);
128
+ blob = await response.blob();
129
+ } else if (shareFile instanceof Blob) {
130
+ blob = shareFile;
131
+ } else {
132
+ // Invalid shareFile, fall back to URL sharing
133
+ if (!safeUrl) return;
134
+ await nav.share({
135
+ title: shareText,
136
+ text: shareText,
137
+ url: safeUrl,
138
+ });
139
+ onNativeShare?.();
140
+ return;
141
+ }
142
+
143
+ // Create a File from the Blob
144
+ const file = new File([blob], shareFilename, { type: blob.type || "image/png" });
145
+
146
+ // Check if file sharing is supported
147
+ const shareData: ShareData = {
148
+ files: [file],
149
+ title: shareText,
150
+ text: shareText,
151
+ };
152
+
153
+ if (nav.canShare && !nav.canShare(shareData)) {
154
+ // File sharing not supported, fall back to URL sharing
155
+ if (!safeUrl) return;
156
+ await nav.share({
157
+ title: shareText,
158
+ text: shareText,
159
+ url: safeUrl,
160
+ });
161
+ onNativeShare?.();
162
+ return;
163
+ }
164
+
165
+ await nav.share(shareData);
166
+ onNativeShare?.();
167
+ return;
168
+ }
169
+
170
+ // Fall back to URL sharing
171
+ if (!safeUrl) return;
114
172
  await nav.share({
115
173
  title: shareText,
116
174
  text: shareText,
@@ -120,7 +178,7 @@ export function useShareSheet({
120
178
  } catch {
121
179
  // user canceled or share failed -> ignore
122
180
  }
123
- }, [safeUrl, shareText, onNativeShare]);
181
+ }, [safeUrl, shareText, shareFile, shareFilename, onNativeShare]);
124
182
 
125
183
  const downloadFile = useCallback(async () => {
126
184
  const url = (downloadUrl ?? "").trim();
package/src/types.ts CHANGED
@@ -21,7 +21,7 @@ export const CSS_VAR_UI_DEFAULTS = {
21
21
  [CSS_VARS_UI.overlayBg]: "rgba(0, 0, 0, 0.7)",
22
22
  [CSS_VARS_UI.drawerBg]: "#09090b",
23
23
  [CSS_VARS_UI.drawerBorder]: "#27272a",
24
- [CSS_VARS_UI.handleBg]: "#27272a",
24
+ [CSS_VARS_UI.handleBg]: "rgba(255, 255, 255, 0.2)",
25
25
  [CSS_VARS_UI.titleColor]: "#ffffff",
26
26
  [CSS_VARS_UI.subtitleColor]: "#a1a1aa",
27
27
  [CSS_VARS_UI.buttonLabelColor]: "#ffffff",
@@ -108,6 +108,12 @@ export interface ShareSheetContentProps {
108
108
  downloadUrl?: string | null;
109
109
  /** Filename for downloaded file */
110
110
  downloadFilename?: string;
111
+ /** Custom preview image URL (skips OG fetching if provided) */
112
+ previewImage?: string | null;
113
+ /** File to share via native share (data URL or Blob) - takes priority over URL sharing */
114
+ shareFile?: string | Blob | null;
115
+ /** Filename for the shared file */
116
+ shareFilename?: string;
111
117
  /** Custom class name for the container (shorthand for classNames.root) */
112
118
  className?: string;
113
119
  /** Override class names for sub-components */
@@ -122,6 +128,8 @@ export interface ShareSheetContentProps {
122
128
  onCopy?: () => void;
123
129
  /** Called when download starts */
124
130
  onDownload?: () => void;
131
+ /** Called on every share option click, with the option id (analytics-friendly) */
132
+ onShare?: (option: ShareOption) => void;
125
133
  /** Hide specific share options */
126
134
  hide?: ShareOption[];
127
135
  /** Show only these platforms, in this exact order */