PopperJS to display the sharing interface at the location where the user selected the text. We’ll also tie this up into a reusable hook in order to access it across many parts of a project. To do this we’ll break the feature into 2 parts:
useShareToSocial
that will do all the heavy lifting of building the tweet and sharing it to Twitter.First things first, let’s get text highlight selection in place. To do this we need to reach for the HTML spec’s Selection API. As well as getting the selected text we want to also get the coordinates to pass through to PopperJS.
const selectionChange = React.useCallback(() => {
const selection = window.getSelection();
if (selection) {
const selectedText = selection.toString();
if (selection.isCollapsed || !selectedText.length) {
setSelectedText('');
}
setSelectedText(selectedText);
}
const virtualReference: NullableVirtualElement = {
getBoundingClientRect: () => {
return window
.getSelection()
?.getRangeAt(0)
.getBoundingClientRect() as DOMRect | null;
}
};
setReferenceElement(virtualReference);
}, [setSelectedText]);
In the above we are also resetting our text selection when we have de-selected text on the screen.
While we’re at it, let’s grab the URL of the page we’re on to include it in the tweet. To do that we can call window.location.href
. Then we pass that to the encodeURIComponent()
method, which converts it to UTF-8 encoding to put in our URL bar.
const pageUrl = encodeURIComponent(window.location.href)
We can improve the accessibility of this feature by taking advantage of a browser setting called Caret Browsing. Available in Chrome, Firefox and Edge through the F7 keyboard shortcut - Caret Browsing lets users navigate the page and select text with directional keys like they are in a MS Word or Google document. We will also trigger the function to fire on text selection change.
Using Caret Browsing to select text with keyboard and share to Twitter
PopperJS provides us with a virtualReference
of the selected text which we can use to position the popover. We’re going to use the coordinates Popper provides to set our reference element by passing it up to the parent to be set as the ref
.
return () => ({
width: 0,
height: 0,
top: y,
right: x,
bottom: y,
left: x,
})
Subscribe for updates
We send a newsletter from time to time when we publish new resources, articles, and open source projects on the topics of software design and engineering, design systems, and process & practice.
Now that we have the main pieces, we can put them together to build that tweet!
We need to ensure that the selected text fits within Twitter’s tweet limit of 280 characters. By including the truncated text in the popover’s preview, we’ll avoid any last-minute surprises that the user might encounter when they go to share their tweet.
const buildTweet = ({
pageUrl,
tweetContent,
}: {
tweetContent: string | undefined
pageUrl: string
}) => {
if (tweetContent !== undefined) {
const characterTotal = tweetContent.length + pageUrl.length
if (characterTotal + 19 < 280) {
return encodeURIComponent(`"${tweetContent}" – `)
}
const amountToTrim = 280 - (pageUrl.length + 19)
return encodeURIComponent(`"${tweetContent.slice(0, amountToTrim)}…" – `)
}
return ''
}
export const buildTargetLink = ({ quote }: ShareQuoteProps) => {
const pageUrl = encodeURIComponent(window.location.href)
const twitterLink = (tweetContent: string) =>
`https://twitter.com/intent/tweet?text=${tweetContent}&url=${pageUrl}&via=thethinkmill`
const tweet = buildTweet({
pageUrl,
tweetContent: quote,
})
return twitterLink(tweet)
}
Let’s go through some of the numbers in the snippet above:
characterTotal
to reflect the number of characters we wrap the text selection with "" – via @thethinkmill
. If we have too many characters with the wrapping text included, we move to the alternate option in the function.encodeURIComponent()
to make it appropriate to pass to the URL.Now that we have our page URL and our built tweet at the right length we can use Twitter’s intent/tweet URL to fire off the tweet. The function we have above builds this URL in this way:
https://twitter.com/intent/tweet
– our main starting pointtext=${tweetContent}
– the built quote at the correct length&url=${pageUrl}
– the page URL we are on taken from window.location&via=thethinkmill
– adding the intended account to be tagged when someone shares from an articleNow that we have done all the heavy lifting of selecting the text and building our tweet. All that’s left is to wrap it all up and be ready to ship this feature. To do this we will make a component to wrap our content and trigger the share function. Below is the whole component and then we will go into it more in-depth.
export const ShareableText = ({
wrapperId,
children,
}: {
wrapperId: string
children: React.ReactElement
}) => {
const { textElementRef, textElementSelectionChangeHandler, popupElement } =
useShareableText({ wrapperId })
const childrenWithProps = React.Children.map(children, (child) =>
React.cloneElement(child, {
id: wrapperId,
ref: textElementRef,
onMouseUp: textElementSelectionChangeHandler,
})
)
return (
<React.Fragment>
{childrenWithProps}
{popupElement}
</React.Fragment>
)
}
We are pulling in the pieces we need out of the useShareableText hook. We grab the selection handler function and the relevant parts used by PopperJS.
const { textElementRef, textElementSelectionChangeHandler, popupElement } =
useShareableText({ wrapperId })
Then we use the cloneElement method from React and attach the required props needed.
const childrenWithProps = React.Children.map(children, (child) =>
React.cloneElement(child, {
id: wrapperId,
ref: textElementRef,
onMouseUp: textElementSelectionChangeHandler,
})
)
Now we can return a fragment with the props for the wrapper and the PopperJS element.
return(
{childrenWithProps}
{popupElement}
);
The result is a convenient wrapper around our content, which is the final piece of the share-to-Twitter puzzle. That gets placed around our content and we’re done.
return <ShareableText wrapperId="shareable-wrapper">{content}</ShareableText>
We used the selection API to get selected text from the page. We took advantage of PopperJS’ virtualReference
function to know where on the page our selected text was located. And finally, we built a neat function that creates the tweet with some wrapping text and fits it within Twitter‘s 280 character limit. The end result is a seamless sharing experience you can use across mouse, touch, and keyboard inputs to let readers share their favourite excerpt of an article without having to copy, paste and edit.
You can check out the CodeSandbox here, and the working example page here. If you have more questions about the implementation you can find me on Twitter.
To learn more about the tools and APIs used in this tutorial check out the links below.
article
· 11 min readarticle
· 16 min readarticle
· 12 min readarticle
· 17 min readarticle
·talk
article
· 11 min readarticle
· 16 min readarticle
· 12 min readarticle
· 17 min readarticle
·article
· 11 min readarticle
· 16 min readarticle
· 12 min readarticle
· 17 min readarticle
·Have a chat with one of our co-founders, Jed or Boris, about how Thinkmill can support your organisation’s software ambitions.
Contact us