From 8d087a79a18fe098e3265bb6ca19a2a34a0e45f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:50:53 +0300 Subject: [PATCH 1/7] feat(x): make url optional for text-only posts X's intent endpoint accepts a post with only text, but XShareButton and buildXShareUrl required a url. Allow the url to be omitted as long as text is set. Requested in #570. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/quiet-posts-text.md | 5 +++++ README.md | 13 +++++++------ src/XShareButton.tsx | 13 ++++++++----- test/XShareButton.test.tsx | 20 +++++++++++++++++++- 4 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 .changeset/quiet-posts-text.md diff --git a/.changeset/quiet-posts-text.md b/.changeset/quiet-posts-text.md new file mode 100644 index 00000000..fc79d147 --- /dev/null +++ b/.changeset/quiet-posts-text.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Make `url` optional on `XShareButton` and `buildXShareUrl` so text-only posts can be shared by passing only `text`. At least one of `url` or `text` is still required. Requested in #570. diff --git a/README.md b/README.md index c0b00716..647f724f 100644 --- a/README.md +++ b/README.md @@ -668,12 +668,13 @@ import { WhatsappShareButton, WhatsappIcon } from "react-share"; #### XShareButton -| Prop | Type | Default | Description | -| ---------- | ---------- | ------- | ---------------------------------- | -| `text` | `string` | – | Text to share before the URL. | -| `via` | `string` | – | Account to attribute the share to. | -| `hashtags` | `string[]` | – | Hashtags without the leading `#`. | -| `related` | `string[]` | – | Related accounts to recommend. | +| Prop | Type | Default | Description | +| ---------- | ---------- | ------- | ------------------------------------------------------------- | +| `url` | `string` | – | Optional for X. Set `text` instead to share a text-only post. | +| `text` | `string` | – | Text to share before the URL. | +| `via` | `string` | – | Account to attribute the share to. | +| `hashtags` | `string[]` | – | Hashtags without the leading `#`. | +| `related` | `string[]` | – | Related accounts to recommend. | - X can ignore or rewrite some share fields. diff --git a/src/XShareButton.tsx b/src/XShareButton.tsx index d44e5ca3..6d90d6fe 100644 --- a/src/XShareButton.tsx +++ b/src/XShareButton.tsx @@ -4,7 +4,7 @@ import { objectToGetParams } from './utils/objectToGetParams'; import { ShareButton, type ShareButtonProps } from './ShareButton'; function xLink( - url: string, + url: string | undefined, { text, via, @@ -12,14 +12,14 @@ function xLink( related = [], }: { text?: string; via?: string; hashtags?: string[]; related?: string[] }, ) { - assert(url, 'x.url'); + assert(url || text, 'x.url or x.text'); assert(Array.isArray(hashtags), 'x.hashtags is not an array'); assert(Array.isArray(related), 'x.related is not an array'); return ( 'https://x.com/intent/post' + objectToGetParams({ - url, + url: url || undefined, text, via, hashtags: hashtags.length > 0 ? hashtags.join(',') : undefined, @@ -37,8 +37,10 @@ type XShareButtonProps = Omit< hashtags?: string[]; related?: string[]; }>, - 'title' + 'title' | 'url' > & { + /** URL of the shared page. Optional when `text` is set, for text-only posts. */ + url?: string; text?: string; via?: string; hashtags?: string[]; @@ -46,10 +48,11 @@ type XShareButtonProps = Omit< }; export const XShareButton = forwardRef( - ({ hashtags, related, text, via, ...props }, ref) => ( + ({ hashtags, related, text, url, via, ...props }, ref) => ( { it('builds a share url with hashtags and related accounts', () => { const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); @@ -76,6 +76,24 @@ describe('XShareButton', () => { ); }); + it('shares text only when no url is given', () => { + const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); + + render(Share on X); + + fireEvent.click(screen.getByRole('button', { name: 'Share on X' })); + + expect(openSpy).toHaveBeenCalledWith( + 'https://x.com/intent/post?text=Just%20text', + '', + expect.any(String), + ); + }); + + it('throws when neither url nor text is given', () => { + expect(() => buildXShareUrl(undefined, {})).toThrow('x.url or x.text'); + }); + it('applies the default X aria-label to icon-only buttons', () => { render( From 37bf4cc005e7a5b8684f9950cb3a52e646c4a610 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:51:24 +0300 Subject: [PATCH 2/7] feat(email): add a `to` recipient prop Allow prefilling the recipient of the mailto link with one address or an array of addresses, in EmailShareButton and buildEmailShareUrl. This matches the existing `to` prop on FacebookMessengerShareButton. Requested in #418. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/kind-mails-arrive.md | 5 ++++ README.md | 13 +++++----- src/EmailShareButton.tsx | 20 +++++++++++++--- test/EmailShareButton.test.tsx | 42 ++++++++++++++++++++++++++++++++- 4 files changed, 70 insertions(+), 10 deletions(-) create mode 100644 .changeset/kind-mails-arrive.md diff --git a/.changeset/kind-mails-arrive.md b/.changeset/kind-mails-arrive.md new file mode 100644 index 00000000..50b829db --- /dev/null +++ b/.changeset/kind-mails-arrive.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Add a `to` prop to `EmailShareButton` and `buildEmailShareUrl` for prefilling the recipient. It accepts one address or an array of addresses. Requested in #418. diff --git a/README.md b/README.md index 647f724f..a9bff3d9 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ See every button in action in the [deployed demo](https://npm-react-share-demo.n | Network | Button | Icon | Network-specific props | | ------------------ | --------------------------------------------------------------- | ----------------------- | -------------------------------------------------- | | Bluesky | [`BlueskyShareButton`](#blueskysharebutton) | `BlueskyIcon` | `text`, `separator` | -| Email | [`EmailShareButton`](#emailsharebutton) | `EmailIcon` | `subject`, `body`, `separator` | +| Email | [`EmailShareButton`](#emailsharebutton) | `EmailIcon` | `to`, `subject`, `body`, `separator` | | Facebook | [`FacebookShareButton`](#facebooksharebutton) | `FacebookIcon` | `hashtag` | | Facebook Messenger | [`FacebookMessengerShareButton`](#facebookmessengersharebutton) | `FacebookMessengerIcon` | **`appId`**, `redirectUri`, `to`, `mobileBehavior` | | Gab | [`GabShareButton`](#gabsharebutton) | `GabIcon` | `text` | @@ -185,11 +185,12 @@ import { BlueskyShareButton, BlueskyIcon } from "react-share"; #### EmailShareButton -| Prop | Type | Default | Description | -| ----------- | -------- | ------- | ----------------------------------------------------------------------------- | -| `subject` | `string` | – | Email subject line. | -| `body` | `string` | – | Text prepended before the shared URL. | -| `separator` | `string` | `"\n"` | Separator between the body and URL. The default puts the URL on its own line. | +| Prop | Type | Default | Description | +| ----------- | -------------------- | ------- | ----------------------------------------------------------------------------- | +| `to` | `string \| string[]` | – | Recipient address, or several addresses. | +| `subject` | `string` | – | Email subject line. | +| `body` | `string` | – | Text prepended before the shared URL. | +| `separator` | `string` | `"\n"` | Separator between the body and URL. The default puts the URL on its own line. | - Uses `mailto:` navigation instead of opening a popup. - Does not expose `openShareDialogOnClick` or `onClick`. diff --git a/src/EmailShareButton.tsx b/src/EmailShareButton.tsx index a3cf3c59..d153e0dc 100644 --- a/src/EmailShareButton.tsx +++ b/src/EmailShareButton.tsx @@ -6,19 +6,32 @@ type Options = { body?: string; separator?: string; subject?: string; + /** Recipient address, or several addresses. */ + to?: string | string[]; }; type EmailShareButtonProps = Omit, 'onClick' | 'openShareDialogOnClick'> & Options; -function emailLink(url: string, { subject, body, separator = '\n' }: Options) { - return 'mailto:' + objectToGetParams({ subject, body: body ? body + separator + url : url }); +function emailRecipients(to: Options['to']) { + return (Array.isArray(to) ? to : [to]) + .filter(Boolean) + .map(address => encodeURIComponent(address as string).replace(/%40/g, '@')) + .join(','); +} + +function emailLink(url: string, { subject, body, separator = '\n', to }: Options) { + return ( + 'mailto:' + + emailRecipients(to) + + objectToGetParams({ subject, body: body ? body + separator + url : url }) + ); } export const buildEmailShareUrl = emailLink; export const EmailShareButton = forwardRef( - ({ body, separator, subject, ...props }, ref) => ( + ({ body, separator, subject, to, ...props }, ref) => ( ), diff --git a/test/EmailShareButton.test.tsx b/test/EmailShareButton.test.tsx index 08d89d61..0cbeb4eb 100644 --- a/test/EmailShareButton.test.tsx +++ b/test/EmailShareButton.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { fireEvent, render, screen } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; -import { EmailShareButton } from '../src/EmailShareButton'; +import { EmailShareButton, buildEmailShareUrl } from '../src/EmailShareButton'; describe('EmailShareButton', () => { it('navigates to a mailto link instead of opening a popup', () => { @@ -57,4 +57,44 @@ describe('EmailShareButton', () => { expect(hrefState.value).toBe('mailto:?subject=Subject&body=https%3A%2F%2Fexample.com'); }); + + it('adds one or more recipients to the mailto link', () => { + expect( + buildEmailShareUrl('https://example.com', { subject: 'Hi', to: 'you@example.com' }), + ).toBe('mailto:you@example.com?subject=Hi&body=https%3A%2F%2Fexample.com'); + expect( + buildEmailShareUrl('https://example.com', { to: ['a@example.com', 'b@example.com'] }), + ).toBe('mailto:a@example.com,b@example.com?body=https%3A%2F%2Fexample.com'); + }); + + it('ignores an empty recipient', () => { + expect(buildEmailShareUrl('https://example.com', { to: [] })).toBe( + 'mailto:?body=https%3A%2F%2Fexample.com', + ); + }); + + it('passes the `to` prop through to the mailto link', () => { + const hrefState = { value: 'https://example.test/' }; + Object.defineProperty(window, 'location', { + configurable: true, + value: { + get href() { + return hrefState.value; + }, + set href(value: string) { + hrefState.value = value; + }, + }, + }); + + render( + + Share by email + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Share by email' })); + + expect(hrefState.value).toBe('mailto:you@example.com?body=https%3A%2F%2Fexample.com'); + }); }); From 8c7648661cad31e73244a48c5ea42d36940dc341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:51:35 +0300 Subject: [PATCH 3/7] feat(whatsapp): add a `phone` recipient prop Allow addressing the share to a specific number by passing a phone in international format. Non-digit characters are stripped, and the number is added to both the web (api.whatsapp.com) and the app (whatsapp://) links. Requested in #359. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/warm-phones-ring.md | 5 ++++ README.md | 3 ++- src/WhatsappShareButton.tsx | 28 +++++++++++----------- test/WhatsappShareButton.test.tsx | 39 ++++++++++++++++++++++++++++++- 4 files changed, 60 insertions(+), 15 deletions(-) create mode 100644 .changeset/warm-phones-ring.md diff --git a/.changeset/warm-phones-ring.md b/.changeset/warm-phones-ring.md new file mode 100644 index 00000000..0fb9a2b1 --- /dev/null +++ b/.changeset/warm-phones-ring.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Add a `phone` prop to `WhatsappShareButton` and `buildWhatsappShareUrl` for sharing to a specific number. It takes the number in international format and removes everything except digits. It applies to both the web and the app link. Requested in #359. diff --git a/README.md b/README.md index a9bff3d9..da7c5c13 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ See every button in action in the [deployed demo](https://npm-react-share-demo.n | Telegram | [`TelegramShareButton`](#telegramsharebutton) | `TelegramIcon` | `text`, `mobileBehavior` | | Threads | [`ThreadsShareButton`](#threadssharebutton) | `ThreadsIcon` | `text` | | Tumblr | [`TumblrShareButton`](#tumblrsharebutton) | `TumblrIcon` | `postTitle`, `tags`, `caption`, `posttype` | -| Viber | [`ViberShareButton`](#vibersharebutton) | `ViberIcon` | `message`, `separator`, `mobileBehavior` | +| Viber | [`ViberShareButton`](#vibersharebutton) | `ViberIcon` | `phone`, `message`, `separator`, `mobileBehavior` | | VK | [`VKShareButton`](#vksharebutton) | `VKIcon` | `shareTitle`, `image`, `noParse`, `noVkLinks` | | Weibo | [`WeiboShareButton`](#weibosharebutton) | `WeiboIcon` | `shareTitle`, `image` | | WhatsApp | [`WhatsappShareButton`](#whatsappsharebutton) | `WhatsappIcon` | `message`, `separator`, `mobileBehavior` | @@ -643,6 +643,7 @@ import { WeiboShareButton, WeiboIcon } from "react-share"; | Prop | Type | Default | Description | | ---------------- | -------------------------- | ------- | ------------------------------------------------------------------------------------------- | +| `phone` | `string` | – | Recipient phone number in international format. Non-digits are removed. | | `message` | `string` | – | Message to share before the URL. | | `separator` | `string` | `" "` | Separator between the message and URL. | | `mobileBehavior` | `"web" \| "app" \| "auto"` | `"web"` | `"app"` uses a WhatsApp app link and `"auto"` uses it only on detected mobile environments. | diff --git a/src/WhatsappShareButton.tsx b/src/WhatsappShareButton.tsx index 04d961c1..eec9e3ea 100644 --- a/src/WhatsappShareButton.tsx +++ b/src/WhatsappShareButton.tsx @@ -4,23 +4,26 @@ import { assert } from './utils/assert'; import { objectToGetParams } from './utils/objectToGetParams'; import { ShareButton, type ShareButtonProps } from './ShareButton'; -type WhatsappShareButtonProps = Omit< - ShareButtonProps<{ message?: string; separator?: string }>, - 'title' -> & { +type WhatsappOptions = { message?: string; phone?: string; separator?: string }; + +type WhatsappShareButtonProps = Omit, 'title'> & { message?: string; mobileBehavior?: MobileBehavior; + /** Recipient phone number in international format, for example `+358401234567`. */ + phone?: string; separator?: string; }; -function whatsappLink( - url: string, - { message, separator = ' ' }: { message?: string; separator?: string }, -) { +function whatsappPhone(phone: string | undefined) { + return phone?.replace(/\D/g, '') || undefined; +} + +function whatsappLink(url: string, { message, phone, separator = ' ' }: WhatsappOptions) { assert(url, 'whatsapp.url'); return ( 'https://api.whatsapp.com/send' + objectToGetParams({ + phone: whatsappPhone(phone), text: message ? message + separator + url : url, }) ); @@ -28,21 +31,19 @@ function whatsappLink( export const buildWhatsappShareUrl = whatsappLink; -function whatsappAppLink( - url: string, - { message, separator = ' ' }: { message?: string; separator?: string }, -) { +function whatsappAppLink(url: string, { message, phone, separator = ' ' }: WhatsappOptions) { assert(url, 'whatsapp.url'); return ( 'whatsapp://send' + objectToGetParams({ + phone: whatsappPhone(phone), text: message ? message + separator + url : url, }) ); } export const WhatsappShareButton = forwardRef( - ({ message, mobileBehavior, separator, ...props }, ref) => { + ({ message, mobileBehavior, phone, separator, ...props }, ref) => { const { linkBehavior, networkLink } = resolveMobileShareStrategy( mobileBehavior, buildWhatsappShareUrl, @@ -58,6 +59,7 @@ export const WhatsappShareButton = forwardRef { function stubLocation() { @@ -79,4 +79,41 @@ describe('WhatsappShareButton', () => { expect.any(String), ); }); + + it('addresses the share to a phone number, keeping digits only', () => { + expect( + buildWhatsappShareUrl('https://example.com', { phone: '+358 40-123 4567', message: 'Hi' }), + ).toBe('https://api.whatsapp.com/send?phone=358401234567&text=Hi%20https%3A%2F%2Fexample.com'); + }); + + it('passes the `phone` prop to the web and app links', () => { + const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); + const hrefState = stubLocation(); + + const { rerender } = render( + + Share to WhatsApp + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Share to WhatsApp' })); + + expect(openSpy).toHaveBeenLastCalledWith( + 'https://api.whatsapp.com/send?phone=358401234567&text=https%3A%2F%2Fexample.com', + '', + expect.any(String), + ); + + rerender( + + Share to WhatsApp + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Share to WhatsApp' })); + + expect(hrefState.value).toBe( + 'whatsapp://send?phone=358401234567&text=https%3A%2F%2Fexample.com', + ); + }); }); From 5ebcdc2752b997d99b980df510b5cf89d90c29e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:52:00 +0300 Subject: [PATCH 4/7] feat(tumblr): add a `content` field and list the post types For post types other than `link`, Tumblr reads the actual post data, such as the quote text, from `content`. Add it to TumblrShareButton and buildTumblrShareUrl, and list the documented post types for `posttype`. Re-implements #564 on top of the v6 prop names, since the original no longer applied after `title` became `postTitle`. Co-Authored-By: Maddy Miller Co-Authored-By: Claude Sonnet 5.5 --- .changeset/bold-quotes-post.md | 5 +++ README.md | 63 +++++++++++++++-------------- src/TumblrShareButton.tsx | 19 +++++++-- test/ShareButtonComponents.test.tsx | 25 +++++++++++- 4 files changed, 76 insertions(+), 36 deletions(-) create mode 100644 .changeset/bold-quotes-post.md diff --git a/.changeset/bold-quotes-post.md b/.changeset/bold-quotes-post.md new file mode 100644 index 00000000..c278ca61 --- /dev/null +++ b/.changeset/bold-quotes-post.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Add a `content` prop to `TumblrShareButton` and `buildTumblrShareUrl`, and list the other Tumblr post types for `posttype`. For post types other than `link`, `content` carries the actual post data, such as the quote text. Based on #564 by @me4502. diff --git a/README.md b/README.md index da7c5c13..f653274f 100644 --- a/README.md +++ b/README.md @@ -74,31 +74,31 @@ See every button in action in the [deployed demo](https://npm-react-share-demo.n ## Supported networks -| Network | Button | Icon | Network-specific props | -| ------------------ | --------------------------------------------------------------- | ----------------------- | -------------------------------------------------- | -| Bluesky | [`BlueskyShareButton`](#blueskysharebutton) | `BlueskyIcon` | `text`, `separator` | -| Email | [`EmailShareButton`](#emailsharebutton) | `EmailIcon` | `to`, `subject`, `body`, `separator` | -| Facebook | [`FacebookShareButton`](#facebooksharebutton) | `FacebookIcon` | `hashtag` | -| Facebook Messenger | [`FacebookMessengerShareButton`](#facebookmessengersharebutton) | `FacebookMessengerIcon` | **`appId`**, `redirectUri`, `to`, `mobileBehavior` | -| Gab | [`GabShareButton`](#gabsharebutton) | `GabIcon` | `text` | -| Hatena | [`HatenaShareButton`](#hatenasharebutton) | `HatenaIcon` | `shareTitle` | -| Instapaper | [`InstapaperShareButton`](#instapapersharebutton) | `InstapaperIcon` | `shareTitle`, `description` | -| Line | [`LineShareButton`](#linesharebutton) | `LineIcon` | `text` | -| LinkedIn | [`LinkedinShareButton`](#linkedinsharebutton) | `LinkedinIcon` | – | -| LiveJournal | [`LivejournalShareButton`](#livejournalsharebutton) | `LivejournalIcon` | `shareTitle`, `description` | -| Mail.Ru | [`MailruShareButton`](#mailrusharebutton) | `MailruIcon` | `shareTitle`, `description`, `imageUrl` | -| Odnoklassniki | [`OKShareButton`](#oksharebutton) | `OKIcon` | `shareTitle`, `description`, `image` | -| Pinterest | [`PinterestShareButton`](#pinterestsharebutton) | `PinterestIcon` | **`media`**, `description`, `pinId` | -| Reddit | [`RedditShareButton`](#redditsharebutton) | `RedditIcon` | `shareTitle` | -| Telegram | [`TelegramShareButton`](#telegramsharebutton) | `TelegramIcon` | `text`, `mobileBehavior` | -| Threads | [`ThreadsShareButton`](#threadssharebutton) | `ThreadsIcon` | `text` | -| Tumblr | [`TumblrShareButton`](#tumblrsharebutton) | `TumblrIcon` | `postTitle`, `tags`, `caption`, `posttype` | -| Viber | [`ViberShareButton`](#vibersharebutton) | `ViberIcon` | `phone`, `message`, `separator`, `mobileBehavior` | -| VK | [`VKShareButton`](#vksharebutton) | `VKIcon` | `shareTitle`, `image`, `noParse`, `noVkLinks` | -| Weibo | [`WeiboShareButton`](#weibosharebutton) | `WeiboIcon` | `shareTitle`, `image` | -| WhatsApp | [`WhatsappShareButton`](#whatsappsharebutton) | `WhatsappIcon` | `message`, `separator`, `mobileBehavior` | -| X | [`XShareButton`](#xsharebutton) | `XIcon` | `text`, `via`, `hashtags`, `related` | -| Device share sheet | [`NativeShareButton`](#nativesharebutton) | `NativeShareIcon` | `title`, `text`, `url`, `files` | +| Network | Button | Icon | Network-specific props | +| ------------------ | --------------------------------------------------------------- | ----------------------- | ----------------------------------------------------- | +| Bluesky | [`BlueskyShareButton`](#blueskysharebutton) | `BlueskyIcon` | `text`, `separator` | +| Email | [`EmailShareButton`](#emailsharebutton) | `EmailIcon` | `to`, `subject`, `body`, `separator` | +| Facebook | [`FacebookShareButton`](#facebooksharebutton) | `FacebookIcon` | `hashtag` | +| Facebook Messenger | [`FacebookMessengerShareButton`](#facebookmessengersharebutton) | `FacebookMessengerIcon` | **`appId`**, `redirectUri`, `to`, `mobileBehavior` | +| Gab | [`GabShareButton`](#gabsharebutton) | `GabIcon` | `text` | +| Hatena | [`HatenaShareButton`](#hatenasharebutton) | `HatenaIcon` | `shareTitle` | +| Instapaper | [`InstapaperShareButton`](#instapapersharebutton) | `InstapaperIcon` | `shareTitle`, `description` | +| Line | [`LineShareButton`](#linesharebutton) | `LineIcon` | `text` | +| LinkedIn | [`LinkedinShareButton`](#linkedinsharebutton) | `LinkedinIcon` | – | +| LiveJournal | [`LivejournalShareButton`](#livejournalsharebutton) | `LivejournalIcon` | `shareTitle`, `description` | +| Mail.Ru | [`MailruShareButton`](#mailrusharebutton) | `MailruIcon` | `shareTitle`, `description`, `imageUrl` | +| Odnoklassniki | [`OKShareButton`](#oksharebutton) | `OKIcon` | `shareTitle`, `description`, `image` | +| Pinterest | [`PinterestShareButton`](#pinterestsharebutton) | `PinterestIcon` | **`media`**, `description`, `pinId` | +| Reddit | [`RedditShareButton`](#redditsharebutton) | `RedditIcon` | `shareTitle` | +| Telegram | [`TelegramShareButton`](#telegramsharebutton) | `TelegramIcon` | `text`, `mobileBehavior` | +| Threads | [`ThreadsShareButton`](#threadssharebutton) | `ThreadsIcon` | `text` | +| Tumblr | [`TumblrShareButton`](#tumblrsharebutton) | `TumblrIcon` | `postTitle`, `tags`, `caption`, `posttype`, `content` | +| Viber | [`ViberShareButton`](#vibersharebutton) | `ViberIcon` | `phone`, `message`, `separator`, `mobileBehavior` | +| VK | [`VKShareButton`](#vksharebutton) | `VKIcon` | `shareTitle`, `image`, `noParse`, `noVkLinks` | +| Weibo | [`WeiboShareButton`](#weibosharebutton) | `WeiboIcon` | `shareTitle`, `image` | +| WhatsApp | [`WhatsappShareButton`](#whatsappsharebutton) | `WhatsappIcon` | `message`, `separator`, `mobileBehavior` | +| X | [`XShareButton`](#xsharebutton) | `XIcon` | `text`, `via`, `hashtags`, `related` | +| Device share sheet | [`NativeShareButton`](#nativesharebutton) | `NativeShareIcon` | `title`, `text`, `url`, `files` | Required props are in **bold**. Every icon also has a badge-style `*LegacyIcon` variant (see [Icons](#icons)), and every network button has a matching `build*ShareUrl` helper (see [Building share URLs yourself](#building-share-urls-yourself)). @@ -533,12 +533,13 @@ import { ThreadsShareButton, ThreadsIcon } from "react-share"; #### TumblrShareButton -| Prop | Type | Default | Description | -| ----------- | ---------- | -------- | --------------------------------------- | -| `postTitle` | `string` | – | Title sent in the Tumblr post payload. | -| `tags` | `string[]` | – | Tags to attach to the post. | -| `caption` | `string` | – | Description shown with the shared page. | -| `posttype` | `string` | `"link"` | Tumblr post type. | +| Prop | Type | Default | Description | +| ----------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | +| `postTitle` | `string` | – | Title sent in the Tumblr post payload. | +| `tags` | `string[]` | – | Tags to attach to the post. | +| `caption` | `string` | – | Description shown with the shared page. | +| `posttype` | `string` | `"link"` | Tumblr post type, such as `"quote"`. | +| `content` | `string` | – | Post content for post types other than `"link"`. See [Tumblr's documentation](https://help.tumblr.com/knowledge-base/share-button-documentation/). |
Example diff --git a/src/TumblrShareButton.tsx b/src/TumblrShareButton.tsx index 4bd77251..4de8e0bb 100644 --- a/src/TumblrShareButton.tsx +++ b/src/TumblrShareButton.tsx @@ -3,6 +3,8 @@ import { assert } from './utils/assert'; import { objectToGetParams } from './utils/objectToGetParams'; import { ShareButton, type ShareButtonProps } from './ShareButton'; +type TumblrPostType = 'link' | 'text' | 'quote' | 'photo' | 'chat' | 'video' | (string & {}); + function tumblrLink( url: string, { @@ -10,7 +12,14 @@ function tumblrLink( caption, tags, posttype, - }: { postTitle?: string; caption?: string; tags?: string; posttype?: 'link' | string }, + content, + }: { + postTitle?: string; + caption?: string; + tags?: string; + posttype?: TumblrPostType; + content?: string; + }, ) { assert(url, 'tumblr.url'); @@ -22,6 +31,7 @@ function tumblrLink( caption, tags, posttype, + content, }) ); } @@ -31,14 +41,16 @@ export const buildTumblrShareUrl = tumblrLink; type Options = { postTitle?: string; caption?: string; - posttype?: 'link' | string; + /** The post content for post types other than `link`, such as the quote text or photo URL. */ + content?: string; + posttype?: TumblrPostType; }; type TumblrShareButtonProps = Omit, 'title'> & Options & { tags?: string[] }; export const TumblrShareButton = forwardRef( - ({ caption, postTitle, posttype, tags, ...props }, ref) => ( + ({ caption, content, postTitle, posttype, tags, ...props }, ref) => ( { expect(button).not.toHaveAttribute('posttype'); }); + it('sends the Tumblr content field for non-link post types', () => { + const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); + + render( + + Share on Tumblr + , + ); + + const button = screen.getByRole('button', { name: 'Share on Tumblr' }); + + fireEvent.click(button); + + expect(button).not.toHaveAttribute('content'); + expect(openSpy).toHaveBeenCalledWith( + 'https://www.tumblr.com/widgets/share/tool?canonicalUrl=https%3A%2F%2Fexample.com&tags=&posttype=quote&content=The%20quote', + '', + expect.any(String), + ); + }); + it('adds the email fallback label for icon-only buttons', () => { render( From a40bfc885cedeba154fe8f2ae74f4b33fadf3856 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:53:00 +0300 Subject: [PATCH 5/7] feat(share-buttons): add an `openInNewTab` option Some apps prefer a regular browser tab over a sized popup window. Add `openInNewTab` to popup-based share buttons and a matching `newTab` option to `openShareWindow`. It also works with an async `beforeOnClick`, where the blank tab is opened synchronously and loaded afterwards. Proposed in #342 and requested in #449. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/open-new-tabs.md | 5 ++++ README.md | 4 +++ src/EmailShareButton.tsx | 5 +++- src/ShareButton.tsx | 8 ++++++ src/shareWindow.ts | 26 ++++++++++++------ test/ShareButton.test.tsx | 54 +++++++++++++++++++++++++++++++++++++ test/ShareHelpers.test.ts | 8 ++++++ 7 files changed, 101 insertions(+), 9 deletions(-) create mode 100644 .changeset/open-new-tabs.md diff --git a/.changeset/open-new-tabs.md b/.changeset/open-new-tabs.md new file mode 100644 index 00000000..307ce8fd --- /dev/null +++ b/.changeset/open-new-tabs.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Add an `openInNewTab` prop to popup-based share buttons, and a matching `newTab` option for `openShareWindow`, to open the share page in a regular browser tab instead of a popup window. Requested in #449 and proposed in #342. diff --git a/README.md b/README.md index f653274f..d6aab54a 100644 --- a/README.md +++ b/README.md @@ -139,6 +139,7 @@ These props apply to every network share button. `NativeShareButton` has its [ow | `onShareWindowClose` | `() => void` | – | Called after the popup closes. | | `windowWidth` | `number` | – | Overrides the popup width. | | `windowHeight` | `number` | – | Overrides the popup height. | +| `openInNewTab` | `boolean` | `false` | Opens the share page in a regular browser tab instead of a popup window. The `window*` props are ignored. | | `windowPosition` | `"windowCenter" \| "screenCenter"` | – | Controls popup positioning. | | `mobileBehavior` | `"web" \| "app" \| "auto"` | `"web"` | Only on `FacebookMessengerShareButton`, `TelegramShareButton`, `ViberShareButton`, and `WhatsappShareButton`. See [Mobile app handoff](#mobile-app-handoff). | @@ -812,6 +813,9 @@ const url = buildXShareUrl(shareUrl, { }); openShareWindow(url, { width: 550, height: 400 }); + +// or open a regular browser tab instead of a popup window +openShareWindow(url, { newTab: true }); ``` ## Accessibility diff --git a/src/EmailShareButton.tsx b/src/EmailShareButton.tsx index d153e0dc..dd539bef 100644 --- a/src/EmailShareButton.tsx +++ b/src/EmailShareButton.tsx @@ -10,7 +10,10 @@ type Options = { to?: string | string[]; }; -type EmailShareButtonProps = Omit, 'onClick' | 'openShareDialogOnClick'> & +type EmailShareButtonProps = Omit< + ShareButtonProps, + 'onClick' | 'openInNewTab' | 'openShareDialogOnClick' +> & Options; function emailRecipients(to: Options['to']) { diff --git a/src/ShareButton.tsx b/src/ShareButton.tsx index db7d4843..f6a67ff1 100644 --- a/src/ShareButton.tsx +++ b/src/ShareButton.tsx @@ -71,6 +71,12 @@ export interface Props extends NativeButtonProps { */ onShareWindowOpen?: (shareWindow: Window | null) => void; openShareDialogOnClick?: boolean; + /** + * Opens the share page in a regular browser tab instead of a popup window. + * `windowWidth`, `windowHeight`, and `windowPosition` are ignored in that case. + * @default false + */ + openInNewTab?: boolean; opts: LinkOptions; resetButtonStyle?: boolean; /** @@ -104,6 +110,7 @@ export function ShareButton>({ onClick, onShareWindowClose, onShareWindowOpen, + openInNewTab = false, openShareDialogOnClick = true, opts, resetButtonStyle = true, @@ -136,6 +143,7 @@ export function ShareButton>({ const opensPopup = openShareDialogOnClick && linkBehavior === 'popup'; const windowOptions = { height: windowHeight, + newTab: openInNewTab, onClose: onShareWindowClose, position: windowPosition, width: windowWidth, diff --git a/src/shareWindow.ts b/src/shareWindow.ts index 121819f1..61f271b9 100644 --- a/src/shareWindow.ts +++ b/src/shareWindow.ts @@ -3,6 +3,8 @@ export type LinkBehavior = 'popup' | 'navigate'; export type ShareWindowOptions = { height?: number; + /** Open a regular browser tab instead of a sized popup window. */ + newTab?: boolean; onClose?: (dialog: Window | null) => void; position?: WindowPosition; width?: number; @@ -20,7 +22,13 @@ const getBoxPositionOnScreenCenter = (width: number, height: number) => ({ export function openShareWindow( url: string, - { height = 400, onClose, position = 'windowCenter', width = 550 }: ShareWindowOptions = {}, + { + height = 400, + newTab = false, + onClose, + position = 'windowCenter', + width = 550, + }: ShareWindowOptions = {}, ) { const config: { [key: string]: string | number } = { height, @@ -39,13 +47,15 @@ export function openShareWindow( : getBoxPositionOnScreenCenter(width, height)), }; - const shareDialog = window.open( - url, - '', - Object.keys(config) - .map(key => `${key}=${config[key]}`) - .join(', '), - ); + const shareDialog = newTab + ? window.open(url, '_blank') + : window.open( + url, + '', + Object.keys(config) + .map(key => `${key}=${config[key]}`) + .join(', '), + ); if (onClose) { const interval = window.setInterval(() => { diff --git a/test/ShareButton.test.tsx b/test/ShareButton.test.tsx index 9feebe3d..560073eb 100644 --- a/test/ShareButton.test.tsx +++ b/test/ShareButton.test.tsx @@ -38,6 +38,60 @@ describe('ShareButton', () => { expect(onClick).toHaveBeenCalledWith(expect.any(Object), 'https://example.com/share'); }); + it('opens a regular tab when openInNewTab is set', () => { + const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); + + render( + url} + networkName="test" + openInNewTab + opts={{}} + url="https://example.com" + > + Share + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Share' })); + + expect(openSpy).toHaveBeenCalledTimes(1); + expect(openSpy).toHaveBeenCalledWith('https://example.com', '_blank'); + }); + + it('pre-opens a tab for async beforeOnClick when openInNewTab is set', async () => { + let resolveBeforeOnClick = () => {}; + const shareWindow = { close: vi.fn(), closed: false, location: { replace: vi.fn() } }; + const openSpy = vi.spyOn(window, 'open').mockReturnValue(shareWindow as unknown as Window); + + render( + + new Promise(resolve => { + resolveBeforeOnClick = resolve; + }) + } + networkLink={(url: string) => url} + networkName="test" + openInNewTab + opts={{}} + url="https://example.com" + > + Share + , + ); + + fireEvent.click(screen.getByRole('button', { name: 'Share' })); + + expect(openSpy).toHaveBeenCalledWith('about:blank', '_blank'); + + resolveBeforeOnClick(); + + await waitFor(() => + expect(shareWindow.location.replace).toHaveBeenCalledWith('https://example.com'), + ); + }); + it('waits for beforeOnClick before invoking onClick', async () => { const steps: string[] = []; const onClick = vi.fn(() => { diff --git a/test/ShareHelpers.test.ts b/test/ShareHelpers.test.ts index c652db86..6c6fec6d 100644 --- a/test/ShareHelpers.test.ts +++ b/test/ShareHelpers.test.ts @@ -97,4 +97,12 @@ describe('share helpers', () => { expect.stringContaining('left=500'), ); }); + + it('opens a regular tab instead of a popup when newTab is set', () => { + const openSpy = vi.spyOn(window, 'open').mockReturnValue(null); + + openShareWindow('https://example.com/share', { newTab: true }); + + expect(openSpy).toHaveBeenCalledWith('https://example.com/share', '_blank'); + }); }); From e15ed0015c9920ab89e45cb467e448399c512c2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:54:41 +0300 Subject: [PATCH 6/7] feat(farcaster): add FarcasterShareButton and icons Farcaster (formerly Warpcast) documents a compose intent at farcaster.xyz/~/compose that takes text, embeds, channelKey and parentCastHash. Add a share button that attaches the shared URL as an embed, along with raw and legacy icons (glyph from Simple Icons, CC0), the build helper, demo entry and docs. Requested in #558. Co-Authored-By: Claude Sonnet 5.5 --- .changeset/purple-casts-share.md | 5 ++++ README.md | 25 ++++++++++++++++ demo/Demo.tsx | 15 ++++++++++ example.svg | 25 ++++++++-------- src/FarcasterIcon.tsx | 14 +++++++++ src/FarcasterLegacyIcon.tsx | 11 +++++++ src/FarcasterShareButton.tsx | 50 +++++++++++++++++++++++++++++++ src/ShareButton.tsx | 1 + src/index.ts | 4 +++ test/NetworkShareButtons.test.tsx | 12 ++++++++ test/ShareHelpers.test.ts | 6 ++++ 11 files changed, 156 insertions(+), 12 deletions(-) create mode 100644 .changeset/purple-casts-share.md create mode 100644 src/FarcasterIcon.tsx create mode 100644 src/FarcasterLegacyIcon.tsx create mode 100644 src/FarcasterShareButton.tsx diff --git a/.changeset/purple-casts-share.md b/.changeset/purple-casts-share.md new file mode 100644 index 00000000..d45891c3 --- /dev/null +++ b/.changeset/purple-casts-share.md @@ -0,0 +1,5 @@ +--- +'react-share': minor +--- + +Add `FarcasterShareButton`, `FarcasterIcon`, `FarcasterLegacyIcon`, and `buildFarcasterShareUrl` for sharing to Farcaster (formerly Warpcast) through its compose intent. The shared URL is attached as an embed, and `text`, `channelKey`, and `parentCastHash` are supported. Requested in #558. diff --git a/README.md b/README.md index d6aab54a..9d11ed64 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,7 @@ See every button in action in the [deployed demo](https://npm-react-share-demo.n | Email | [`EmailShareButton`](#emailsharebutton) | `EmailIcon` | `to`, `subject`, `body`, `separator` | | Facebook | [`FacebookShareButton`](#facebooksharebutton) | `FacebookIcon` | `hashtag` | | Facebook Messenger | [`FacebookMessengerShareButton`](#facebookmessengersharebutton) | `FacebookMessengerIcon` | **`appId`**, `redirectUri`, `to`, `mobileBehavior` | +| Farcaster | [`FarcasterShareButton`](#farcastersharebutton) | `FarcasterIcon` | `text`, `channelKey`, `parentCastHash` | | Gab | [`GabShareButton`](#gabsharebutton) | `GabIcon` | `text` | | Hatena | [`HatenaShareButton`](#hatenasharebutton) | `HatenaIcon` | `shareTitle` | | Instapaper | [`InstapaperShareButton`](#instapapersharebutton) | `InstapaperIcon` | `shareTitle`, `description` | @@ -262,6 +263,30 @@ import { FacebookMessengerShareButton, FacebookMessengerIcon } from "react-share
+#### FarcasterShareButton + +| Prop | Type | Default | Description | +| ---------------- | -------- | ------- | --------------------------------------------------- | +| `text` | `string` | – | Text of the cast. | +| `channelKey` | `string` | – | Channel to post the cast to, such as `"farcaster"`. | +| `parentCastHash` | `string` | – | Hash of a cast to reply to. | + +- The shared `url` is attached to the cast as an embed. +- Opens the [Farcaster compose intent](https://docs.farcaster.xyz/reference/farcaster/intent-urls) (formerly Warpcast). + +
+Example + +```jsx +import { FarcasterShareButton, FarcasterIcon } from "react-share"; + + + +; +``` + +
+ #### GabShareButton | Prop | Type | Default | Description | diff --git a/demo/Demo.tsx b/demo/Demo.tsx index e45daee4..700b0ad6 100644 --- a/demo/Demo.tsx +++ b/demo/Demo.tsx @@ -8,6 +8,8 @@ import { FacebookMessengerIcon, FacebookMessengerShareButton, FacebookShareButton, + FarcasterIcon, + FarcasterShareButton, GabIcon, GabShareButton, HatenaIcon, @@ -194,6 +196,19 @@ export function Demo() { +
+ + + +
+
+ @@ -11,15 +11,16 @@ - - - - - - - - - - - + + + + + + + + + + + + diff --git a/src/FarcasterIcon.tsx b/src/FarcasterIcon.tsx new file mode 100644 index 00000000..c94feddc --- /dev/null +++ b/src/FarcasterIcon.tsx @@ -0,0 +1,14 @@ +import { RawIconBase, type RawIconProps } from './icons/RawIconBase'; + +export function FarcasterIcon(props: RawIconProps) { + return ( + } + color="#855DCD" + viewBox="0 0 24 24" + {...props} + > + + + ); +} diff --git a/src/FarcasterLegacyIcon.tsx b/src/FarcasterLegacyIcon.tsx new file mode 100644 index 00000000..7f979263 --- /dev/null +++ b/src/FarcasterLegacyIcon.tsx @@ -0,0 +1,11 @@ +import { LegacyIconBase, type LegacyIconProps } from './icons/LegacyIconBase'; + +export function FarcasterLegacyIcon(props: LegacyIconProps) { + return ( + + + + + + ); +} diff --git a/src/FarcasterShareButton.tsx b/src/FarcasterShareButton.tsx new file mode 100644 index 00000000..96431752 --- /dev/null +++ b/src/FarcasterShareButton.tsx @@ -0,0 +1,50 @@ +import { forwardRef } from 'react'; +import { assert } from './utils/assert'; +import { objectToGetParams } from './utils/objectToGetParams'; +import { ShareButton, type ShareButtonProps } from './ShareButton'; + +type FarcasterOptions = { + channelKey?: string; + parentCastHash?: string; + text?: string; +}; + +type FarcasterShareButtonProps = Omit, 'title'> & + FarcasterOptions; + +function farcasterLink(url: string, { channelKey, parentCastHash, text }: FarcasterOptions) { + assert(url, 'farcaster.url'); + + return ( + 'https://farcaster.xyz/~/compose' + + objectToGetParams({ + text, + 'embeds[]': url, + channelKey, + parentCastHash, + }).replace('embeds%5B%5D=', 'embeds[]=') + ); +} + +export const buildFarcasterShareUrl = farcasterLink; + +export const FarcasterShareButton = forwardRef( + ({ channelKey, parentCastHash, text, ...props }, ref) => ( + + ), +); + +FarcasterShareButton.displayName = 'FarcasterShareButton'; diff --git a/src/ShareButton.tsx b/src/ShareButton.tsx index f6a67ff1..72e5ec1f 100644 --- a/src/ShareButton.tsx +++ b/src/ShareButton.tsx @@ -12,6 +12,7 @@ const DEFAULT_ARIA_LABELS = { email: 'Share by email', facebook: 'Share on Facebook', facebookmessenger: 'Share in Messenger', + farcaster: 'Share on Farcaster', gab: 'Share on Gab', hatena: 'Share on Hatena', instapaper: 'Save to Instapaper', diff --git a/src/index.ts b/src/index.ts index 6ef3ad05..28648c88 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,6 +14,10 @@ export { FacebookMessengerShareButton } from './FacebookMessengerShareButton'; export { buildFacebookMessengerShareUrl } from './FacebookMessengerShareButton'; export { FacebookShareButton } from './FacebookShareButton'; export { buildFacebookShareUrl } from './FacebookShareButton'; +export { FarcasterIcon } from './FarcasterIcon'; +export { FarcasterLegacyIcon } from './FarcasterLegacyIcon'; +export { FarcasterShareButton } from './FarcasterShareButton'; +export { buildFarcasterShareUrl } from './FarcasterShareButton'; export { GabIcon } from './GabIcon'; export { GabLegacyIcon } from './GabLegacyIcon'; export { GabShareButton } from './GabShareButton'; diff --git a/test/NetworkShareButtons.test.tsx b/test/NetworkShareButtons.test.tsx index 646feed9..bc55e0d5 100644 --- a/test/NetworkShareButtons.test.tsx +++ b/test/NetworkShareButtons.test.tsx @@ -4,6 +4,7 @@ import { describe, expect, it, vi } from 'vitest'; import { BlueskyShareButton } from '../src/BlueskyShareButton'; import { FacebookShareButton } from '../src/FacebookShareButton'; +import { FarcasterShareButton } from '../src/FarcasterShareButton'; import { GabShareButton } from '../src/GabShareButton'; import { HatenaShareButton } from '../src/HatenaShareButton'; import { InstapaperShareButton } from '../src/InstapaperShareButton'; @@ -46,6 +47,17 @@ const shareButtonCases: ShareButtonCase[] = [ url: 'https://example.com', }, }, + { + children: 'Share on Farcaster', + Component: FarcasterShareButton, + expectedUrl: + 'https://farcaster.xyz/~/compose?text=Example%20title&embeds[]=https%3A%2F%2Fexample.com&channelKey=farcaster', + props: { + channelKey: 'farcaster', + text: 'Example title', + url: 'https://example.com', + }, + }, { children: 'Share on Gab', Component: GabShareButton, diff --git a/test/ShareHelpers.test.ts b/test/ShareHelpers.test.ts index 6c6fec6d..a634d8cf 100644 --- a/test/ShareHelpers.test.ts +++ b/test/ShareHelpers.test.ts @@ -3,6 +3,7 @@ import { describe, expect, it, vi } from 'vitest'; import { buildBlueskyShareUrl, buildEmailShareUrl, + buildFarcasterShareUrl, buildFacebookShareUrl, buildLinkedinShareUrl, buildViberShareUrl, @@ -43,6 +44,11 @@ describe('share helpers', () => { () => buildBlueskyShareUrl('https://example.com', { text: 'Hello' }), 'https://bsky.app/intent/compose?text=Hello%20https%3A%2F%2Fexample.com', ], + [ + 'Farcaster', + () => buildFarcasterShareUrl('https://example.com', { parentCastHash: '0xabc', text: 'Hi' }), + 'https://farcaster.xyz/~/compose?text=Hi&embeds[]=https%3A%2F%2Fexample.com&parentCastHash=0xabc', + ], [ 'Email', () => buildEmailShareUrl('https://example.com', { body: 'Hello' }), From 07af86bbdcadbecffc78087490fa0d75bb00d92f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Klaus=20Nyga=CC=8Ard?= Date: Mon, 12 Oct 2026 14:54:58 +0300 Subject: [PATCH 7/7] docs(readme): show how to build custom networks and async share links Two recurring requests are not covered by the share buttons: sharing to a network the library has no button for, and preparing the share URL (such as a short link) after the click. Document how to do both with openShareWindow and the build*ShareUrl helpers. Related to #150, #216, #256 and #336. Co-Authored-By: Claude Sonnet 5.5 --- README.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/README.md b/README.md index 9d11ed64..1004ccf5 100644 --- a/README.md +++ b/README.md @@ -843,6 +843,41 @@ openShareWindow(url, { width: 550, height: 400 }); openShareWindow(url, { newTab: true }); ``` +### Custom networks + +For a network the library does not cover, build its share URL yourself and open it with `openShareWindow`: + +```jsx +import { openShareWindow } from "react-share"; + +; +``` + +### Preparing the share data on click + +Share buttons build their link from props at click time. If you need to fetch something first, such as a short link or a token, open the popup immediately so popup blockers allow it, then load the URL once it is ready: + +```jsx +import { buildXShareUrl, openShareWindow } from "react-share"; + +async function share() { + const popup = openShareWindow("about:blank"); + + try { + const shortUrl = await createShortLink(shareUrl); + + popup?.location.replace(buildXShareUrl(shortUrl, { text: "Read this next" })); + } catch { + popup?.close(); + } +} +``` + ## Accessibility - Icon-only buttons need an accessible name. Pass `aria-label` or `aria-labelledby` on the share button.