├── .nvmrc
├── typings.d.ts
├── .github
├── FUNDING.yml
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── feature_request.md
│ └── bug_report.md
└── workflows
│ ├── main.yml
│ └── release.yml
├── .gitignore
├── src
├── index.tsx
├── components
│ ├── header.tsx
│ ├── backdrop.tsx
│ ├── portal.tsx
│ ├── close.tsx
│ ├── icon
│ │ ├── index.tsx
│ │ └── list.tsx
│ └── social-icons.tsx
├── hooks
│ └── use-disclosure.tsx
├── interfaces.ts
├── style.css
└── sharer.tsx
├── .prettierrc.json
├── .vscode
├── settings.json
└── extensions.json
├── .storybook
├── preview.js
├── preview-head.html
└── main.js
├── stories
└── web-share-component.stories.tsx
├── tsconfig.json
├── LICENSE
├── .eslintrc.cjs
├── package.json
└── README.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | 16
2 |
--------------------------------------------------------------------------------
/typings.d.ts:
--------------------------------------------------------------------------------
1 | // declare module "*.css";
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: harshzalavadiya
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.log
2 | .DS_Store
3 | node_modules
4 | .cache
5 | dist
6 | storybook-static
7 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | export type { RWebShareProps } from "./interfaces";
2 | export { RWebShare } from "./sharer";
3 |
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "printWidth": 80,
3 | "tabWidth": 2,
4 | "useTabs": false,
5 | "semi": true,
6 | "singleQuote": false
7 | }
8 |
--------------------------------------------------------------------------------
/src/components/header.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function Header({ title }) {
4 | return ;
5 | }
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: npm
4 | directory: "/"
5 | schedule:
6 | interval: monthly
7 | time: "23:30"
8 | open-pull-requests-limit: 10
9 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.colorCustomizations": {
3 | "activityBar.background": "#5a56a0",
4 | "titleBar.activeBackground": "#604e98",
5 | "titleBar.activeForeground": "#FBFAF9"
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/.storybook/preview.js:
--------------------------------------------------------------------------------
1 | export const parameters = {
2 | actions: { argTypesRegex: "^on[A-Z].*" },
3 | controls: {
4 | matchers: {
5 | color: /(background|color)$/i,
6 | date: /Date$/,
7 | },
8 | },
9 | }
--------------------------------------------------------------------------------
/.storybook/preview-head.html:
--------------------------------------------------------------------------------
1 |
19 |
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "dbaeumer.vscode-eslint",
4 | "dsznajder.es7-react-js-snippets",
5 | "esbenp.prettier-vscode",
6 | "meganrogge.template-string-converter",
7 | "paragdiwan.gitpatch",
8 | "shardulm94.trailing-spaces",
9 | "wix.vscode-import-cost",
10 | "formulahendry.auto-rename-tag"
11 | ]
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/backdrop.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export function Backdrop({ children, onClose }) {
4 | const handleOnClose = (e) => {
5 | if (e.target === e.currentTarget) {
6 | onClose(e);
7 | }
8 | };
9 |
10 | return (
11 |
12 | {children}
13 |
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/portal.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { createPortal } from "react-dom";
3 |
4 | export const Portal: any = ({ children }: any) => {
5 | const el = document.createElement("div");
6 |
7 | React.useEffect(() => {
8 | document.body.appendChild(el);
9 | return () => {
10 | document.body.removeChild(el);
11 | };
12 | }, [el]);
13 |
14 | return createPortal(children, el);
15 | };
16 |
--------------------------------------------------------------------------------
/src/hooks/use-disclosure.tsx:
--------------------------------------------------------------------------------
1 | import { useCallback, useState } from "react";
2 |
3 | export function useDisclosure() {
4 | const [isOpen, setIsOpen] = useState(false);
5 |
6 | const onOpen = useCallback(() => setIsOpen(true), []);
7 | const onClose = useCallback(() => setIsOpen(false), []);
8 | const onToggle = useCallback(() => setIsOpen((state) => !state), []);
9 |
10 | return { isOpen, onOpen, onClose, onToggle };
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/close.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | interface CloseButtonProps {
4 | onClose;
5 | closeText?: string;
6 | }
7 |
8 | export function CloseButton({ onClose, closeText }: CloseButtonProps) {
9 | return (
10 |
18 | );
19 | }
20 |
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | stories: [
3 | "../stories/**/*.stories.mdx",
4 | "../stories/**/*.stories.@(js|jsx|ts|tsx)",
5 | ],
6 | addons: [
7 | "@storybook/addon-links",
8 | "@storybook/addon-essentials",
9 | {
10 | name: '@storybook/addon-docs',
11 | options: {
12 | configureJSX: true,
13 | transcludeMarkdown: true,
14 | },
15 | },
16 | ],
17 | framework: "@storybook/react",
18 | };
19 |
--------------------------------------------------------------------------------
/stories/web-share-component.stories.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { RWebShare } from "../src";
4 |
5 | export default {
6 | title: "Welcome",
7 | };
8 |
9 | export const Default = () => (
10 |
11 |
12 | console.log(`${name} share successful!`)}
19 | >
20 |
21 |
22 |
23 | );
24 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/src/interfaces.ts:
--------------------------------------------------------------------------------
1 | export interface ShareData {
2 | text?: string;
3 | title?: string;
4 | url?: string;
5 | }
6 |
7 | export interface RWebShareProps {
8 | children: any;
9 | closeText?: string;
10 | data: ShareData;
11 | sites?: string[];
12 | onClick?;
13 | disableNative?;
14 | }
15 |
16 | export interface SocialIconsProps {
17 | onClose;
18 | closeText?: string;
19 | sites: string[];
20 | data: Required;
21 | onClick?;
22 | }
23 |
24 | export interface IconProps {
25 | onClose;
26 | name: string;
27 | data: Required;
28 | onClick?;
29 | }
30 |
31 | export interface IconItem {
32 | path: JSX.Element;
33 | e;
34 | color: string;
35 | viewBox?: string;
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 |
3 | on: [push]
4 |
5 | jobs:
6 | build:
7 | name: Node ${{ matrix.node }} on ${{ matrix.os }}
8 |
9 | runs-on: ${{ matrix.os }}
10 | strategy:
11 | matrix:
12 | node: ["16.x"]
13 | os: [ubuntu-latest, windows-latest, macOS-latest]
14 |
15 | steps:
16 | - name: Checkout repo
17 | uses: actions/checkout@v2
18 |
19 | - name: Use Node ${{ matrix.node }}
20 | uses: actions/setup-node@v2
21 | with:
22 | node-version: ${{ matrix.node }}
23 |
24 | - name: Install deps and build (with cache)
25 | uses: bahmutov/npm-install@v1
26 |
27 | - name: Lint
28 | run: yarn lint
29 |
30 | - name: Build
31 | run: yarn build
32 |
--------------------------------------------------------------------------------
/src/components/icon/index.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { IconProps } from "../../interfaces";
4 | import { IconList } from "./list";
5 |
6 | export default function Icon({ name, data, onClose, onClick }: IconProps) {
7 | const { path, viewBox = "0 0 24 24", color, e } = IconList[name];
8 |
9 | const handleOnButtonClicked = () => {
10 | onClick && onClick(name); // callback
11 | e(encodeURIComponent(data.url), data.text, data.title);
12 | onClose();
13 | };
14 |
15 | return (
16 |
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "composite": false,
4 | "declaration": true,
5 | "declarationMap": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "inlineSources": false,
9 | "isolatedModules": true,
10 | "jsx": "react-jsx",
11 | "lib": [
12 | "ES2021",
13 | "dom"
14 | ],
15 | "module": "ESNext",
16 | "moduleResolution": "node",
17 | "noUnusedLocals": false,
18 | "noUnusedParameters": false,
19 | "preserveWatchOutput": true,
20 | "skipLibCheck": true,
21 | "strict": true,
22 | "target": "ES6",
23 | "noImplicitAny": false,
24 | "noImplicitReturns": false
25 | },
26 | "exclude": [
27 | "build",
28 | "dist",
29 | "node_modules"
30 | ],
31 | "include": [
32 | "."
33 | ]
34 | }
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: RELEASE
2 |
3 | on:
4 | push:
5 | tags:
6 | - v[0-9]+.[0-9]+.[0-9]+
7 |
8 | jobs:
9 | test:
10 | name: Release
11 | runs-on: ubuntu-latest
12 |
13 | steps:
14 | - name: Checkout repo
15 | uses: actions/checkout@v2
16 |
17 | - name: Setup node
18 | uses: actions/setup-node@v2
19 | with:
20 | node-version: 16.x
21 | registry-url: https://registry.npmjs.org
22 |
23 | - name: Install deps and build (with cache)
24 | uses: bahmutov/npm-install@v1
25 |
26 | - name: Lint
27 | run: yarn lint
28 |
29 | - name: Build
30 | run: yarn build
31 |
32 | - name: Publishing to NPM
33 | env:
34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
35 | run: npm publish
36 |
--------------------------------------------------------------------------------
/src/components/social-icons.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { SocialIconsProps } from "../interfaces";
4 | import { CloseButton } from "./close";
5 | import { Header } from "./header";
6 | import Icon from "./icon";
7 |
8 | export const SocialIcons = ({
9 | onClose,
10 | sites,
11 | data,
12 | closeText,
13 | onClick,
14 | }: SocialIconsProps) => (
15 |
20 |
21 |
22 | {sites.map((name) => (
23 |
30 | ))}
31 |
32 |
33 |
34 | );
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Harsh Zalavadiya
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.
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **Sandbox / Git Repo**
14 |
15 | https://codesandbox.io/s/react-web-share-46skt
16 |
17 | **To Reproduce**
18 | Steps to reproduce the behavior:
19 | 1. Go to '...'
20 | 2. Click on '....'
21 | 3. Scroll down to '....'
22 | 4. See error
23 |
24 | **Expected behavior**
25 | A clear and concise description of what you expected to happen.
26 |
27 | **Screenshots**
28 | If applicable, add screenshots to help explain your problem.
29 |
30 | **Desktop (please complete the following information):**
31 | - OS: [e.g. iOS]
32 | - Browser [e.g. chrome, safari]
33 | - Version [e.g. 22]
34 |
35 | **Smartphone (please complete the following information):**
36 | - Device: [e.g. iPhone6]
37 | - OS: [e.g. iOS8.1]
38 | - Browser [e.g. stock browser, safari]
39 | - Version [e.g. 22]
40 |
41 | **Additional context**
42 | Add any other context about the problem here.
43 |
--------------------------------------------------------------------------------
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | es2021: true,
6 | node: true,
7 | },
8 | parser: "@typescript-eslint/parser",
9 | plugins: ["@typescript-eslint", "prettier", "simple-import-sort"],
10 | extends: [
11 | "eslint:recommended",
12 | "plugin:@typescript-eslint/eslint-recommended",
13 | "plugin:@typescript-eslint/recommended",
14 | "plugin:react/recommended",
15 | "plugin:storybook/recommended",
16 | ],
17 | rules: {
18 | "@typescript-eslint/ban-ts-comment": "off",
19 | "@typescript-eslint/ban-ts-ignore": "off",
20 | "@typescript-eslint/camelcase": "off",
21 | "@typescript-eslint/explicit-function-return-type": "off",
22 | "@typescript-eslint/explicit-module-boundary-types": "off",
23 | "@typescript-eslint/interface-name-prefix": "off",
24 | "@typescript-eslint/no-empty-interface": "off",
25 | "@typescript-eslint/no-explicit-any": "off",
26 | "@typescript-eslint/no-unused-vars": [
27 | "error",
28 | {
29 | ignoreRestSiblings: true,
30 | },
31 | ],
32 | "no-case-declarations": "off",
33 | "no-console": [
34 | "error",
35 | {
36 | allow: ["warn", "error", "debug"],
37 | },
38 | ],
39 | "no-useless-escape": "off",
40 | "prettier/prettier": "error",
41 | "react/display-name": "off",
42 | "react/jsx-key": "off",
43 | "react/no-children-prop": "off",
44 | "react/prop-types": "off",
45 | "simple-import-sort/exports": "error",
46 | "simple-import-sort/imports": "error",
47 | },
48 | settings: {
49 | react: {
50 | version: "detect",
51 | },
52 | },
53 | };
54 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | .web-share-fade {
2 | animation: rwsFade 0.5s;
3 | animation-fill-mode: both;
4 | }
5 |
6 | @keyframes rwsFade {
7 | 0% {
8 | opacity: 0;
9 | }
10 | 100% {
11 | opacity: 1;
12 | }
13 | }
14 |
15 | .web-share-fade-in-up {
16 | animation: rwsfadeInUp 0.5s;
17 | animation-fill-mode: both;
18 | }
19 |
20 | @keyframes rwsfadeInUp {
21 | 0% {
22 | opacity: 0;
23 | transform: translateY(20px);
24 | }
25 | 100% {
26 | opacity: 1;
27 | transform: translateY(0);
28 | }
29 | }
30 |
31 | .rws-icons {
32 | padding: 1.25rem;
33 | display: grid;
34 | grid-template-columns: repeat(4, 1fr);
35 | grid-gap: 1.25rem;
36 | }
37 |
38 | .rws-icon {
39 | width: 100%;
40 | height: auto;
41 | cursor: pointer;
42 | border: 0;
43 | background: #1a78f6;
44 | padding: 0.75rem;
45 | border-radius: 0.5rem;
46 | font-size: 0;
47 | }
48 |
49 | .rws-container {
50 | max-width: 24rem;
51 | width: 90%;
52 | background-color: white;
53 | border-radius: 0.5rem;
54 | border-bottom-left-radius: 0;
55 | border-bottom-right-radius: 0;
56 | }
57 |
58 | .rws-backdrop {
59 | position: fixed;
60 | left: 0;
61 | top: 0;
62 | width: 100%;
63 | height: 100%;
64 | background: rgba(0, 0, 0, 0.4);
65 | display: flex;
66 | flex-direction: column;
67 | align-items: center;
68 | justify-content: flex-end;
69 | z-index: 1400;
70 | }
71 |
72 | .rws-header {
73 | padding: 1rem 1.25rem;
74 | font-size: 1.25rem;
75 | font-weight: 600;
76 | padding-bottom: 0;
77 | }
78 |
79 | .rws-close {
80 | background: #edf2f7;
81 | cursor: pointer;
82 | padding: 0.75rem;
83 | display: block;
84 | width: 100%;
85 | border: 0;
86 | font-size: 1rem;
87 | }
88 |
--------------------------------------------------------------------------------
/src/sharer.tsx:
--------------------------------------------------------------------------------
1 | import "./style.css";
2 |
3 | import React, { cloneElement, memo, useCallback, useMemo } from "react";
4 |
5 | import { Backdrop } from "./components/backdrop";
6 | import { IconList } from "./components/icon/list";
7 | import { Portal } from "./components/portal";
8 | import { SocialIcons } from "./components/social-icons";
9 | import { useDisclosure } from "./hooks/use-disclosure";
10 | import type { RWebShareProps } from "./interfaces";
11 |
12 | const defaultSites = Object.keys(IconList).slice(0, 8);
13 |
14 | export const RWebShare = memo((props: RWebShareProps) => {
15 | const { onOpen, onClose, isOpen } = useDisclosure();
16 |
17 | const shareData = useMemo(
18 | () => ({
19 | ...props.data,
20 | title: props.data.title || "share",
21 | text: props.data.text || "",
22 | url:
23 | props.data.url ||
24 | (typeof window !== "undefined" && window.location.href) ||
25 | "",
26 | }),
27 | [props.data]
28 | );
29 |
30 | const handleOnClick = useCallback(async () => {
31 | if (window.navigator.share && !props.disableNative) {
32 | try {
33 | await window.navigator.share(shareData);
34 | props.onClick();
35 | } catch (e) {
36 | console.warn(e);
37 | }
38 | } else {
39 | onOpen();
40 | }
41 | }, [shareData]);
42 |
43 | return (
44 | <>
45 | {/* Overrides Children element's `onClick` event */}
46 | {cloneElement(props.children, {
47 | ...props.children?.props,
48 | onClick: handleOnClick,
49 | })}
50 |
51 | {/* Share Component */}
52 | {isOpen && (
53 |
54 |
55 |
62 |
63 |
64 | )}
65 | >
66 | );
67 | });
68 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-web-share",
3 | "author": "harshzalavadiya",
4 | "version": "2.0.2",
5 | "description": "Tiny Web Share API Wrapper with fallback for unsupported browsers",
6 | "license": "MIT",
7 | "repository": "https://github.com/hc-oss/react-web-share",
8 | "module": "./dist/esm/index.js",
9 | "main": "./dist/index.js",
10 | "types": "./dist/index.d.ts",
11 | "scripts": {
12 | "build": "tsup src/index.tsx --inject-style --legacy-output --minify --format esm,cjs --dts --external react",
13 | "dev": "tsup src/index.tsx --inject-style --legacy-output --format esm,cjs --watch --dts --external react",
14 | "lint": "eslint src --fix",
15 | "size": "size-limit",
16 | "storybook": "start-storybook -p 6006",
17 | "build-storybook": "build-storybook"
18 | },
19 | "dependencies": {},
20 | "peerDependencies": {
21 | "react": "^16 || ^17 || ^18",
22 | "react-dom": "^16 || ^17 || ^18"
23 | },
24 | "devDependencies": {
25 | "@size-limit/preset-small-lib": "^8.1.0",
26 | "@storybook/addon-actions": "^6.5.12",
27 | "@storybook/addon-essentials": "^6.5.12",
28 | "@storybook/addon-knobs": "^6.4.0",
29 | "@storybook/addon-links": "^6.5.12",
30 | "@storybook/react": "^6.5.12",
31 | "@types/react-dom": "^18.0.6",
32 | "@types/react": "^18.0.21",
33 | "@typescript-eslint/eslint-plugin": "^5.40.0",
34 | "@typescript-eslint/parser": "^5.40.0",
35 | "eslint-plugin-prettier": "^4.2.1",
36 | "eslint-plugin-react": "^7.31.10",
37 | "eslint-plugin-simple-import-sort": "^8.0.0",
38 | "eslint-plugin-storybook": "^0.6.6",
39 | "eslint": "8.25.0",
40 | "postcss": "^8.4.17",
41 | "prettier": "^2.7.1",
42 | "react-dom": "^18.2.0",
43 | "react": "^18.2.0",
44 | "size-limit": "^8.1.0",
45 | "storybook-addon-turbo-build": "^1.1.0",
46 | "tsup": "^6.2.3",
47 | "typescript": "^4.8.4"
48 | },
49 | "browserslist": [
50 | "defaults",
51 | "not IE 11",
52 | "maintained node versions"
53 | ],
54 | "files": [
55 | "dist/**"
56 | ],
57 | "size-limit": [
58 | {
59 | "path": "dist/index.js",
60 | "limit": "10 KB"
61 | }
62 | ],
63 | "keywords": [
64 | "react",
65 | "share",
66 | "web-share",
67 | "native",
68 | "fallback",
69 | "polyfill",
70 | "component",
71 | "tiny",
72 | "lightweight"
73 | ]
74 | }
75 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-web-share
2 |
3 | Tiny [Web Share API](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/share) wrapper with fallback for unsupported browsers
4 |
5 | [](https://github.com/hc-oss/react-web-share/actions)
6 | [](https://npm.im/react-web-share)
7 | [](https://bundlephobia.com/result?p=react-web-share@latest)
8 |
9 | [](https://codesandbox.io/s/react-web-share-46skt)
10 |
11 | > 💡 most browsers restricts web share api only to https websites
12 |
13 | ## ✨ Features
14 |
15 | - 🍃 Only ~6kb gzipped and no external dependencies
16 | - 🌀 Uses React Portal
17 | - ✌ Written w/ TypeScript
18 |
19 | ## 🔧 Installation
20 |
21 | ```bash
22 | npm i react-web-share # npm
23 | yarn add react-web-share # yarn
24 | ```
25 |
26 | ## Preview
27 |
28 | ### Mobile
29 |
30 | 
31 |
32 | ### Desktop
33 |
34 | 
35 |
36 | ## 📦 Example
37 |
38 | ```tsx
39 | import React, { useState } from "react";
40 | import { RWebShare } from "react-web-share";
41 |
42 | const Example = () => {
43 | return (
44 |
45 | console.log("shared successfully!")}
52 | >
53 |
54 |
55 |
56 | );
57 | };
58 |
59 | export default Example;
60 | ```
61 |
62 | ## 👀 Props
63 |
64 | | Prop | Description | Type | Default |
65 | | --------------- | --------------------------- | -------------------- | --------------------------------------------- |
66 | | `data` | Share Object | `{text, url, title}` | `{text: "", url: currentURL, title: "Share"}` |
67 | | `sites` | sites | `string[]` | all platforms (see list below for key list) |
68 | | `closeText` | translate close | `string` | localise close text |
69 | | `onClick` | callback on sucessful share | | |
70 | | `disableNative` | disables native share | `boolean` | `false` |
71 |
72 | ## 🌎 Sites
73 |
74 | - facebook
75 | - twitter
76 | - whatsapp
77 | - reddit
78 | - telegram
79 | - linkedin
80 | - mail
81 | - copy (Copy to Clipboard)
82 | - vk
83 | - okru
84 |
85 | ## 📜 License
86 |
87 | MIT © [harshzalavadiya](https://github.com/harshzalavadiya)
88 |
--------------------------------------------------------------------------------
/src/components/icon/list.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | import { IconItem } from "../../interfaces";
4 |
5 | export interface IconListObject {
6 | [key: string]: IconItem;
7 | }
8 |
9 | const externalOpen = (URL) => window.open(URL, "_blank", "noopener");
10 |
11 | export const IconList: IconListObject = {
12 | facebook: {
13 | path: (
14 |
15 | ),
16 | color: "#0076FB",
17 | e: (l) => externalOpen(`https://www.facebook.com/sharer/sharer.php?u=${l}`),
18 | },
19 | twitter: {
20 | path: (
21 |
22 | ),
23 | color: "#1DA1F2",
24 | e: (l, t) =>
25 | externalOpen(`https://twitter.com/intent/tweet?text=${t}&url=${l}`),
26 | },
27 | whatsapp: {
28 | path: (
29 |
30 | ),
31 | color: "#25D366",
32 | e: (l, t) => externalOpen(`https://api.whatsapp.com/send?text=${t} ${l}`),
33 | },
34 | reddit: {
35 | path: (
36 |
37 | ),
38 | color: "#FF4500",
39 | e: (l, t) =>
40 | externalOpen(`https://www.reddit.com/submit?url=${l}&title=${t}`),
41 | },
42 | telegram: {
43 | path: (
44 |
45 | ),
46 | color: "#0088CC",
47 | e: (l, t) =>
48 | externalOpen(`https://telegram.me/share/msg?url=${l}&text=${t}`),
49 | },
50 | linkedin: {
51 | path: (
52 |
53 | ),
54 | color: "#0073b1",
55 | e: (l, t, ti) =>
56 | externalOpen(
57 | `https://www.linkedin.com/shareArticle?mini=true&url=${l}&title=${ti}&summary=${t}`
58 | ),
59 | },
60 | mail: {
61 | path: (
62 |
63 | ),
64 | color: "#E53E3E",
65 | e: (l, t) => externalOpen(`mailto:?body=${l}&subject=${t}`),
66 | },
67 | copy: {
68 | path: (
69 |
70 | ),
71 | color: "#718096",
72 | e: (l) => navigator.clipboard.writeText(decodeURIComponent(l)),
73 | },
74 | vk: {
75 | path: (
76 |
77 | ),
78 | color: "#07f",
79 | e: (l, t, ti) =>
80 | externalOpen(`http://vk.com/share.php?url=${l}&title=${ti}&comment=${t}`),
81 | },
82 | okru: {
83 | path: (
84 |
85 | ),
86 | color: "#e27e35",
87 | e: (l) =>
88 | externalOpen(
89 | `https://connect.ok.ru/dk?st.cmd=WidgetSharePreview&st.shareUrl=${l}`
90 | ),
91 | },
92 | };
93 |
--------------------------------------------------------------------------------