@gwendall/share-menu

v1.0.0

Published

A beautiful share menu component with social media integrations

Readme

@gwendall/share-menu

npm version npm downloads License: MIT

A beautiful, fully customizable share menu component for React. Supports 15+ social platforms with a modern drawer UI, CSS variable theming, and headless mode for complete control.

✨ Features

  • 🎨 Fully themeable — CSS variables + Tailwind class overrides
  • 🧩 Headless mode — Use the hook to build your own UI
  • 📱 Mobile-first — Beautiful drawer with native share API support
  • 🔧 Customizable — Hide/show platforms, custom labels & icons
  • 📦 Tree-shakeable — Import only what you need
  • 🌐 15+ platforms — WhatsApp, X, Telegram, Instagram, and more

📦 Installation

npm install @gwendall/share-menu
# or
pnpm add @gwendall/share-menu
# or
yarn add @gwendall/share-menu

Peer Dependencies

npm install react react-dom

Tailwind CSS Setup

Add the package to your tailwind.config.js content array:

// tailwind.config.js
module.exports = {
  content: [
    "./src/**/*.{js,ts,jsx,tsx}",
    "./node_modules/@gwendall/share-menu/dist/**/*.{js,mjs}", // 👈 Add this
  ],
  // ...
}

🚀 Quick Start

Full Drawer (recommended)

import { ShareMenuDrawer } from "@gwendall/share-menu";

function App() {
  return (
    <ShareMenuDrawer
      title="Share this!"
      shareUrl="https://example.com"
      shareText="Check out this awesome page!"
    >
      <button>Share</button>
    </ShareMenuDrawer>
  );
}

Content Only (for custom modals)

import { ShareMenuContent } from "@gwendall/share-menu/content";

function CustomModal() {
  return (
    <Dialog>
      <ShareMenuContent
        shareUrl="https://example.com"
        shareText="Check this out!"
      />
    </Dialog>
  );
}

Headless (full control)

import { useShareMenu } from "@gwendall/share-menu/headless";

function CustomShareUI() {
  const {
    canNativeShare,
    copied,
    copyLink,
    nativeShare,
    shareWhatsApp,
    shareX,
  } = useShareMenu({
    shareUrl: "https://example.com",
    shareText: "Check this out!",
  });

  return (
    <div className="flex gap-2">
      {canNativeShare && <button onClick={nativeShare}>Share</button>}
      <button onClick={copyLink}>{copied ? "Copied!" : "Copy"}</button>
      <button onClick={shareWhatsApp}>WhatsApp</button>
      <button onClick={shareX}>X</button>
    </div>
  );
}

🖼️ Content Preview

The share menu can display a preview of the content being shared. It automatically detects the content type based on the URL and displays an appropriate preview.

Auto-detection

// Image preview (detected from extension)
<ShareMenuDrawer preview="https://example.com/image.png" {...props} />

// Video preview (detected from extension)
<ShareMenuDrawer preview="https://example.com/video.mp4" {...props} />

// Audio file (shows icon + filename)
<ShareMenuDrawer preview="https://example.com/song.mp3" {...props} />

// Link (shows link icon + URL)
<ShareMenuDrawer preview="https://example.com/page" {...props} />

Explicit Type

// Force image type (e.g., for API endpoints)
<ShareMenuDrawer
  preview={{ url: "/api/og?id=123", type: "image" }}
  {...props}
/>

// Video with poster image
<ShareMenuDrawer
  preview={{
    url: "https://example.com/video.mp4",
    type: "video",
    poster: "https://example.com/thumbnail.jpg"
  }}
  {...props}
/>

// File with custom filename
<ShareMenuDrawer
  preview={{
    url: "https://example.com/doc.pdf",
    type: "file",
    filename: "Report Q4 2024.pdf"
  }}
  {...props}
/>

PreviewConfig

interface PreviewConfig {
  url: string;              // URL of the content
  type?: PreviewType;       // "image" | "video" | "audio" | "file" | "link" | "auto"
  filename?: string;        // Display name for file/audio types
  alt?: string;             // Alt text for images
  poster?: string;          // Poster image for videos
}

Supported Formats

  • Images: jpg, jpeg, png, gif, webp, svg, bmp, ico, avif
  • Videos: mp4, webm, mov, avi, mkv, m4v, ogv
  • Audio: mp3, wav, ogg, m4a, aac, flac, wma

🎨 Theming

CSS Variables

Override these variables to match your theme:

:root {
  /* Drawer */
  --share-menu-overlay-bg: rgba(0, 0, 0, 0.7);
  --share-menu-drawer-bg: #09090b;
  --share-menu-drawer-border: #27272a;
  --share-menu-handle-bg: #27272a;
  
  /* Typography */
  --share-menu-title-color: #ffffff;
  --share-menu-subtitle-color: #a1a1aa;
  --share-menu-button-label-color: #ffffff;
  
  /* Image Preview */
  --share-menu-preview-bg: rgba(255, 255, 255, 0.05);
  --share-menu-preview-shimmer: rgba(255, 255, 255, 0.1);
  
  /* Platform colors (optional - defaults to brand colors) */
  --share-menu-whatsapp-bg: #25D366;
  --share-menu-telegram-bg: #229ED9;
  /* ... see full list below */
}

/* Dark/Light mode support */
.dark {
  --share-menu-drawer-bg: #0a0a0a;
}

.light {
  --share-menu-drawer-bg: #ffffff;
  --share-menu-title-color: #09090b;
}
/* Drawer */
--share-menu-overlay-bg: rgba(0, 0, 0, 0.7);
--share-menu-drawer-bg: #09090b;
--share-menu-drawer-border: #27272a;
--share-menu-handle-bg: #27272a;

/* Typography */
--share-menu-title-color: #ffffff;
--share-menu-subtitle-color: #a1a1aa;
--share-menu-button-label-color: #ffffff;

/* Image Preview */
--share-menu-preview-bg: rgba(255, 255, 255, 0.05);
--share-menu-preview-shimmer: rgba(255, 255, 255, 0.1);

/* Platform backgrounds */
--share-menu-native-share-bg: #7c3aed;
--share-menu-copy-bg: #3b82f6;
--share-menu-download-bg: #ef4444;
--share-menu-whatsapp-bg: #25D366;
--share-menu-telegram-bg: #229ED9;
--share-menu-instagram-bg: #E1306C;
--share-menu-facebook-bg: #1877F2;
--share-menu-snapchat-bg: #FFFC00;
--share-menu-sms-bg: #22c55e;
--share-menu-email-bg: #f97316;
--share-menu-linkedin-bg: #0A66C2;
--share-menu-reddit-bg: #FF4500;
--share-menu-x-bg: #000000;
--share-menu-tiktok-bg: #000000;
--share-menu-threads-bg: #000000;

Tailwind Class Overrides

Override any part of the component with classNames:

<ShareMenuDrawer
  shareUrl="..."
  shareText="..."
  classNames={{
    // Drawer
    overlay: "bg-black/80 backdrop-blur-sm",
    drawer: "bg-background rounded-t-3xl",
    drawerInner: "p-6",
    handle: "bg-muted",
    
    // Content
    root: "max-w-lg",
    header: "mb-6",
    title: "text-3xl font-bold text-foreground",
    subtitle: "text-muted-foreground",
    grid: "gap-6",
    button: "w-20",
    buttonIcon: "rounded-xl shadow-lg",
    buttonLabel: "font-medium",
  }}
>

⚙️ API Reference

Props

ShareMenuContent / ShareMenuDrawer

| Prop | Type | Default | Description | |------|------|---------|-------------| | title | string | "Share" | Title displayed at the top | | shareUrl | string | required | URL to share | | shareText | string | required | Text to share | | preview | string \| PreviewConfig | — | Preview of content (see Preview section below) | | downloadUrl | string | — | URL for download button | | downloadFilename | string | — | Filename for download | | className | string | — | Class for root container | | classNames | object | — | Override sub-component classes | | buttonSize | number | 45 | Button size in pixels | | iconSize | number | 22 | Icon size in pixels | | show | ShareOption[] | — | Only show these platforms | | hide | ShareOption[] | — | Hide these platforms | | labels | object | — | Custom button labels | | icons | object | — | Custom button icons | | onNativeShare | () => void | — | Native share callback | | onCopy | () => void | — | Copy callback | | onDownload | () => void | — | Download callback |

ShareMenuDrawer (additional)

| Prop | Type | Default | Description | |------|------|---------|-------------| | children | ReactNode | required | Trigger element | | disabled | boolean | false | Disable the trigger | | open | boolean | — | Controlled open state | | onOpenChange | (open: boolean) => void | — | Open state callback |

ShareOption

Available platform identifiers:

type ShareOption =
  | "native"    // Native share API
  | "copy"      // Copy to clipboard
  | "download"  // Download file
  | "whatsapp"
  | "telegram"
  | "instagram"
  | "facebook"
  | "snapchat"
  | "sms"
  | "email"
  | "linkedin"
  | "reddit"
  | "x"
  | "tiktok"
  | "threads";

useShareMenu Hook

const {
  canNativeShare,  // boolean - browser supports native share
  copied,          // boolean - link was recently copied
  downloading,     // boolean - download in progress
  safeUrl,         // string - resolved share URL
  
  // Actions
  copyLink,        // () => Promise<void>
  nativeShare,     // () => Promise<void>
  downloadFile,    // () => Promise<void>
  
  // Platform share functions
  shareWhatsApp,   // () => void
  shareTelegram,   // () => void
  shareX,          // () => void
  shareFacebook,   // () => void
  shareInstagram,  // () => void
  shareTikTok,     // () => void
  shareThreads,    // () => void
  shareSnapchat,   // () => void
  shareSMS,        // () => void
  shareEmail,      // () => void
  shareLinkedIn,   // () => void
  shareReddit,     // () => void
} = useShareMenu({
  shareUrl: string,
  shareText: string,
  downloadUrl?: string,
  downloadFilename?: string,
  emailSubject?: string,
  onNativeShare?: () => void,
  onCopy?: () => void,
  onDownload?: () => void,
});

📦 Exports

// Everything
import { 
  ShareMenuDrawer, 
  ShareMenuContent,
  useShareMenu,
  CSS_VARS,
  CSS_VAR_DEFAULTS,
  // Platform utilities
  PLATFORMS,
  PLATFORM_COLORS,
  PLATFORM_ICONS,
  PLATFORM_LABELS,
  getPlatform,
  getPlatformColor,
  // Share functions
  shareToWhatsApp,
  shareToX,
  // ...
} from "@gwendall/share-menu";

// Content only (smaller bundle)
import { ShareMenuContent } from "@gwendall/share-menu/content";

// Drawer only
import { ShareMenuDrawer } from "@gwendall/share-menu/drawer";

// Headless (smallest bundle - no UI components)
import { 
  useShareMenu, 
  PLATFORM_COLORS,
  PLATFORM_ICONS,
  getPlatform,
  shareToWhatsApp, 
  shareToX,
  // ...
} from "@gwendall/share-menu/headless";

🎨 Platform Utilities

Access platform colors, icons, and labels for custom UIs:

import { 
  PLATFORM_COLORS, 
  PLATFORM_ICONS, 
  PLATFORM_LABELS,
  getPlatform,
  getAllPlatforms,
} from "@gwendall/share-menu";

// Get all platforms
const platforms = getAllPlatforms();
// [{ id: "whatsapp", label: "WhatsApp", colors: {...}, Icon: ... }, ...]

// Get single platform
const whatsapp = getPlatform("whatsapp");
// { id: "whatsapp", label: "WhatsApp", colors: { bg: "#25D366", text: "#fff" }, Icon: ... }

// Use colors directly
const bgColor = PLATFORM_COLORS.whatsapp.bg; // "#25D366"
const textColor = PLATFORM_COLORS.whatsapp.text; // "#ffffff"

// Use icons
const WhatsAppIcon = PLATFORM_ICONS.whatsapp;
<WhatsAppIcon size={24} className="text-white" />

// Use labels
const label = PLATFORM_LABELS.whatsapp; // "WhatsApp"

Build a custom share button

import { getPlatform, shareToWhatsApp } from "@gwendall/share-menu";

function WhatsAppButton({ url, text }: { url: string; text: string }) {
  const { colors, Icon, label } = getPlatform("whatsapp");
  
  return (
    <button
      onClick={() => shareToWhatsApp(url, text)}
      style={{ backgroundColor: colors.bg, color: colors.text }}
    >
      <Icon size={20} />
      {label}
    </button>
  );
}

🛠 Examples

Filter platforms

// Show only specific platforms
<ShareMenuContent
  shareUrl="..."
  shareText="..."
  show={["copy", "whatsapp", "telegram", "x"]}
/>

// Hide specific platforms
<ShareMenuContent
  shareUrl="..."
  shareText="..."
  hide={["tiktok", "snapchat", "threads"]}
/>

Custom labels

<ShareMenuContent
  shareUrl="..."
  shareText="..."
  labels={{
    copy: "Copy Link",
    native: "More options...",
    whatsapp: "Send via WhatsApp",
  }}
/>

With download

<ShareMenuDrawer
  shareUrl="https://example.com/post/123"
  shareText="Check out my video!"
  downloadUrl="https://example.com/video.mp4"
  downloadFilename="my-video.mp4"
>
  <button>Share</button>
</ShareMenuDrawer>

Controlled state

function ControlledExample() {
  const [open, setOpen] = useState(false);

  return (
    <>
      <button onClick={() => setOpen(true)}>Open Share Menu</button>
      <ShareMenuDrawer
        open={open}
        onOpenChange={setOpen}
        shareUrl="..."
        shareText="..."
      >
        <span /> {/* Hidden trigger */}
      </ShareMenuDrawer>
    </>
  );
}

📋 Requirements

  • React 18+
  • Tailwind CSS (for default styling)

Note: If you're not using Tailwind, you can use the headless hook to build your own UI, or override all classes via classNames.

🤝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

📄 License

MIT © Gwendall