-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(app): add a share button ballon for selected text in articles
- Loading branch information
Kohei Asai
committed
Apr 11, 2021
1 parent
15374c0
commit cae3259
Showing
6 changed files
with
774 additions
and
402 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { Story, Meta } from "@storybook/react"; | ||
import * as React from "react"; | ||
import { | ||
TextSelectionShareBalloon, | ||
TextSelectionShareBalloonProps, | ||
} from "./text-selection-share-balloon"; | ||
|
||
export default { | ||
title: "Components/TextSelectionShareBalloon", | ||
component: TextSelectionShareBalloon, | ||
argTypes: {}, | ||
args: { | ||
shareUrl: | ||
"https://www.kohei.dev/posts/for-engineers-who-have-overseas-ambition?hl=ja-JP", | ||
}, | ||
} as Meta; | ||
|
||
export const Example: Story<TextSelectionShareBalloonProps> = (props) => ( | ||
<> | ||
<p> | ||
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod | ||
tempor incididunt ut labore et dolore magna aliqua. Donec ultrices | ||
tincidunt arcu non sodales. Nulla facilisi nullam vehicula ipsum a arcu | ||
cursus. Eu mi bibendum neque egestas congue. Turpis tincidunt id aliquet | ||
risus feugiat in ante. Sed viverra ipsum nunc aliquet bibendum enim | ||
facilisis. Odio ut sem nulla pharetra diam sit amet. Odio ut sem nulla | ||
pharetra diam sit amet nisl. Id cursus metus aliquam eleifend mi. | ||
Venenatis tellus in metus vulputate eu scelerisque felis imperdiet. Etiam | ||
erat velit scelerisque in dictum non. Magna ac placerat vestibulum lectus | ||
mauris ultrices eros in. Nunc aliquet bibendum enim facilisis gravida. | ||
Imperdiet dui accumsan sit amet nulla facilisi morbi. Pharetra magna ac | ||
placerat vestibulum lectus mauris ultrices eros. Velit aliquet sagittis id | ||
consectetur purus ut faucibus. Nisi lacus sed viverra tellus in. Sit amet | ||
nisl purus in mollis nunc. | ||
</p> | ||
|
||
<p> | ||
Arcu non sodales neque sodales ut etiam sit amet nisl. Egestas sed tempus | ||
urna et pharetra pharetra massa massa ultricies. Amet facilisis magna | ||
etiam tempor orci eu lobortis elementum. Eros donec ac odio tempor orci | ||
dapibus ultrices. A diam maecenas sed enim ut sem. Aliquet bibendum enim | ||
facilisis gravida neque. Sed cras ornare arcu dui. Bibendum neque egestas | ||
congue quisque egestas diam in. Fermentum posuere urna nec tincidunt | ||
praesent semper. Felis bibendum ut tristique et egestas quis. In arcu | ||
cursus euismod quis viverra nibh cras. Fringilla urna porttitor rhoncus | ||
dolor purus non enim. | ||
</p> | ||
|
||
<p> | ||
Sit amet consectetur adipiscing elit. Consectetur purus ut faucibus | ||
pulvinar elementum integer. Tincidunt tortor aliquam nulla facilisi. Mi | ||
bibendum neque egestas congue quisque egestas. Duis at consectetur lorem | ||
donec massa sapien. Feugiat pretium nibh ipsum consequat nisl. Purus | ||
semper eget duis at tellus at urna. Volutpat maecenas volutpat blandit | ||
aliquam etiam erat velit scelerisque. Eget sit amet tellus cras adipiscing | ||
enim eu. Facilisi etiam dignissim diam quis enim lobortis scelerisque | ||
fermentum. Ante in nibh mauris cursus mattis molestie. Vel turpis nunc | ||
eget lorem dolor sed viverra. Habitant morbi tristique senectus et. Arcu | ||
ac tortor dignissim convallis aenean et tortor at risus. Mauris cursus | ||
mattis molestie a iaculis at erat. Risus at ultrices mi tempus imperdiet | ||
nulla malesuada. Eget duis at tellus at urna condimentum mattis | ||
pellentesque. | ||
</p> | ||
|
||
<p> | ||
Turpis in eu mi bibendum neque. Sed tempus urna et pharetra pharetra massa | ||
massa. Fames ac turpis egestas sed tempus urna et. Risus pretium quam | ||
vulputate dignissim suspendisse in est. Leo urna molestie at elementum eu. | ||
Pellentesque sit amet porttitor eget dolor. Odio eu feugiat pretium nibh | ||
ipsum consequat nisl vel pretium. Porttitor eget dolor morbi non arcu. | ||
Donec et odio pellentesque diam volutpat commodo sed egestas. Adipiscing | ||
elit pellentesque habitant morbi tristique senectus. Elementum nisi quis | ||
eleifend quam adipiscing. Amet mauris commodo quis imperdiet. Condimentum | ||
id venenatis a condimentum vitae. Malesuada bibendum arcu vitae elementum | ||
curabitur vitae nunc sed. Vitae et leo duis ut diam quam nulla porttitor. | ||
Et malesuada fames ac turpis egestas integer eget. Amet nulla facilisi | ||
morbi tempus iaculis urna id volutpat. | ||
</p> | ||
|
||
<p> | ||
Ultrices sagittis orci a scelerisque purus semper eget duis at. Ut sem | ||
nulla pharetra diam sit amet nisl suscipit. Vitae nunc sed velit dignissim | ||
sodales ut. Eget duis at tellus at urna. Massa ultricies mi quis hendrerit | ||
dolor magna eget est. Eros donec ac odio tempor orci. Massa tincidunt nunc | ||
pulvinar sapien et ligula ullamcorper malesuada. Ullamcorper morbi | ||
tincidunt ornare massa. Tortor dignissim convallis aenean et tortor at. | ||
Suspendisse in est ante in nibh mauris cursus mattis molestie. Ut | ||
tristique et egestas quis ipsum suspendisse ultrices. Venenatis tellus in | ||
metus vulputate eu. Vitae purus faucibus ornare suspendisse sed nisi. | ||
Lectus quam id leo in vitae turpis massa. Pellentesque habitant morbi | ||
tristique senectus et netus et malesuada. Commodo quis imperdiet massa | ||
tincidunt. | ||
</p> | ||
|
||
<TextSelectionShareBalloon {...props} /> | ||
</> | ||
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
import { css } from "@linaria/core"; | ||
import * as React from "react"; | ||
import { Facebook, Linkedin, Twitter } from "react-feather"; | ||
import { | ||
useFacebookShare, | ||
useLinkedinShare, | ||
useTwitterShare, | ||
} from "../services/social-share"; | ||
import { ButtonListBalloon, ButtonListBaloonItem } from "./button-list-balloon"; | ||
|
||
export interface TextSelectionShareBalloonProps extends React.Attributes { | ||
/** | ||
* | ||
*/ | ||
shareUrl: string; | ||
/** | ||
* A function determine if the balloon should appear by `anchorNode` and `focusNode`. Return `true` to show the balloon. | ||
* | ||
* It always returns `true` if you don't pass a function. | ||
*/ | ||
disableWhen?: (anchorNode: Node, focusNode: Node) => boolean; | ||
/** | ||
* An event listener that gets called whenever you click the button in the ballon. | ||
*/ | ||
onButtonClick?: ( | ||
e: React.MouseEvent, | ||
details: { type: string; selection: string } | ||
) => void; | ||
className?: string; | ||
style?: React.CSSProperties; | ||
} | ||
|
||
export const TextSelectionShareBalloon: React.VFC<TextSelectionShareBalloonProps> = ({ | ||
shareUrl, | ||
disableWhen = () => false, | ||
onButtonClick = () => {}, | ||
className, | ||
...props | ||
}) => { | ||
const [rect, setRect] = React.useState<DOMRect | null>(null); | ||
const [text, setText] = React.useState(""); | ||
const [initialScrollY, setInitialScrollY] = React.useState( | ||
globalThis.window?.scrollY ?? 0 | ||
); | ||
const [scrollY, setScrollY] = React.useState(globalThis.window?.scrollY ?? 0); | ||
const shareOnTwitter = useTwitterShare({ url: shareUrl, text }); | ||
const shareOnFacebook = useFacebookShare({ url: shareUrl, text }); | ||
const shareOnLinkedin = useLinkedinShare({ url: shareUrl }); | ||
|
||
React.useEffect(() => { | ||
const onScroll: EventListener = () => { | ||
setScrollY(globalThis.window.scrollY); | ||
}; | ||
|
||
globalThis.document.addEventListener("scroll", onScroll); | ||
|
||
return () => { | ||
globalThis.document.removeEventListener("scroll", onScroll); | ||
}; | ||
}, []); | ||
|
||
React.useEffect(() => { | ||
const onSelectionChange = () => { | ||
const selection = globalThis.document.getSelection(); | ||
|
||
if ( | ||
selection && | ||
selection.type === "Range" && | ||
selection.rangeCount >= 1 && | ||
!disableWhen(selection.anchorNode!, selection.focusNode!) | ||
) { | ||
const firstRect = selection.getRangeAt(0).getClientRects().item(0); | ||
|
||
setRect(firstRect); | ||
setText(selection.toString().replaceAll(/\s+/g, " ")); | ||
setInitialScrollY(globalThis.window.scrollY); | ||
|
||
return; | ||
} | ||
|
||
setRect(null); | ||
setText(""); | ||
setInitialScrollY(0); | ||
}; | ||
|
||
globalThis.document.addEventListener("selectionchange", onSelectionChange); | ||
|
||
return () => { | ||
globalThis.document.removeEventListener( | ||
"selectionchange", | ||
onSelectionChange | ||
); | ||
}; | ||
}, [disableWhen]); | ||
|
||
return ( | ||
<ButtonListBalloon | ||
className={css` | ||
display: none; | ||
position: fixed; | ||
@media (hover: hover) and (pointer: fine) { | ||
display: block; | ||
} | ||
`} | ||
style={ | ||
rect | ||
? { | ||
top: rect.y - 64 - scrollY + initialScrollY, | ||
left: rect.x, | ||
opacity: 1, | ||
visibility: "visible", | ||
} | ||
: { opacity: 0, visibility: "hidden" } | ||
} | ||
{...props} | ||
> | ||
<ButtonListBaloonItem | ||
icon={<Twitter />} | ||
onClick={(e) => { | ||
onButtonClick(e, { type: "twitter", selection: text }); | ||
|
||
shareOnTwitter(); | ||
}} | ||
> | ||
Tweet | ||
</ButtonListBaloonItem> | ||
|
||
<ButtonListBaloonItem | ||
icon={<Linkedin />} | ||
onClick={(e) => { | ||
onButtonClick(e, { type: "linkedin", selection: text }); | ||
|
||
shareOnLinkedin(); | ||
}} | ||
> | ||
Share | ||
</ButtonListBaloonItem> | ||
|
||
<ButtonListBaloonItem | ||
icon={<Facebook />} | ||
onClick={(e) => { | ||
onButtonClick(e, { type: "facebook", selection: text }); | ||
|
||
shareOnFacebook(); | ||
}} | ||
> | ||
Share | ||
</ButtonListBaloonItem> | ||
</ButtonListBalloon> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.