├── .eslintrc.js
├── .gitignore
├── .prettierrc
├── .travis.yml
├── .vscode
└── launch.json
├── LICENSE
├── README.md
├── example
├── index.html
├── index.jsx
└── styles.css
├── jest.config.js
├── package.json
├── release
├── __tests__
│ ├── get-url.test.d.ts
│ └── share-content.test.d.ts
├── get-url.d.ts
├── index.d.ts
├── index.js
├── index.js.map
├── index.mjs
├── index.mjs.map
├── index.umd.js
├── index.umd.js.map
└── share-content.d.ts
├── src
├── __tests__
│ ├── get-url.test.ts
│ └── share-content.test.ts
├── get-url.ts
├── index.ts
├── share-content.ts
└── typing.d.ts
├── tsconfig.json
└── yarn.lock
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | env: {
4 | browser: true,
5 | es6: true
6 | },
7 | extends: [
8 | 'plugin:react/recommended',
9 | 'plugin:@typescript-eslint/recommended',
10 | 'prettier/@typescript-eslint'
11 | ],
12 | parserOptions: {
13 | ecmaFeatures: {
14 | jsx: true
15 | },
16 | ecmaVersion: 2018,
17 | sourceType: 'module'
18 | },
19 | plugins: ['react', 'react-hooks'],
20 | settings: {
21 | react: {
22 | version: 'detect'
23 | }
24 | }
25 | };
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
23 | .cache
24 | .rts2_cache_*
25 |
26 | dist
27 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | trailingComma: "es5"
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "stable"
4 | cache:
5 | directories:
6 | - node_modules
7 | script:
8 | - npm test
9 | - npm run build
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Debug Jest Tests",
6 | "type": "node",
7 | "request": "launch",
8 | "runtimeArgs": [
9 | "--inspect-brk",
10 | "${workspaceRoot}/node_modules/jest/bin/jest.js",
11 | "--runInBand"
12 | ],
13 | "console": "integratedTerminal",
14 | "internalConsoleOptions": "neverOpen",
15 | "port": 9229
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Agney Menon
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # useWebShare
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | A custom react hook for triggering the native [web share](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share) dialog in [supported browsers](https://caniuse.com/#feat=web-share)
14 |
15 | [Demo](https://agneym.github.io/react-use-web-share/)
16 |
17 | ## Usage
18 |
19 | ```
20 | npm install react-use-web-share
21 | ```
22 |
23 | ```javascript
24 | const { loading, isSupported, share } = useWebShare();
25 |
26 | function onClick() {
27 | share();
28 | }
29 | ```
30 |
31 | See [example](https://github.com/BoyWithSilverWings/react-use-web-share/tree/master/example) directory for full example.
32 |
33 | ### Parameters
34 |
35 | | Parameter | description | default | required |
36 | | :-------: | :---------------------------------------------------: | :------: | :------: |
37 | | onSuccess | called on successfully sharing the content | () => {} | false |
38 | | onError | called when caught error from navigator share content | () => {} | false |
39 |
40 | ### Returns
41 |
42 | | Name | Type | Description |
43 | | :---------: | :------: | :------------------------------------------------------------------------------------------: |
44 | | loading | boolean | Loading state |
45 | | isSupported | boolean | Detects whether the feature is supported in user's browser. Can be used to show the fallback |
46 | | share | function | can be called to trigger the native share popup |
47 |
48 | ### `share`
49 |
50 | This is the function that triggers the native share dialog in browser.
51 |
52 | This takes an object as argument.
53 |
54 | | Name | description | default |
55 | | :---: | :------------------: | :---------------------------------------------: |
56 | | title | title of shared item | `document.title` |
57 | | text | text of shared item |
58 | | url | url to be shared | canonical url if present, otherwise current url |
59 |
60 | ## Contributing
61 |
62 | 1. Install dependencies
63 |
64 | ```
65 | npm install
66 | ```
67 |
68 | 2. Run dev for lib
69 |
70 | ```
71 | npm run dev
72 | ```
73 |
74 | 3. Run demo
75 |
76 | ```
77 | npm start
78 | ```
79 |
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | useWebShare example
8 |
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/example/index.jsx:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 | import ReactDOM from "react-dom";
3 |
4 | import "./styles.css";
5 | import useWebShare from "../src";
6 |
7 | function submitForm(target, actionFn) {
8 | const title = target.title.value;
9 | const text = target.text.value;
10 | const url = target.url.value;
11 | actionFn({
12 | title, text, url
13 | });
14 | }
15 |
16 | function App() {
17 | const { isSupported, loading, share } = useWebShare();
18 | return (
19 |
20 | { !loading && isSupported ? (
21 |
22 |
Let's Share
23 |
38 |
39 | ) : (
40 |
41 |
42 | Your browser does not support
43 | Web Share API
44 |
45 |
The Sadness :(
46 |
47 | )}
48 |
49 | )
50 | }
51 |
52 | ReactDOM.render(, document.getElementById("root"));
53 |
--------------------------------------------------------------------------------
/example/styles.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: system;
3 | font-style: normal;
4 | font-weight: 300;
5 | src: local(".SFNSText-Light"),
6 | local(".HelveticaNeueDeskInterface-Light"), local(".LucidaGrandeUI"),
7 | local("Ubuntu Light"), local("Segoe UI Light"), local("Roboto-Light"),
8 | local("DroidSans"), local("Tahoma");
9 | }
10 |
11 | body {
12 | font-family: "system";
13 | margin: 0;
14 | }
15 |
16 | .App {
17 | min-height: calc(100vh - 4rem);
18 | }
19 |
20 | .container {
21 | width: 75%;
22 | margin: 0 auto;
23 | padding-top: 5rem;
24 | text-align: center;
25 | }
26 |
27 | .container form {
28 | margin: 2rem 0;
29 | }
30 |
31 | .container fieldset {
32 | border: none;
33 | text-align: left;
34 | }
35 |
36 | .container input {
37 | width: 100%;
38 | padding: 0.5rem;
39 | }
40 |
41 | .container button {
42 | padding: 0.8rem 2rem;
43 | margin: 0 auto;
44 | display: block;
45 | background-color: #1B98E0;
46 | box-shadow: none;
47 | color: #ffffff;
48 | border: none;
49 | cursor: pointer;
50 | }
51 |
52 | .footer {
53 | text-align: right;
54 | padding: 0 1rem;
55 | }
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.tsx?$": "ts-jest",
4 | },
5 | rootDir: "src",
6 | };
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-use-web-share",
3 | "version": "1.0.2",
4 | "main": "release/index.js",
5 | "umd:main": "release/index.umd.js",
6 | "source": "src/index.ts",
7 | "keywords": [
8 | "react",
9 | "hook",
10 | "web share",
11 | "share"
12 | ],
13 | "scripts": {
14 | "start": "parcel example/index.html",
15 | "build": "microbundle",
16 | "dev": "microbundle watch",
17 | "prettier": "prettier --write {src,example}/**/*.{js,ts,jsx,tsx}",
18 | "test": "jest",
19 | "remove-dist": "rimraf dist",
20 | "build-example": "parcel build example/index.html --public-url ./",
21 | "deploy-example": "gh-pages -d dist",
22 | "update-example": "npm run remove-dist && npm run build-example && npm run deploy-example",
23 | "eslint": "eslint ./src/**/*.{ts,tsx}"
24 | },
25 | "description": "A custom react hook for triggering the native web share dialog",
26 | "repository": "github:agneym/react-use-web-share",
27 | "homepage": "https://agneym.github.io/react-use-web-share/",
28 | "author": "Agney ",
29 | "bugs": {
30 | "url": "https://github.com/agneym/react-use-web-share/issues"
31 | },
32 | "license": "MIT",
33 | "devDependencies": {
34 | "@types/jest": "^25.1.3",
35 | "@types/react": "16.9.23",
36 | "@types/react-dom": "16.9.5",
37 | "@typescript-eslint/eslint-plugin": "^2.21.0",
38 | "@typescript-eslint/parser": "^2.21.0",
39 | "eslint": "^6.8.0",
40 | "eslint-config-prettier": "6.10.0",
41 | "eslint-plugin-react": "^7.18.3",
42 | "eslint-plugin-react-hooks": "^2.4.0",
43 | "gh-pages": "^2.2.0",
44 | "husky": "^4.2.3",
45 | "jest": "25.1.0",
46 | "jest-fetch-mock": "3.0.1",
47 | "microbundle": "^0.11.0",
48 | "parcel-bundler": "1.12.4",
49 | "prettier": "1.19.1",
50 | "pretty-quick": "^2.0.1",
51 | "react": "16.12.0",
52 | "react-dom": "16.12.0",
53 | "rimraf": "^3.0.2",
54 | "ts-jest": "25.2.1"
55 | },
56 | "peerDependencies": {
57 | "react": ">= 16.8.0"
58 | },
59 | "dependencies": {
60 | "@testing-library/react": "^9.4.1"
61 | },
62 | "husky": {
63 | "hooks": {
64 | "pre-commit": "pretty-quick --staged"
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/release/__tests__/get-url.test.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/release/__tests__/share-content.test.d.ts:
--------------------------------------------------------------------------------
1 | export {};
2 |
--------------------------------------------------------------------------------
/release/get-url.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the URL to be shared.
3 | * If the site uses canonical URL, then use that URL otherwise the current URL
4 | * @param url URL that might be passed on by the user
5 | */
6 | declare function getUrl(url?: string): string;
7 | export default getUrl;
8 |
--------------------------------------------------------------------------------
/release/index.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Use native web share dialog when available
3 | * @param onSuccess function called on successfully sharing content
4 | * @param onError callback function called on error sharing content
5 | * @example
6 | * const { isSupported, isLoading, share } = useWebShare(successFn, errorFn);
7 | */
8 | declare function useWebShare(onSuccess?: () => void, onError?: () => void): {
9 | loading: boolean;
10 | isSupported: boolean;
11 | share: (config: Partial) => void;
12 | };
13 | export default useWebShare;
14 |
--------------------------------------------------------------------------------
/release/index.js:
--------------------------------------------------------------------------------
1 | var t = require("react");
2 | function e(t, e) {
3 | return function(n) {
4 | var r = (function(t) {
5 | if (t) return t;
6 | var e = document.querySelector("link[rel=canonical]");
7 | return e ? e.href : window.location.href;
8 | })(n.url),
9 | i = n.title || document.title;
10 | navigator
11 | .share({ text: n.text, title: i, url: r })
12 | .then(t)
13 | .catch(e);
14 | };
15 | }
16 | module.exports = function(n, r) {
17 | void 0 === n && (n = function() {}), void 0 === r && (r = function() {});
18 | var i = t.useState(!0),
19 | o = i[0],
20 | u = i[1],
21 | a = t.useState(!1),
22 | c = a[0],
23 | f = a[1];
24 | return (
25 | t.useEffect(
26 | function() {
27 | navigator.share ? f(!0) : f(!1), u(!1);
28 | },
29 | [n, r]
30 | ),
31 | { loading: o, isSupported: c, share: e(n, r) }
32 | );
33 | };
34 | //# sourceMappingURL=index.js.map
35 |
--------------------------------------------------------------------------------
/release/index.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.js","sources":["../src/share-content.ts","../src/get-url.ts","../src/index.ts"],"sourcesContent":["import getUrl from \"./get-url\";\n\n/**\n * Trigger native share popup\n */\nfunction shareContent(onSuccess: () => void, onError: () => void) {\n return function (config: Partial) {\n const url = getUrl(config.url);\n const title = config.title || document.title;\n const text = config.text;\n navigator\n .share({ text, title, url })\n .then(onSuccess)\n .catch(onError);\n };\n}\n\nexport default shareContent;","/**\n * Get the URL to be shared.\n * If the site uses canonical URL, then use that URL otherwise the current URL\n * @param url URL that might be passed on by the user\n */\nfunction getUrl(url?: string): string {\n if(!!url) {\n return url;\n } else {\n const canonicalEl = document.querySelector('link[rel=canonical]') as HTMLLinkElement;\n return canonicalEl ? canonicalEl.href : window.location.href;\n }\n}\n\nexport default getUrl;","import { useState, useEffect } from 'react';\n\nimport shareContent from \"./share-content\";\n\n/**\n * Use native web share dialog when available\n * @param onSuccess function called on successfully sharing content\n * @param onError callback function called on error sharing content\n * @example \n * const { isSupported, isLoading, share } = useWebShare(successFn, errorFn);\n */\nfunction useWebShare(onSuccess = () => { }, onError = () => { }) {\n const [loading, setLoading] = useState(true);\n const [isSupported, setSupport] = useState(false);\n\n useEffect(\n () => {\n if (!!navigator.share) {\n setSupport(true);\n } else {\n setSupport(false);\n }\n setLoading(false);\n },\n [onSuccess, onError]\n );\n\n return {\n loading,\n isSupported,\n share: shareContent(onSuccess, onError),\n };\n}\n\nexport default useWebShare;\n"],"names":["shareContent","onSuccess","onError","config","url","canonicalEl","document","querySelector","href","window","location","getUrl","title","navigator","share","text","then","catch","useState","useEffect","setSupport","setLoading","loading","isSupported"],"mappings":"uBAKA,SAASA,EAAaC,EAAuBC,UACpC,SAAUC,OACTC,ECFV,SAAgBA,MACTA,SACIA,MAEDC,EAAcC,SAASC,cAAc,8BACpCF,EAAcA,EAAYG,KAAOC,OAAOC,SAASF,KDH5CG,CAAOR,EAAOC,KACpBQ,EAAQT,EAAOS,OAASN,SAASM,MAEvCC,UACGC,MAAM,MAFIX,EAAOY,WAEHH,MAAOR,IACrBY,KAAKf,GACLgB,MAAMf,mBEFb,SAAqBD,EAAuBC,mEACZgB,YAAS,mBACLA,YAAS,wBAE3CC,uBAEUN,UAAUC,MACdM,GAAW,GAEXA,GAAW,GAEbC,GAAW,IAEb,CAACpB,EAAWC,IAGP,SACLoB,cACAC,EACAT,MAAOd,EAAaC,EAAWC"}
--------------------------------------------------------------------------------
/release/index.mjs:
--------------------------------------------------------------------------------
1 | import { useState as t, useEffect as n } from "react";
2 | function r(t, n) {
3 | return function(r) {
4 | var e = (function(t) {
5 | if (t) return t;
6 | var n = document.querySelector("link[rel=canonical]");
7 | return n ? n.href : window.location.href;
8 | })(r.url),
9 | o = r.title || document.title;
10 | navigator
11 | .share({ text: r.text, title: o, url: e })
12 | .then(t)
13 | .catch(n);
14 | };
15 | }
16 | export default function(e, o) {
17 | void 0 === e && (e = function() {}), void 0 === o && (o = function() {});
18 | var i = t(!0),
19 | u = i[0],
20 | a = i[1],
21 | c = t(!1),
22 | f = c[0],
23 | l = c[1];
24 | return (
25 | n(
26 | function() {
27 | navigator.share ? l(!0) : l(!1), a(!1);
28 | },
29 | [e, o]
30 | ),
31 | { loading: u, isSupported: f, share: r(e, o) }
32 | );
33 | }
34 | //# sourceMappingURL=index.mjs.map
35 |
--------------------------------------------------------------------------------
/release/index.mjs.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.mjs","sources":["../src/share-content.ts","../src/get-url.ts","../src/index.ts"],"sourcesContent":["import getUrl from \"./get-url\";\n\n/**\n * Trigger native share popup\n */\nfunction shareContent(onSuccess: () => void, onError: () => void) {\n return function (config: Partial) {\n const url = getUrl(config.url);\n const title = config.title || document.title;\n const text = config.text;\n navigator\n .share({ text, title, url })\n .then(onSuccess)\n .catch(onError);\n };\n}\n\nexport default shareContent;","/**\n * Get the URL to be shared.\n * If the site uses canonical URL, then use that URL otherwise the current URL\n * @param url URL that might be passed on by the user\n */\nfunction getUrl(url?: string): string {\n if(!!url) {\n return url;\n } else {\n const canonicalEl = document.querySelector('link[rel=canonical]') as HTMLLinkElement;\n return canonicalEl ? canonicalEl.href : window.location.href;\n }\n}\n\nexport default getUrl;","import { useState, useEffect } from 'react';\n\nimport shareContent from \"./share-content\";\n\n/**\n * Use native web share dialog when available\n * @param onSuccess function called on successfully sharing content\n * @param onError callback function called on error sharing content\n * @example \n * const { isSupported, isLoading, share } = useWebShare(successFn, errorFn);\n */\nfunction useWebShare(onSuccess = () => { }, onError = () => { }) {\n const [loading, setLoading] = useState(true);\n const [isSupported, setSupport] = useState(false);\n\n useEffect(\n () => {\n if (!!navigator.share) {\n setSupport(true);\n } else {\n setSupport(false);\n }\n setLoading(false);\n },\n [onSuccess, onError]\n );\n\n return {\n loading,\n isSupported,\n share: shareContent(onSuccess, onError),\n };\n}\n\nexport default useWebShare;\n"],"names":["shareContent","onSuccess","onError","config","url","canonicalEl","document","querySelector","href","window","location","getUrl","title","navigator","share","text","then","catch","useState","useEffect","setSupport","setLoading","loading","isSupported"],"mappings":"gDAKA,SAASA,EAAaC,EAAuBC,UACpC,SAAUC,OACTC,ECFV,SAAgBA,MACTA,SACIA,MAEDC,EAAcC,SAASC,cAAc,8BACpCF,EAAcA,EAAYG,KAAOC,OAAOC,SAASF,KDH5CG,CAAOR,EAAOC,KACpBQ,EAAQT,EAAOS,OAASN,SAASM,MAEvCC,UACGC,MAAM,MAFIX,EAAOY,WAEHH,MAAOR,IACrBY,KAAKf,GACLgB,MAAMf,mBEFb,SAAqBD,EAAuBC,mEACZgB,GAAS,mBACLA,GAAS,wBAE3CC,aAEUN,UAAUC,MACdM,GAAW,GAEXA,GAAW,GAEbC,GAAW,IAEb,CAACpB,EAAWC,IAGP,SACLoB,cACAC,EACAT,MAAOd,EAAaC,EAAWC"}
--------------------------------------------------------------------------------
/release/index.umd.js:
--------------------------------------------------------------------------------
1 | !(function(e, t) {
2 | "object" == typeof exports && "undefined" != typeof module
3 | ? (module.exports = t(require("react")))
4 | : "function" == typeof define && define.amd
5 | ? define(["react"], t)
6 | : (e.reactUseWebShare = t(e.react));
7 | })(this, function(e) {
8 | function t(e, t) {
9 | return function(n) {
10 | var r = (function(e) {
11 | if (e) return e;
12 | var t = document.querySelector("link[rel=canonical]");
13 | return t ? t.href : window.location.href;
14 | })(n.url),
15 | o = n.title || document.title;
16 | navigator
17 | .share({ text: n.text, title: o, url: r })
18 | .then(e)
19 | .catch(t);
20 | };
21 | }
22 | return function(n, r) {
23 | void 0 === n && (n = function() {}), void 0 === r && (r = function() {});
24 | var o = e.useState(!0),
25 | i = o[0],
26 | u = o[1],
27 | c = e.useState(!1),
28 | a = c[0],
29 | f = c[1];
30 | return (
31 | e.useEffect(
32 | function() {
33 | navigator.share ? f(!0) : f(!1), u(!1);
34 | },
35 | [n, r]
36 | ),
37 | { loading: i, isSupported: a, share: t(n, r) }
38 | );
39 | };
40 | });
41 | //# sourceMappingURL=index.umd.js.map
42 |
--------------------------------------------------------------------------------
/release/index.umd.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"file":"index.umd.js","sources":["../src/share-content.ts","../src/get-url.ts","../src/index.ts"],"sourcesContent":["import getUrl from \"./get-url\";\n\n/**\n * Trigger native share popup\n */\nfunction shareContent(onSuccess: () => void, onError: () => void) {\n return function (config: Partial) {\n const url = getUrl(config.url);\n const title = config.title || document.title;\n const text = config.text;\n navigator\n .share({ text, title, url })\n .then(onSuccess)\n .catch(onError);\n };\n}\n\nexport default shareContent;","/**\n * Get the URL to be shared.\n * If the site uses canonical URL, then use that URL otherwise the current URL\n * @param url URL that might be passed on by the user\n */\nfunction getUrl(url?: string): string {\n if(!!url) {\n return url;\n } else {\n const canonicalEl = document.querySelector('link[rel=canonical]') as HTMLLinkElement;\n return canonicalEl ? canonicalEl.href : window.location.href;\n }\n}\n\nexport default getUrl;","import { useState, useEffect } from 'react';\n\nimport shareContent from \"./share-content\";\n\n/**\n * Use native web share dialog when available\n * @param onSuccess function called on successfully sharing content\n * @param onError callback function called on error sharing content\n * @example \n * const { isSupported, isLoading, share } = useWebShare(successFn, errorFn);\n */\nfunction useWebShare(onSuccess = () => { }, onError = () => { }) {\n const [loading, setLoading] = useState(true);\n const [isSupported, setSupport] = useState(false);\n\n useEffect(\n () => {\n if (!!navigator.share) {\n setSupport(true);\n } else {\n setSupport(false);\n }\n setLoading(false);\n },\n [onSuccess, onError]\n );\n\n return {\n loading,\n isSupported,\n share: shareContent(onSuccess, onError),\n };\n}\n\nexport default useWebShare;\n"],"names":["shareContent","onSuccess","onError","config","url","canonicalEl","document","querySelector","href","window","location","getUrl","title","navigator","share","text","then","catch","useState","useEffect","setSupport","setLoading","loading","isSupported"],"mappings":"iNAKA,SAASA,EAAaC,EAAuBC,UACpC,SAAUC,OACTC,ECFV,SAAgBA,MACTA,SACIA,MAEDC,EAAcC,SAASC,cAAc,8BACpCF,EAAcA,EAAYG,KAAOC,OAAOC,SAASF,KDH5CG,CAAOR,EAAOC,KACpBQ,EAAQT,EAAOS,OAASN,SAASM,MAEvCC,UACGC,MAAM,MAFIX,EAAOY,WAEHH,MAAOR,IACrBY,KAAKf,GACLgB,MAAMf,WEFb,SAAqBD,EAAuBC,mEACZgB,YAAS,mBACLA,YAAS,wBAE3CC,uBAEUN,UAAUC,MACdM,GAAW,GAEXA,GAAW,GAEbC,GAAW,IAEb,CAACpB,EAAWC,IAGP,SACLoB,cACAC,EACAT,MAAOd,EAAaC,EAAWC"}
--------------------------------------------------------------------------------
/release/share-content.d.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Trigger native share popup
3 | */
4 | declare function shareContent(onSuccess: () => void, onError: () => void): (config: Partial) => void;
5 | export default shareContent;
6 |
--------------------------------------------------------------------------------
/src/__tests__/get-url.test.ts:
--------------------------------------------------------------------------------
1 | import getUrl from "../get-url";
2 |
3 | describe("Get url function", () => {
4 | test("returns me passed url", () => {
5 | const url = getUrl("string");
6 | expect(url).toBe("string");
7 | });
8 |
9 | test("returns me current url", () => {
10 | window = Object.create(window);
11 | Object.defineProperty(window, 'location', {
12 | value: {
13 | href: "string"
14 | }
15 | });
16 | const url = getUrl("string");
17 | expect(url).toBe("string");
18 | });
19 |
20 | test("returns me canonical url", () => {
21 | document.body.innerHTML = `
22 |
23 | `;
24 | const url = getUrl("string");
25 | expect(url).toBe("string");
26 | })
27 | })
--------------------------------------------------------------------------------
/src/__tests__/share-content.test.ts:
--------------------------------------------------------------------------------
1 | import shareContent from "../share-content";
2 |
3 | describe("Share Content function", () => {
4 | const successFn = () => { };
5 | const errorFn = () => { };
6 |
7 | beforeEach(() => {
8 | navigator.share = jest.fn(() => Promise.resolve());
9 | });
10 |
11 | test("calls share function", () => {
12 | shareContent(successFn, errorFn)({});
13 | expect(navigator.share).toBeCalled();
14 | });
15 |
16 | test("returns a function", () => {
17 | const result = shareContent(successFn, errorFn);
18 | expect(typeof result).toBe("function");
19 | });
20 |
21 | test("calls success on successful share", async () => {
22 | const mockedSuccessFn = jest.fn();
23 | const mockedErrorFn = jest.fn();
24 | await shareContent(mockedSuccessFn, mockedErrorFn)({});
25 | expect(mockedSuccessFn).toBeCalled();
26 | expect(mockedErrorFn).not.toBeCalled();
27 | });
28 |
29 | /*
30 | Have to find a way to make this work without throwing the error in console.
31 | test("calls error on failed share", async () => {
32 | navigator.share = jest.fn();
33 | const share = jest.spyOn(navigator, "share");
34 | share.mockImplementation(() => {
35 | throw new Error();
36 | });
37 | const mockedSuccessFn = jest.fn();
38 | const mockedErrorFn = jest.fn();
39 | await shareContent(mockedSuccessFn, mockedErrorFn)({});
40 | expect(mockedSuccessFn).not.toBeCalled();
41 | expect(mockedErrorFn).toBeCalled();
42 | }) */
43 | })
--------------------------------------------------------------------------------
/src/get-url.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Get the URL to be shared.
3 | * If the site uses canonical URL, then use that URL otherwise the current URL
4 | * @param url URL that might be passed on by the user
5 | */
6 | function getUrl(url?: string): string {
7 | if (!!url) {
8 | return url;
9 | } else {
10 | const canonicalEl = document.querySelector(
11 | "link[rel=canonical]"
12 | ) as HTMLLinkElement;
13 | return canonicalEl ? canonicalEl.href : window.location.href;
14 | }
15 | }
16 |
17 | export default getUrl;
18 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react';
2 |
3 | import shareContent from "./share-content";
4 |
5 | /**
6 | * Use native web share dialog when available
7 | * @param onSuccess function called on successfully sharing content
8 | * @param onError callback function called on error sharing content
9 | * @example
10 | * const { isSupported, isLoading, share } = useWebShare(successFn, errorFn);
11 | */
12 | function useWebShare(onSuccess = () => { }, onError = () => { }) {
13 | const [loading, setLoading] = useState(true);
14 | const [isSupported, setSupport] = useState(false);
15 |
16 | useEffect(
17 | () => {
18 | if (!!navigator.share) {
19 | setSupport(true);
20 | } else {
21 | setSupport(false);
22 | }
23 | setLoading(false);
24 | },
25 | [onSuccess, onError]
26 | );
27 |
28 | return {
29 | loading,
30 | isSupported,
31 | share: shareContent(onSuccess, onError),
32 | };
33 | }
34 |
35 | export default useWebShare;
36 |
--------------------------------------------------------------------------------
/src/share-content.ts:
--------------------------------------------------------------------------------
1 | import getUrl from "./get-url";
2 |
3 | /**
4 | * Trigger native share popup
5 | */
6 | function shareContent(onSuccess: () => void, onError: () => void) {
7 | return function (config: Partial) {
8 | const url = getUrl(config.url);
9 | const title = config.title || document.title;
10 | const text = config.text;
11 | navigator
12 | .share({ text, title, url })
13 | .then(onSuccess)
14 | .catch(onError);
15 | };
16 | }
17 |
18 | export default shareContent;
--------------------------------------------------------------------------------
/src/typing.d.ts:
--------------------------------------------------------------------------------
1 | interface IShareConfig {
2 | title: string;
3 | text?: string;
4 | url: string;
5 | }
6 |
7 | interface Navigator {
8 | share: (config: IShareConfig) => Promise;
9 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "esModuleInterop": true,
5 | "jsx": "react",
6 | "lib": ["es2017", "dom"],
7 | "noEmitOnError": true,
8 | "strict": true,
9 | "target": "es5"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------