├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── pull_request_template.md └── workflows │ └── deploy.yml ├── .gitignore ├── .prettierrc.json ├── .storybook ├── backgrounds.js ├── main.js ├── manager.js ├── preview.js └── theme.js ├── .travis.yml ├── README.md ├── cypress.json ├── cypress ├── fixtures │ └── example.json ├── plugins │ └── index.js └── support │ ├── commands.ts │ └── index.ts ├── package-lock.json ├── package.json ├── src ├── components │ ├── TwitterDMButton.tsx │ ├── TwitterFollowButton.tsx │ ├── TwitterHashtagButton.tsx │ ├── TwitterMentionButton.tsx │ ├── TwitterMomentShare.tsx │ ├── TwitterOnAirButton.tsx │ ├── TwitterShareButton.tsx │ ├── TwitterTimelineEmbed.tsx │ ├── TwitterTweetEmbed.tsx │ ├── TwitterVideoEmbed.tsx │ └── twiter-widget-url.ts ├── index.tsx ├── stories │ ├── TwitterDMButton.stories.tsx │ ├── TwitterFollowButton.stories.tsx │ ├── TwitterHashtagButton.stories.tsx │ ├── TwitterMentionButton.stories.tsx │ ├── TwitterMomentShare.stories.tsx │ ├── TwitterOnAirButton.stories.tsx │ ├── TwitterShareButtton.stories.tsx │ ├── TwitterTimelineEmbed.stories.tsx │ ├── TwitterTweetEmbed.stories.tsx │ ├── TwitterVideoEmbed.stories.tsx │ └── story.css ├── styles.module.css ├── tests │ └── cypress │ │ └── components │ │ ├── TwitterDMButton.spec.tsx │ │ ├── TwitterFollowButton.spec.tsx │ │ ├── TwitterHashtagButton.spec.tsx │ │ ├── TwitterMentionButton.spec.tsx │ │ ├── TwitterOnAirButton.spec.tsx │ │ ├── TwitterShareButton.spec.tsx │ │ ├── TwitterTimelineEmbed.spec.tsx │ │ ├── TwitterTweetEmbed.spec.tsx │ │ └── TwitterVideoEmbed.spec.tsx └── typings.d.ts ├── tsconfig.json └── tsconfig.test.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | node_modules/ 4 | .snapshots/ 5 | *.min.js -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "@typescript-eslint/parser", 3 | "extends": [ 4 | "standard", 5 | "standard-react", 6 | "plugin:prettier/recommended", 7 | "prettier/standard", 8 | "prettier/react", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "env": { 13 | "node": true 14 | }, 15 | "globals": { 16 | "cy": "readonly", 17 | "Cypress": "readonly" 18 | }, 19 | "parserOptions": { 20 | "ecmaVersion": 2020, 21 | "ecmaFeatures": { 22 | "legacyDecorators": true, 23 | "jsx": true 24 | } 25 | }, 26 | "settings": { 27 | "react": { 28 | "version": "16" 29 | } 30 | }, 31 | "rules": { 32 | "space-before-function-paren": 0, 33 | "react/prop-types": 0, 34 | "react/jsx-handler-names": 0, 35 | "react/jsx-fragments": 0, 36 | "react/no-unused-prop-types": 0, 37 | "import/export": 0, 38 | "@typescript-eslint/no-explicit-any": 0, 39 | "@typescript-eslint/explicit-function-return-type": 0, 40 | "@typescript-eslint/no-var-requires": 0, 41 | "@typescript-eslint/ban-ts-ignore": 0, 42 | "@typescript-eslint/ban-ts-comment": 0 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | **PR Checklist** 3 | 4 | - [ ] Add feature name in the title 5 | - [ ] Set the context in the description 6 | - [ ] Set the appropriate label 7 | - [ ] Add tests 8 | - [ ] Relevant screenshots 9 | - [ ] Fixed a bug? Add it as a test 10 | - [ ] is a breaking change? 11 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish to GH pages 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | 8 | pull_request: 9 | type: [opened, synchronize, reopened, ready_for_review] 10 | branches: 11 | - master 12 | 13 | jobs: 14 | build: 15 | name: Build 🚧 🚧 🚧 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Check out code 20 | uses: actions/checkout@v2 21 | 22 | - name: Use Node.js 12.x 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: 12.x 26 | 27 | - name: Install Node Dependencies 28 | run: npm install 29 | 30 | - name: Run the lint 31 | run: npm run lint 32 | 33 | - name: Run Cypress 🧪🧪🧪 34 | run: npm run cypress:ci 35 | 36 | - name: Build package 37 | run: npm run build 38 | 39 | - name: Build storybook 40 | run: npm run build-storybook 41 | 42 | storybook: 43 | name: Storybook Github Pages Deployment 🚀 44 | runs-on: ubuntu-latest 45 | needs: build 46 | if: github.ref == 'refs/heads/master' 47 | 48 | steps: 49 | - name: Check out code 50 | uses: actions/checkout@v2 51 | 52 | - name: Use Node.js 12.x 53 | uses: actions/setup-node@v1 54 | with: 55 | node-version: 12.x 56 | 57 | - name: Install Node Dependencies 58 | run: npm install 59 | 60 | - name: Build package 61 | run: npm run build 62 | 63 | - name: Build storybook 64 | run: npm run build-storybook 65 | 66 | - name: Deploy to GH Pages 🚀 67 | uses: JamesIves/github-pages-deploy-action@4.1.4 68 | with: 69 | branch: gh-pages 70 | folder: storybook-static 71 | token: ${{ secrets.GH_AUTH_TOKEN }} 72 | 73 | versioning: 74 | name: Automated Version Bump 😎 75 | runs-on: ubuntu-latest 76 | needs: build 77 | if: github.ref == 'refs/heads/master' 78 | 79 | steps: 80 | - name: Check out Code 81 | uses: actions/checkout@v2 82 | 83 | - name: Automated Version Bump 84 | uses: phips28/gh-action-bump-version@master 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GH_AUTH_TOKEN }} 87 | with: 88 | commit-message: '[Skip CI] Automate Version Bump : update version {{version}}' 89 | 90 | publish: 91 | name: Publish to npmjs.com 92 | runs-on: ubuntu-latest 93 | needs: versioning 94 | if: github.ref == 'refs/heads/master' 95 | steps: 96 | - uses: actions/checkout@v1 97 | - uses: actions/setup-node@v1 98 | with: 99 | node-version: 12.x 100 | - run: npm install 101 | - run: npm run build 102 | - uses: JS-DevTools/npm-publish@v1 103 | with: 104 | token: ${{ secrets.NPM_TOKEN }} 105 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # See https://help.github.com/ignore-files/ for more about ignoring files. 3 | 4 | # dependencies 5 | node_modules 6 | 7 | # builds 8 | build 9 | dist 10 | storybook-static 11 | .idea 12 | .rpt2_cache 13 | 14 | # misc 15 | .DS_Store 16 | .env 17 | .env.local 18 | .env.development.local 19 | .env.test.local 20 | .env.production.local 21 | 22 | npm-debug.log* 23 | yarn-debug.log* 24 | yarn-error.log 25 | 26 | *.iml 27 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "jsxSingleQuote": true, 4 | "semi": true, 5 | "tabWidth": 2, 6 | "bracketSpacing": true, 7 | "jsxBracketSameLine": false, 8 | "arrowParens": "always", 9 | "trailingComma": "none" 10 | } 11 | -------------------------------------------------------------------------------- /.storybook/backgrounds.js: -------------------------------------------------------------------------------- 1 | const backgrounds = { 2 | default: 'Blue', 3 | values: [ 4 | {name: 'White', value: '#FFFFFF'}, 5 | {name: 'Whitesmoke', value: 'whitesmoke'}, 6 | {name: 'Black', value: 'black'}, 7 | {name: 'Red', value: '#EF9A9A'}, 8 | {name: 'Pink', value: '#F48FB1'}, 9 | {name: 'Purple', value: '#CE93D8'}, 10 | {name: 'Deep Purple', value: '#B39DDB'}, 11 | {name: 'Indigo', value: '#9FA8DA'}, 12 | {name: 'Blue', value: '#90CAF9', default: true}, 13 | {name: 'Light Blue', value: '#81D4FA'}, 14 | {name: 'Dark Blue', value: '#5CB1EC'}, 15 | {name: 'Green', value: '#A5D6A7'}, 16 | {name: 'Deep Orange', value: '#FFAB91'}, 17 | {name: 'Blue Grey', value: '#B0BEC5'} 18 | ] 19 | }; 20 | 21 | export default backgrounds; 22 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "stories": [ 3 | "../src/**/*.stories.mdx", 4 | "../src/**/*.stories.@(js|jsx|ts|tsx)" 5 | ], 6 | "addons": [ 7 | "@storybook/addon-links", 8 | "@storybook/addon-essentials" 9 | ] 10 | } -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | import pluginTheme from './theme'; 3 | 4 | addons.setConfig({ 5 | theme: pluginTheme, 6 | }); 7 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import backgrounds from "./backgrounds"; 2 | export const parameters = { 3 | actions: { argTypesRegex: "^on[A-Z].*" }, 4 | backgrounds, 5 | controls: { 6 | matchers: { 7 | color: /(background|color)$/i, 8 | date: /Date$/, 9 | }, 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /.storybook/theme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export default create({ 4 | base: 'light', 5 | brandTitle: 'React Twitter Embed', 6 | brandUrl: 'https://github.com/saurabhnemade/react-twitter-embed', 7 | brandImage: '', 8 | }); 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 12 4 | 5 | after_success: 6 | - npm install -g rollup 7 | - npm install 8 | - npm run cypress:ci 9 | - npm run build-storybook 10 | 11 | notifications: 12 | email: 13 | recipients: 14 | - saurabhnemade@yandex.com 15 | on_success: always 16 | on_failure: always 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # React Twitter Embed Component 3 | 4 | [![NPM](https://img.shields.io/npm/v/react-twitter-embed.svg)](https://www.npmjs.com/package/react-twitter-embed) [![Storybook](https://cdn.jsdelivr.net/gh/storybooks/brand@master/badge/badge-storybook.svg)](https://saurabhnemade.github.io/react-twitter-embed/) [![Build Status](https://travis-ci.org/saurabhnemade/react-twitter-embed.svg?branch=storybook-migration)](https://travis-ci.org/saurabhnemade/react-twitter-embed) [![Known Vulnerabilities](https://snyk.io/test/github/saurabhnemade/react-twitter-embed/badge.svg)](https://snyk.io/test/github/saurabhnemade/react-twitter-embed) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![License](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://raw.githubusercontent.com/saurabhnemade/react-twitter-embed/master/LICENSE) 5 | 6 | 7 | React Twitter Embed Component 8 | 9 | Simplest way to add Twitter Widgets to your react project. 10 | 11 | ## Demo and Examples 12 | https://saurabhnemade.github.io/react-twitter-embed/ 13 | 14 | 15 | ## Install 16 | 17 | ```bash 18 | npm install --save react-twitter-embed 19 | ``` 20 | 21 | ## Usage 22 | 23 | ```jsx 24 | import { TwitterTimelineEmbed, TwitterShareButton, TwitterFollowButton, TwitterHashtagButton, TwitterMentionButton, TwitterTweetEmbed, TwitterMomentShare, TwitterDMButton, TwitterVideoEmbed, TwitterOnAirButton } from 'react-twitter-embed'; 25 | ``` 26 | 27 | **Adding Timeline:** 28 | 29 | 34 | 35 | **Adding Tweet:** 36 | 37 | 40 | 41 | **Adding Share Button:** 42 | 43 | 47 | 48 | **Adding Mention Button:** 49 | 50 | 53 | 54 | **Adding Hashtag button:** 55 | 56 | 59 | 60 | **Adding follow button:** 61 | 62 | 65 | 66 | **Adding Moment:** 67 | 68 | 71 | 72 | **Adding Direct Messaging Button:** 73 | 74 | 77 | 78 | **Adding Twitter Video:** 79 | 80 | 83 | 84 | **Adding Twitter On Air Button:** 85 | 86 | 89 | 90 | **Explore All Options by Twitter Widgets API:** 91 | 92 | https://developer.twitter.com/en/docs/twitter-for-websites/javascript-api/overview 93 | 94 | 95 | ## License 96 | 97 | MIT © [saurabhnemade](https://github.com/saurabhnemade) 98 | 99 | Copyright (c) 2022 100 | 101 | Permission is hereby granted, free of charge, to any person obtaining a copy 102 | of this software and associated documentation files (the "Software"), to deal 103 | in the Software without restriction, including without limitation the rights 104 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 105 | copies of the Software, and to permit persons to whom the Software is 106 | furnished to do so, subject to the following conditions: 107 | 108 | The above copyright notice and this permission notice shall be included in all 109 | copies or substantial portions of the Software. 110 | 111 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 112 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 113 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 114 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 115 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 116 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 117 | SOFTWARE. 118 | 119 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "chromeWebSecurity": false, 3 | "component": { 4 | "componentFolder": "src/tests/cypress/components", 5 | "testFiles": "**/*spec.{js,jsx,ts,tsx}" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io", 4 | "body": "Fixtures are a great way to mock data for responses to routes" 5 | } 6 | -------------------------------------------------------------------------------- /cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | // eslint-disable-next-line no-unused-vars 19 | module.exports = (on, config) => { 20 | // `on` is used to hook into various events Cypress emits 21 | // `config` is the resolved Cypress config 22 | if (config.testingType === 'component') { 23 | require('@cypress/react/plugins/react-scripts')(on, config); 24 | } 25 | 26 | return config; 27 | }; 28 | -------------------------------------------------------------------------------- /cypress/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | // 11 | // 12 | // -- This is a parent command -- 13 | // Cypress.Commands.add('login', (email, password) => { ... }) 14 | // 15 | // 16 | // -- This is a child command -- 17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) 18 | // 19 | // 20 | // -- This is a dual command -- 21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) 22 | // 23 | // 24 | // -- This will overwrite an existing command -- 25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) 26 | 27 | import 'cypress-wait-until'; 28 | 29 | const getIframeDocument = () => { 30 | return cy.get('iframe').its('0.contentDocument').should('exist'); 31 | }; 32 | 33 | Cypress.Commands.add('getIframeBody', () => { 34 | // get the document 35 | return ( 36 | getIframeDocument() 37 | // automatically retries until body is loaded 38 | .its('body') 39 | .should('not.be.undefined') 40 | // wraps "body" DOM element to allow 41 | // chaining more Cypress commands, like ".find(...)" 42 | .then(cy.wrap) 43 | ); 44 | }); 45 | -------------------------------------------------------------------------------- /cypress/support/index.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | // eslint-disable-next-line @typescript-eslint/no-namespace 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | 19 | // Alternatively you can use CommonJS syntax: 20 | // require('./commands') 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-twitter-embed", 3 | "version": "4.0.5", 4 | "description": "React Twitter Embed Components", 5 | "author": "Saurabh Nemade", 6 | "license": "MIT", 7 | "repository": "saurabhnemade/react-twitter-embed", 8 | "main": "dist/index.js", 9 | "module": "dist/index.modern.js", 10 | "source": "src/index.tsx", 11 | "engines": { 12 | "node": ">=10" 13 | }, 14 | "scripts": { 15 | "build": "microbundle-crl --no-compress --format modern,cjs", 16 | "cypress:open": "node_modules/.bin/cypress open-ct open", 17 | "cypress:ci": "node_modules/.bin/cypress run-ct", 18 | "start": "microbundle-crl watch --no-compress --format modern,cjs", 19 | "storybook": "start-storybook -p 9009", 20 | "prepare": "npm run build", 21 | "lint": "eslint --ext ts,tsx .", 22 | "lint:fix": "eslint --fix --ext ts,tsx .", 23 | "build-storybook": "build-storybook", 24 | "deploy": "gh-pages -d example/build" 25 | }, 26 | "peerDependencies": { 27 | "react": "^16.0.0 || ^17.0.0 || ^18.0.0", 28 | "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" 29 | }, 30 | "dependencies": { 31 | "scriptjs": "^2.5.9" 32 | }, 33 | "devDependencies": { 34 | "@cypress/react": "^5.9.4", 35 | "@cypress/webpack-dev-server": "^1.5.0", 36 | "@storybook/addon-actions": "^6.4.16", 37 | "@storybook/addon-essentials": "^6.4.16", 38 | "@storybook/addon-links": "^6.4.16", 39 | "@storybook/react": "^6.4.16", 40 | "@storybook/theming": "^6.4.16", 41 | "@testing-library/cypress": "^7.0.6", 42 | "@testing-library/jest-dom": "^4.2.4", 43 | "@testing-library/react": "^9.5.0", 44 | "@testing-library/user-event": "^7.2.1", 45 | "@types/jest": "^25.1.4", 46 | "@types/node": "^12.12.38", 47 | "@types/react": "^16.9.27", 48 | "@types/react-dom": "^16.9.7", 49 | "@typescript-eslint/eslint-plugin": "^2.26.0", 50 | "@typescript-eslint/parser": "^2.26.0", 51 | "cross-env": "^7.0.2", 52 | "cypress": "^7.2.0", 53 | "cypress-wait-until": "^1.7.1", 54 | "eslint": "^6.8.0", 55 | "eslint-config-prettier": "^6.7.0", 56 | "eslint-config-standard": "^14.1.1", 57 | "eslint-config-standard-react": "^9.2.0", 58 | "eslint-plugin-import": "^2.23.4", 59 | "eslint-plugin-node": "^11.1.0", 60 | "eslint-plugin-prettier": "^3.1.1", 61 | "eslint-plugin-promise": "^4.3.1", 62 | "eslint-plugin-react": "^7.17.0", 63 | "eslint-plugin-standard": "^4.0.1", 64 | "gh-pages": "^3.2.3", 65 | "microbundle-crl": "^0.13.11", 66 | "npm-run-all": "^4.1.5", 67 | "prettier": "^2.0.4", 68 | "react": "^16.13.1", 69 | "react-dom": "^16.13.1", 70 | "react-scripts": "^4.0.3", 71 | "typescript": "^4.3.5", 72 | "webpack-dev-server": "^4.7.3" 73 | }, 74 | "files": [ 75 | "dist" 76 | ] 77 | } 78 | -------------------------------------------------------------------------------- /src/components/TwitterDMButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterDMButtonProps { 15 | /** 16 | * Twitter user id for DM button 17 | */ 18 | id: number; 19 | /** 20 | * Additional options to be added to the button 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createDMButton'; 34 | 35 | const TwitterDMButton = (props: TwitterDMButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.id, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | if (isComponentMounted) { 61 | setLoading(false); 62 | if (props.onLoad) { 63 | props.onLoad(element); 64 | } 65 | } 66 | }); 67 | } 68 | }); 69 | 70 | // cleaning up 71 | return () => { 72 | isComponentMounted = false; 73 | }; 74 | }, []); 75 | 76 | return ( 77 | 78 | {loading && {props.placeholder}} 79 |
80 | 81 | ); 82 | }; 83 | 84 | export default TwitterDMButton; 85 | -------------------------------------------------------------------------------- /src/components/TwitterFollowButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterFollowButtonProps { 15 | /** 16 | * Username of twitter user which will be followed on click 17 | */ 18 | screenName: string; 19 | /** 20 | * Additional options to be added to the button 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createFollowButton'; 34 | 35 | const TwitterFollowButton = (props: TwitterFollowButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.screenName, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterFollowButton; 83 | -------------------------------------------------------------------------------- /src/components/TwitterHashtagButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterHashtagButtonProps { 15 | /** 16 | * Tag name for hashtag button 17 | */ 18 | tag: string; 19 | /** 20 | * Additional options to be added to the button 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createHashtagButton'; 34 | 35 | const TwitterHashtagButton = (props: TwitterHashtagButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.tag, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterHashtagButton; 83 | -------------------------------------------------------------------------------- /src/components/TwitterMentionButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterMentionButtonProps { 15 | /** 16 | * Username to which you will need to tweet 17 | */ 18 | screenName: string; 19 | /** 20 | * Additional options for overriding config. 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createMentionButton'; 34 | 35 | const TwitterMentionButton = (props: TwitterMentionButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.screenName, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterMentionButton; 83 | -------------------------------------------------------------------------------- /src/components/TwitterMomentShare.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterMomentShareProps { 15 | /** 16 | * id of Twitter moment to show 17 | */ 18 | momentId: string; 19 | /** 20 | * Additional options for overriding config. 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createMoment'; 34 | 35 | const TwitterMomentShare = (props: TwitterMomentShareProps) => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.momentId, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterMomentShare; 83 | -------------------------------------------------------------------------------- /src/components/TwitterOnAirButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterOnAirButtonProps { 15 | /** 16 | * Username for which you require periscope on air button 17 | */ 18 | username: string; 19 | /** 20 | * Additional options for overriding config. 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createPeriscopeOnAirButton'; 34 | 35 | const TwitterOnAirButton = (props: TwitterOnAirButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.username, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterOnAirButton; 83 | -------------------------------------------------------------------------------- /src/components/TwitterShareButton.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterShareButtonProps { 15 | /** 16 | * Url for sharing 17 | */ 18 | url: string; 19 | /** 20 | * Additional options for overriding config. Details at : https://dev.twitter.com/web/tweet-button/parameters 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createShareButton'; 34 | 35 | const TwitterShareButton = (props: TwitterShareButtonProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.url, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterShareButton; 83 | -------------------------------------------------------------------------------- /src/components/TwitterTimelineEmbed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterTimelineEmbedBase { 15 | /** 16 | * Additional options to pass to twitter widget plugin 17 | */ 18 | options?: JSONObject; 19 | /** 20 | * Automatically fit into parent container height 21 | */ 22 | autoHeight?: boolean; 23 | /** 24 | * With dark or light theme 25 | */ 26 | theme?: 'dark' | 'light'; 27 | /** 28 | * With custom link colors. Note: Only Hex colors are supported. 29 | */ 30 | linkColor?: string; 31 | /** 32 | * With custom border colors. Note: Only Hex colors are supported. 33 | */ 34 | borderColor?: string; 35 | /** 36 | * Hide the header from timeline 37 | */ 38 | noHeader?: boolean; 39 | /** 40 | * Hide the footer from timeline 41 | */ 42 | noFooter?: boolean; 43 | /** 44 | * Hide the border from timeline 45 | */ 46 | noBorders?: boolean; 47 | /** 48 | * Hide the scrollbars 49 | */ 50 | noScrollbar?: boolean; 51 | /** 52 | * Enable Transparancy 53 | */ 54 | transparent?: boolean; 55 | /** 56 | * Custom language code. Supported codes here: https://developer.twitter.com/en/docs/twitter-for-websites/twitter-for-websites-supported-languages/overview.html 57 | */ 58 | lang?: string; 59 | /** 60 | * ariaPolite 61 | */ 62 | ariaPolite?: 'polite' | 'assertive' | 'rude'; 63 | /** 64 | * Limit of tweets to be shown 65 | */ 66 | tweetLimit?: number; 67 | /** 68 | * Placeholder while tweet is loading 69 | */ 70 | placeholder?: string | React.ReactNode; 71 | /** 72 | * Function to execute after load, return html element 73 | */ 74 | onLoad?: (element: any) => void; 75 | } 76 | 77 | export interface TwitterTimelineEmbedSourceScreenName 78 | extends TwitterTimelineEmbedBase { 79 | /** 80 | * This can be either of profile, likes 81 | */ 82 | sourceType: 'profile' | 'likes'; 83 | /** 84 | * username of twitter handle as String 85 | */ 86 | screenName: string; 87 | } 88 | 89 | export interface TwitterTimelineEmbedSourceUserId 90 | extends TwitterTimelineEmbedBase { 91 | /** 92 | * This can be either of profile, likes 93 | */ 94 | sourceType: 'profile' | 'likes'; 95 | /** 96 | * UserId of twitter handle as number 97 | */ 98 | userId: string; 99 | } 100 | 101 | export interface TwitterTimelineEmbedSourceTimeline 102 | extends TwitterTimelineEmbedBase { 103 | /** 104 | * This can be either of timeline 105 | */ 106 | sourceType: 'timeline'; 107 | /** 108 | * To show list, unique list id 109 | * Also used with collections, in that case its valid collection id 110 | */ 111 | id: string; 112 | } 113 | 114 | export interface TwitterTimelineEmbedSourceTimelineWidget 115 | extends TwitterTimelineEmbedBase { 116 | /** 117 | * This can be either of timeline 118 | */ 119 | sourceType: 'timeline'; 120 | /** 121 | * To show list, unique list id 122 | * Also used with collections, in that case its valid collection id 123 | */ 124 | widgetId: string; 125 | } 126 | 127 | export interface TwitterTimelineEmbedSourceList 128 | extends TwitterTimelineEmbedBase { 129 | /** 130 | * This can be either of list 131 | */ 132 | sourceType: 'list'; 133 | /** 134 | * To show list, used along with slug 135 | */ 136 | ownerScreenName: string; 137 | /** 138 | * To show list, used along with ownerScreenName 139 | */ 140 | slug: string; 141 | } 142 | 143 | export interface TwitterTimelineEmbedSourceListId 144 | extends TwitterTimelineEmbedBase { 145 | /** 146 | * This can be either of list 147 | */ 148 | sourceType: 'list'; 149 | /** 150 | * To show list, unique list id 151 | * Also used with collections, in that case its valid collection id 152 | */ 153 | id: string | number; 154 | } 155 | 156 | export interface TwitterTimelineEmbedSourceCollectionId 157 | extends TwitterTimelineEmbedBase { 158 | /** 159 | * This can be collection 160 | */ 161 | sourceType: 'collection'; 162 | /** 163 | * To show list, unique list id 164 | * Also used with collections, in that case its valid collection id 165 | */ 166 | id: string | number; 167 | } 168 | 169 | export interface TwitterTimelineEmbedSourceCollectionUrl 170 | extends TwitterTimelineEmbedBase { 171 | /** 172 | * This can be collection 173 | */ 174 | sourceType: 'collection'; 175 | /** 176 | * To show list, unique list url 177 | * Also used with collections, in that case its valid collection id 178 | */ 179 | url: string; 180 | } 181 | 182 | export interface TwitterTimelineEmbedSourceUrl 183 | extends TwitterTimelineEmbedBase { 184 | /** 185 | * This can be url 186 | */ 187 | sourceType: 'url'; 188 | /** 189 | * To show twitter content with url. 190 | * Supported content includes profiles, likes, lists, and collections. 191 | */ 192 | url: string; 193 | } 194 | 195 | export interface TwitterTimelineEmbedSourceWidget 196 | extends TwitterTimelineEmbedBase { 197 | /** 198 | * This can be widget 199 | */ 200 | sourceType: 'widget'; 201 | /** 202 | * To show custom widget 203 | */ 204 | widgetId: string; 205 | } 206 | 207 | export type TwitterTimelineEmbedPropsType = 208 | | TwitterTimelineEmbedSourceScreenName 209 | | TwitterTimelineEmbedSourceUserId 210 | | TwitterTimelineEmbedSourceTimeline 211 | | TwitterTimelineEmbedSourceTimelineWidget 212 | | TwitterTimelineEmbedSourceList 213 | | TwitterTimelineEmbedSourceListId 214 | | TwitterTimelineEmbedSourceCollectionId 215 | | TwitterTimelineEmbedSourceCollectionUrl 216 | | TwitterTimelineEmbedSourceUrl 217 | | TwitterTimelineEmbedSourceWidget; 218 | 219 | // export interface TwitterTimelineEmbedProps { 220 | // sourceType: 'profile' | 'likes' | 'list' | 'collection' | 'URL' | 'widget' 221 | // url: string; 222 | // options?: JSONObject, 223 | // placeholder?: string | React.ReactNode; 224 | // onLoad?: (element: any) => void; 225 | // }; 226 | 227 | const methodName = 'createTimeline'; 228 | 229 | const TwitterTimelineEmbed = (props: TwitterTimelineEmbedPropsType): any => { 230 | const ref = React.useRef(null); 231 | const [loading, setLoading] = React.useState(true); 232 | 233 | const buildOptions = () => { 234 | let options = Object.assign({}, props.options); 235 | if (props?.autoHeight) { 236 | options.height = (ref.current?.parentNode as HTMLElement)?.offsetHeight; 237 | } 238 | 239 | options = Object.assign({}, options, { 240 | theme: props?.theme, 241 | linkColor: props?.linkColor, 242 | borderColor: props?.borderColor, 243 | lang: props?.lang, 244 | tweetLimit: props?.tweetLimit, 245 | ariaPolite: props?.ariaPolite 246 | }); 247 | 248 | return options; 249 | }; 250 | 251 | const buildChromeOptions = (options: JSONObject) => { 252 | options.chrome = ''; 253 | if (props.noHeader) { 254 | options.chrome = options.chrome + ' noheader'; 255 | } 256 | if (props.noFooter) { 257 | options.chrome = options.chrome + ' nofooter'; 258 | } 259 | if (props.noBorders) { 260 | options.chrome = options.chrome + ' noborders'; 261 | } 262 | if (props.noScrollbar) { 263 | options.chrome = options.chrome + ' noscrollbar'; 264 | } 265 | if (props.transparent) { 266 | options.chrome = options.chrome + ' transparent'; 267 | } 268 | 269 | return options; 270 | }; 271 | 272 | React.useEffect(() => { 273 | let isComponentMounted = true; 274 | const script = require('scriptjs'); 275 | script(twitterWidgetJs, 'twitter-embed', () => { 276 | if (!window.twttr) { 277 | console.error('Failure to load window.twttr, aborting load'); 278 | return; 279 | } 280 | if (isComponentMounted) { 281 | if (!window.twttr.widgets[methodName]) { 282 | console.error( 283 | `Method ${methodName} is not present anymore in twttr.widget api` 284 | ); 285 | return; 286 | } 287 | 288 | let options = buildOptions(); 289 | /** Append chrome options */ 290 | options = buildChromeOptions(options); 291 | 292 | window.twttr.widgets[methodName]( 293 | { 294 | // @ts-ignore 295 | sourceType: props.sourceType, 296 | // @ts-ignore 297 | screenName: props.screenName, 298 | // @ts-ignore 299 | userId: props.userId, 300 | // @ts-ignore 301 | ownerScreenName: props.ownerScreenName, 302 | // @ts-ignore 303 | slug: props.slug, 304 | // @ts-ignore 305 | id: props.id || props.widgetId, 306 | // @ts-ignore 307 | url: props.url 308 | }, 309 | ref?.current, 310 | options 311 | ).then((element: any) => { 312 | setLoading(false); 313 | if (props.onLoad) { 314 | props.onLoad(element); 315 | } 316 | }); 317 | } 318 | }); 319 | 320 | // cleaning up 321 | return () => { 322 | isComponentMounted = false; 323 | }; 324 | }, []); 325 | 326 | return ( 327 | 328 | {loading && {props.placeholder}} 329 |
330 | 331 | ); 332 | }; 333 | 334 | export default TwitterTimelineEmbed; 335 | -------------------------------------------------------------------------------- /src/components/TwitterTweetEmbed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | interface JSONObject { 11 | [k: string]: any; 12 | } 13 | 14 | export interface TwitterTweetEmbedProps { 15 | /** 16 | * Tweet id that needs to be shown 17 | */ 18 | tweetId: string; 19 | /** 20 | * Additional options to pass to twitter widget plugin 21 | */ 22 | options?: JSONObject; 23 | /** 24 | * Placeholder while tweet is loading 25 | */ 26 | placeholder?: string | React.ReactNode; 27 | /** 28 | * Function to execute after load, return html element 29 | */ 30 | onLoad?: (element: any) => void; 31 | } 32 | 33 | const methodName = 'createTweet'; 34 | 35 | const TwitterTweetEmbed = (props: TwitterTweetEmbedProps): any => { 36 | const ref = React.useRef(null); 37 | const [loading, setLoading] = React.useState(true); 38 | 39 | React.useEffect(() => { 40 | let isComponentMounted = true; 41 | const script = require('scriptjs'); 42 | script(twitterWidgetJs, 'twitter-embed', () => { 43 | if (!window.twttr) { 44 | console.error('Failure to load window.twttr, aborting load'); 45 | return; 46 | } 47 | if (isComponentMounted) { 48 | if (!window.twttr.widgets[methodName]) { 49 | console.error( 50 | `Method ${methodName} is not present anymore in twttr.widget api` 51 | ); 52 | return; 53 | } 54 | 55 | window.twttr.widgets[methodName]( 56 | props.tweetId, 57 | ref?.current, 58 | props.options 59 | ).then((element: any) => { 60 | setLoading(false); 61 | if (props.onLoad) { 62 | props.onLoad(element); 63 | } 64 | }); 65 | } 66 | }); 67 | 68 | // cleaning up 69 | return () => { 70 | isComponentMounted = false; 71 | }; 72 | }, []); 73 | 74 | return ( 75 | 76 | {loading && {props.placeholder}} 77 |
78 | 79 | ); 80 | }; 81 | 82 | export default TwitterTweetEmbed; 83 | -------------------------------------------------------------------------------- /src/components/TwitterVideoEmbed.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import twitterWidgetJs from './twiter-widget-url'; 3 | 4 | declare global { 5 | interface Window { 6 | twttr: any; 7 | } 8 | } 9 | 10 | export interface TwitterVideoEmbedProps { 11 | /** 12 | * Id of video tweet. 13 | */ 14 | id: string; 15 | /** 16 | * Placeholder while tweet is loading 17 | */ 18 | placeholder?: string | React.ReactNode; 19 | /** 20 | * Function to execute after load, return html element 21 | */ 22 | onLoad?: (element: any) => void; 23 | } 24 | 25 | const methodName = 'createVideo'; 26 | 27 | const TwitterVideoEmbed = (props: TwitterVideoEmbedProps): any => { 28 | const ref = React.useRef(null); 29 | const [loading, setLoading] = React.useState(true); 30 | 31 | React.useEffect(() => { 32 | let isComponentMounted = true; 33 | const script = require('scriptjs'); 34 | script(twitterWidgetJs, 'twitter-embed', () => { 35 | if (!window.twttr) { 36 | console.error('Failure to load window.twttr, aborting load'); 37 | return; 38 | } 39 | if (isComponentMounted) { 40 | if (!window.twttr.widgets[methodName]) { 41 | console.error( 42 | `Method ${methodName} is not present anymore in twttr.widget api` 43 | ); 44 | return; 45 | } 46 | 47 | window.twttr.widgets[methodName](props.id, ref?.current).then( 48 | (element: any) => { 49 | setLoading(false); 50 | if (props.onLoad) { 51 | props.onLoad(element); 52 | } 53 | } 54 | ); 55 | } 56 | }); 57 | 58 | // cleaning up 59 | return () => { 60 | isComponentMounted = false; 61 | }; 62 | }, []); 63 | 64 | return ( 65 | 66 | {loading && {props.placeholder}} 67 |
68 | 69 | ); 70 | }; 71 | 72 | export default TwitterVideoEmbed; 73 | -------------------------------------------------------------------------------- /src/components/twiter-widget-url.ts: -------------------------------------------------------------------------------- 1 | const twitterWidgetJs = 'https://platform.twitter.com/widgets.js'; 2 | 3 | export default twitterWidgetJs; 4 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import TwitterTimelineEmbed from './components/TwitterTimelineEmbed'; 2 | import TwitterShareButton from './components/TwitterShareButton'; 3 | import TwitterFollowButton from './components/TwitterFollowButton'; 4 | import TwitterHashtagButton from './components/TwitterHashtagButton'; 5 | import TwitterMentionButton from './components/TwitterMentionButton'; 6 | import TwitterTweetEmbed from './components/TwitterTweetEmbed'; 7 | import TwitterMomentShare from './components/TwitterMomentShare'; 8 | import TwitterDMButton from './components/TwitterDMButton'; 9 | import TwitterVideoEmbed from './components/TwitterVideoEmbed'; 10 | import TwitterOnAirButton from './components/TwitterOnAirButton'; 11 | 12 | export { 13 | TwitterTimelineEmbed, 14 | TwitterShareButton, 15 | TwitterFollowButton, 16 | TwitterHashtagButton, 17 | TwitterMentionButton, 18 | TwitterTweetEmbed, 19 | TwitterMomentShare, 20 | TwitterDMButton, 21 | TwitterVideoEmbed, 22 | TwitterOnAirButton 23 | }; 24 | -------------------------------------------------------------------------------- /src/stories/TwitterDMButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterDMButton, { 4 | TwitterDMButtonProps 5 | } from './../components/TwitterDMButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter DM Button', 11 | component: TwitterDMButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const DirectMessageButton = Template.bind({}); 26 | DirectMessageButton.args = { 27 | id: 1364031673 28 | }; 29 | 30 | export const DirectMessageButtonLarge = Template.bind({}); 31 | DirectMessageButtonLarge.args = { 32 | id: 1364031673, 33 | options: { size: 'large' } 34 | }; 35 | 36 | export const DirectMessageButtonWithTextPlaceholder = Template.bind({}); 37 | DirectMessageButtonWithTextPlaceholder.args = { 38 | id: 1364031673, 39 | options: { size: 'large' }, 40 | placeholder: 'Loading' 41 | }; 42 | 43 | export const DirectMessageButtonWithCustomPlaceholder = Template.bind({}); 44 | DirectMessageButtonWithCustomPlaceholder.args = { 45 | id: 1364031673, 46 | options: { size: 'large' }, 47 | placeholder: ( 48 |
56 | Hello I am custom placeholder 57 |
58 | ) 59 | }; 60 | 61 | export const DirectMessageButtonWithOnLoadAction = Template.bind({}); 62 | DirectMessageButtonWithOnLoadAction.args = { 63 | id: 1364031673, 64 | options: { size: 'large' }, 65 | placeholder: ( 66 |
74 | Hello I am custom placeholder 75 |
76 | ), 77 | onLoad: action('Loaded successfully') 78 | }; 79 | -------------------------------------------------------------------------------- /src/stories/TwitterFollowButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterFollowButton, { 4 | TwitterFollowButtonProps 5 | } from './../components/TwitterFollowButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Follow Button', 11 | component: TwitterFollowButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const FollowButtonSimple = Template.bind({}); 26 | FollowButtonSimple.args = { 27 | screenName: 'saurabhnemade' 28 | }; 29 | 30 | export const FollowButtonLarge = Template.bind({}); 31 | FollowButtonLarge.args = { 32 | screenName: 'saurabhnemade', 33 | options: { size: 'large' } 34 | }; 35 | 36 | export const FollowButtonWithTextPlaceholder = Template.bind({}); 37 | FollowButtonWithTextPlaceholder.args = { 38 | screenName: 'saurabhnemade', 39 | options: { size: 'large' }, 40 | placeholder: 'Loading' 41 | }; 42 | 43 | export const FollowButtonWithCustomPlaceholder = Template.bind({}); 44 | FollowButtonWithCustomPlaceholder.args = { 45 | screenName: 'saurabhnemade', 46 | options: { size: 'large' }, 47 | placeholder: ( 48 |
56 | Hello I am custom placeholder 57 |
58 | ) 59 | }; 60 | 61 | export const FollowButtonWithOnLoad = Template.bind({}); 62 | FollowButtonWithOnLoad.args = { 63 | screenName: 'saurabhnemade', 64 | options: { size: 'large' }, 65 | onLoad: action('Loaded successfully') 66 | }; 67 | -------------------------------------------------------------------------------- /src/stories/TwitterHashtagButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterHashtagButton, { 4 | TwitterHashtagButtonProps 5 | } from './../components/TwitterHashtagButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Hashtag Button', 11 | component: TwitterHashtagButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const HashtagButton = Template.bind({}); 26 | HashtagButton.args = { 27 | tag: 'cybersecurity' 28 | }; 29 | 30 | export const HashtagButtonLarge = Template.bind({}); 31 | HashtagButtonLarge.args = { 32 | tag: 'cybersecurity', 33 | options: { size: 'large' } 34 | }; 35 | 36 | export const HashtagButtonWithTextPlaceholder = Template.bind({}); 37 | HashtagButtonWithTextPlaceholder.args = { 38 | tag: 'cybersecurity', 39 | options: { size: 'large' }, 40 | placeholder: 'Loading' 41 | }; 42 | 43 | export const HashtagButtonWithCustomPlaceholder = Template.bind({}); 44 | HashtagButtonWithCustomPlaceholder.args = { 45 | tag: 'cybersecurity', 46 | options: { size: 'large' }, 47 | placeholder: ( 48 |
56 | Hello I am custom placeholder 57 |
58 | ) 59 | }; 60 | 61 | export const HashtagButtonWithOnLoad = Template.bind({}); 62 | HashtagButtonWithOnLoad.args = { 63 | tag: 'cybersecurity', 64 | options: { size: 'large' }, 65 | onLoad: action('Loaded successfully') 66 | }; 67 | -------------------------------------------------------------------------------- /src/stories/TwitterMentionButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterMentionButton, { 4 | TwitterMentionButtonProps 5 | } from './../components/TwitterMentionButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Mention Button', 11 | component: TwitterMentionButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const MentionButton = Template.bind({}); 26 | MentionButton.args = { 27 | screenName: 'saurabhnemade' 28 | }; 29 | 30 | export const MentionButtonLarge = Template.bind({}); 31 | MentionButtonLarge.args = { 32 | screenName: 'saurabhnemade', 33 | options: { size: 'large' } 34 | }; 35 | 36 | export const MentionButtonWithTextPlaceholder = Template.bind({}); 37 | MentionButtonWithTextPlaceholder.args = { 38 | screenName: 'saurabhnemade', 39 | options: { size: 'large' }, 40 | placeholder: 'Loading' 41 | }; 42 | 43 | export const MentionButtonWithCustomPlaceholder = Template.bind({}); 44 | MentionButtonWithCustomPlaceholder.args = { 45 | screenName: 'saurabhnemade', 46 | options: { size: 'large' }, 47 | placeholder: ( 48 |
56 | Hello I am custom placeholder 57 |
58 | ) 59 | }; 60 | 61 | export const MentionButtonWithOnLoad = Template.bind({}); 62 | MentionButtonWithOnLoad.args = { 63 | screenName: 'saurabhnemade', 64 | options: { size: 'large' }, 65 | onLoad: action('Loaded successfully') 66 | }; 67 | -------------------------------------------------------------------------------- /src/stories/TwitterMomentShare.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterMomentShare, { 4 | TwitterMomentShareProps 5 | } from './../components/TwitterMomentShare'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Moment Share', 11 | component: TwitterMomentShare, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const MomentShare = Template.bind({}); 26 | MomentShare.args = { 27 | momentId: '650667182356082688' 28 | }; 29 | 30 | export const MomentShareWithTextPlaceholder = Template.bind({}); 31 | MomentShareWithTextPlaceholder.args = { 32 | momentId: '650667182356082688', 33 | placeholder: 'Loading' 34 | }; 35 | 36 | export const MomentShareWithCustomPlaceholder = Template.bind({}); 37 | MomentShareWithCustomPlaceholder.args = { 38 | momentId: '650667182356082688', 39 | placeholder: ( 40 |
48 | Hello I am custom placeholder 49 |
50 | ) 51 | }; 52 | 53 | export const MomentShareWithOnLoad = Template.bind({}); 54 | MomentShareWithOnLoad.args = { 55 | momentId: '650667182356082688', 56 | onLoad: action('Loaded successfully') 57 | }; 58 | -------------------------------------------------------------------------------- /src/stories/TwitterOnAirButton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterOnAirButton, { 4 | TwitterOnAirButtonProps 5 | } from './../components/TwitterOnAirButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter On Air Button', 11 | component: TwitterOnAirButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const OnAirButton = Template.bind({}); 26 | OnAirButton.args = { 27 | username: 'KatmaiNPS' 28 | }; 29 | 30 | export const OnAirButtonLarge = Template.bind({}); 31 | OnAirButtonLarge.args = { 32 | username: 'KatmaiNPS', 33 | options: { size: 'large' } 34 | }; 35 | 36 | export const OnAirButtonWithTextPlaceholder = Template.bind({}); 37 | OnAirButtonWithTextPlaceholder.args = { 38 | username: 'KatmaiNPS', 39 | placeholder: 'Loading' 40 | }; 41 | 42 | export const OnAirButtonWithCustomPlaceholder = Template.bind({}); 43 | OnAirButtonWithCustomPlaceholder.args = { 44 | username: 'KatmaiNPS', 45 | placeholder: ( 46 |
54 | Hello I am custom placeholder 55 |
56 | ) 57 | }; 58 | 59 | export const OnAirButtonWithOnLoad = Template.bind({}); 60 | OnAirButtonWithOnLoad.args = { 61 | username: 'KatmaiNPS', 62 | options: { size: 'large' }, 63 | onLoad: action('Loaded successfully') 64 | }; 65 | -------------------------------------------------------------------------------- /src/stories/TwitterShareButtton.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterShareButton, { 4 | TwitterShareButtonProps 5 | } from './../components/TwitterShareButton'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Share Button', 11 | component: TwitterShareButton, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const ShareButton = Template.bind({}); 26 | ShareButton.args = { 27 | url: 'https://facebook.com/saurabhnemade', 28 | options: { text: '#reactjs is awesome', via: 'saurabhnemade' } 29 | }; 30 | 31 | export const ShareButtonLarge = Template.bind({}); 32 | ShareButtonLarge.args = { 33 | url: 'https://facebook.com/saurabhnemade', 34 | options: { 35 | text: '#reactjs is awesome', 36 | via: 'saurabhnemade', 37 | size: 'large' 38 | } 39 | }; 40 | 41 | export const ShareButtonWithTextPlaceholder = Template.bind({}); 42 | ShareButtonWithTextPlaceholder.args = { 43 | url: 'https://facebook.com/saurabhnemade', 44 | options: { 45 | text: '#reactjs is awesome', 46 | via: 'saurabhnemade', 47 | size: 'large' 48 | }, 49 | placeholder: 'Loading' 50 | }; 51 | 52 | export const ShareButtonWithCustomPlaceholder = Template.bind({}); 53 | ShareButtonWithCustomPlaceholder.args = { 54 | url: 'https://facebook.com/saurabhnemade', 55 | options: { 56 | text: '#reactjs is awesome', 57 | via: 'saurabhnemade', 58 | size: 'large' 59 | }, 60 | placeholder: ( 61 |
69 | Hello I am custom placeholder 70 |
71 | ) 72 | }; 73 | 74 | export const ShareButtonWithOnLoad = Template.bind({}); 75 | ShareButtonWithOnLoad.args = { 76 | url: 'https://facebook.com/saurabhnemade', 77 | options: { 78 | text: '#reactjs is awesome', 79 | via: 'saurabhnemade', 80 | size: 'large' 81 | }, 82 | onLoad: action('Loaded successfully') 83 | }; 84 | -------------------------------------------------------------------------------- /src/stories/TwitterTimelineEmbed.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterTimelineEmbed, { 4 | TwitterTimelineEmbedPropsType 5 | } from './../components/TwitterTimelineEmbed'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Timeline Embed', 11 | component: TwitterTimelineEmbed, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const TimelineProfileWithScreenName = Template.bind({}); 26 | TimelineProfileWithScreenName.args = { 27 | sourceType: 'profile', 28 | screenName: 'saurabhnemade', 29 | options: { height: 400 }, 30 | onLoad: action('sample action') 31 | }; 32 | 33 | export const TimelineProfileWithUserId = Template.bind({}); 34 | TimelineProfileWithUserId.args = { 35 | sourceType: 'profile', 36 | userId: 1934309676, 37 | options: { height: 400 } 38 | }; 39 | 40 | export const TimelineLikesWithScreenName = Template.bind({}); 41 | TimelineLikesWithScreenName.args = { 42 | sourceType: 'likes', 43 | screenName: 'saurabhnemade', 44 | options: { height: 400 } 45 | }; 46 | 47 | export const TimelineLikesWithUserId = Template.bind({}); 48 | TimelineLikesWithUserId.args = { 49 | sourceType: 'likes', 50 | userId: 1934309676, 51 | options: { height: 400 } 52 | }; 53 | 54 | export const TimelineListWithOwnerScreenNameSlug = Template.bind({}); 55 | TimelineListWithOwnerScreenNameSlug.args = { 56 | sourceType: 'list', 57 | ownerScreenName: 'palafo', 58 | slug: 'breakingnews', 59 | options: { height: 400 } 60 | }; 61 | 62 | export const TimelineListWithListId = Template.bind({}); 63 | TimelineListWithListId.args = { 64 | sourceType: 'list', 65 | id: 8044403, 66 | options: { height: 400 } 67 | }; 68 | 69 | export const TimelineCollection = Template.bind({}); 70 | TimelineCollection.args = { 71 | sourceType: 'collection', 72 | id: '576828964162965504', 73 | options: { height: 400 } 74 | }; 75 | 76 | export const TimelineCollectionWithUrl = Template.bind({}); 77 | TimelineCollectionWithUrl.args = { 78 | sourceType: 'collection', 79 | url: 'https://twitter.com/NYTNow/timelines/576828964162965504', 80 | options: { height: 400 } 81 | }; 82 | 83 | export const TimelineUrlWithProfileUrl = Template.bind({}); 84 | TimelineUrlWithProfileUrl.args = { 85 | sourceType: 'url', 86 | url: 'https://twitter.com/rahul581', 87 | options: { height: 400 } 88 | }; 89 | 90 | export const TimelineUrlWithListUrl = Template.bind({}); 91 | TimelineUrlWithListUrl.args = { 92 | sourceType: 'url', 93 | url: 'https://twitter.com/mashable/lists/social-media', 94 | options: { height: 400 } 95 | }; 96 | 97 | export const TimelineUrlWithLikesUrl = Template.bind({}); 98 | TimelineUrlWithLikesUrl.args = { 99 | sourceType: 'url', 100 | url: 'https://twitter.com/ladygaga/likes', 101 | options: { height: 400 } 102 | }; 103 | 104 | export const TimelineWidget = Template.bind({}); 105 | TimelineWidget.args = { 106 | sourceType: 'widget', 107 | widgetId: '539487832448843776', 108 | options: { height: 400 } 109 | }; 110 | 111 | export const TimelineAutoheight = ({ 112 | sourceType, 113 | widgetId, 114 | autoHeight 115 | }: { 116 | sourceType: 'widget'; 117 | widgetId: string; 118 | autoHeight: boolean; 119 | }) => ( 120 |
121 |
122 | 127 |
128 |
129 | 134 |
135 |
136 | 141 |
142 |
143 | ); 144 | TimelineAutoheight.args = { 145 | sourceType: 'widget', 146 | widgetId: '539487832448843776', 147 | autoHeight: true 148 | }; 149 | 150 | export const TimelineDarkTheme = Template.bind({}); 151 | TimelineDarkTheme.args = { 152 | sourceType: 'timeline', 153 | widgetId: '539487832448843776', 154 | theme: 'dark', 155 | options: { height: 400 } 156 | }; 157 | 158 | export const TimelineLightTheme = Template.bind({}); 159 | TimelineLightTheme.args = { 160 | sourceType: 'timeline', 161 | widgetId: '539487832448843776', 162 | theme: 'light', 163 | options: { height: 400 } 164 | }; 165 | 166 | // Twitter deprecated this 167 | // export const TimelineCustomLinkColor = () => ( 168 | //
169 | //
170 | // 177 | //
178 | //
179 | // 186 | //
187 | //
188 | // 195 | //
196 | //
197 | // ) 198 | 199 | export const TimelineCustomBorderColor = ({ 200 | borderColors 201 | }: { 202 | borderColors: Array; 203 | }) => ( 204 |
205 |
206 | 213 |
214 |
215 | 222 |
223 |
224 | 231 |
232 |
233 | ); 234 | TimelineCustomBorderColor.args = { 235 | borderColors: ['#F44336', '#CDDC39', '#4CAF50'] 236 | }; 237 | 238 | export const TimelineWithNoheader = ({ noHeader }: { noHeader: boolean }) => ( 239 |
240 |
241 | 248 |
249 |
250 | ); 251 | TimelineWithNoheader.args = { 252 | noHeader: true 253 | }; 254 | 255 | export const TimelineWithNofooter = ({ noFooter }: { noFooter: boolean }) => ( 256 |
257 |
258 | 265 |
266 |
267 | ); 268 | TimelineWithNofooter.args = { 269 | noFooter: true 270 | }; 271 | 272 | export const TimelineWithNoHeaderNoFooter = () => ( 273 |
274 |
275 | 281 |
282 |
283 | 291 |
292 |
293 | ); 294 | 295 | export const TimelineWithNoBorder = () => ( 296 |
297 |
298 | 304 |
305 |
306 | 313 |
314 |
315 | ); 316 | 317 | export const TimelineWithNoScrollbar = () => ( 318 |
319 |
320 | 326 |
327 |
328 | 335 |
336 |
337 | ); 338 | 339 | export const TimelineWithTransparentBackground = () => ( 340 |
341 |
342 | 348 |
349 |
350 | 357 |
358 |
359 | ); 360 | 361 | export const TimelineWithCustomLanguage = () => ( 362 |
363 |
364 | 371 |
372 |
373 | 380 |
381 |
382 | 389 |
390 |
391 | ); 392 | 393 | export const TimelineWithTextPlaceholder = Template.bind({}); 394 | TimelineWithTextPlaceholder.args = { 395 | sourceType: 'timeline', 396 | widgetId: '539487832448843776', 397 | theme: 'dark', 398 | lang: 'hi', 399 | placeholder: 'Loading', 400 | options: { height: 400 } 401 | }; 402 | 403 | export const TimelineWithCustomPlaceholder = Template.bind({}); 404 | TimelineWithCustomPlaceholder.args = { 405 | sourceType: 'timeline', 406 | widgetId: '539487832448843776', 407 | theme: 'dark', 408 | lang: 'hi', 409 | placeholder: ( 410 |
418 | Hello I am custom placeholder 419 |
420 | ), 421 | options: { height: 400 } 422 | }; 423 | 424 | export const TimelineWithOnload = Template.bind({}); 425 | TimelineWithOnload.args = { 426 | sourceType: 'timeline', 427 | widgetId: '539487832448843776', 428 | theme: 'dark', 429 | lang: 'hi', 430 | onLoad: action('Timeline loaded'), 431 | options: { height: 400 } 432 | }; 433 | -------------------------------------------------------------------------------- /src/stories/TwitterTweetEmbed.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterTweetEmbed, { 4 | TwitterTweetEmbedProps 5 | } from './../components/TwitterTweetEmbed'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Tweet Embed', 11 | component: TwitterTweetEmbed, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const TweetEmbed = Template.bind({}); 26 | TweetEmbed.args = { 27 | tweetId: '933354946111705097' 28 | }; 29 | 30 | export const TweetEmbedMedia = Template.bind({}); 31 | TweetEmbedMedia.args = { 32 | tweetId: '1083592734038929408' 33 | }; 34 | 35 | export const TweetWithMediaEmbedWithTextPlaceholder = Template.bind({}); 36 | TweetWithMediaEmbedWithTextPlaceholder.args = { 37 | tweetId: '1083592734038929408', 38 | placeholder: 'Loading' 39 | }; 40 | 41 | export const TweetWithMediaEmbedWithCustomPlaceholder = Template.bind({}); 42 | TweetWithMediaEmbedWithCustomPlaceholder.args = { 43 | tweetId: '1083592734038929408', 44 | placeholder: ( 45 |
53 | Hello I am custom placeholder 54 |
55 | ) 56 | }; 57 | 58 | export const TweetWithMediaEmbedWithOnloadFunction = Template.bind({}); 59 | TweetWithMediaEmbedWithOnloadFunction.args = { 60 | tweetId: '1083592734038929408', 61 | options: { cards: 'hidden', width: 300, maxWidth: 800 }, 62 | onLoad: action('Loaded successfully') 63 | }; 64 | 65 | export const TweetWithMediaEmbedHideMedia = Template.bind({}); 66 | TweetWithMediaEmbedHideMedia.args = { 67 | tweetId: '1083592734038929408', 68 | options: { cards: 'hidden', width: 300, maxWidth: 800 }, 69 | onLoad: (tweetWidgetEl: HTMLDivElement) => { 70 | if (tweetWidgetEl.shadowRoot) { 71 | const tweetEl: HTMLDivElement | null = 72 | tweetWidgetEl.shadowRoot.querySelector('.EmbeddedTweet'); 73 | if (tweetEl) { 74 | tweetEl.style.width = '800px'; 75 | tweetEl.style.maxWidth = '800px'; 76 | } 77 | } 78 | } 79 | }; 80 | 81 | // export const TweetWithCustomCss = Template.bind({}); 82 | // TweetWithMediaEmbedHideMedia.args = { 83 | // tweetId: '1083592734038929408', 84 | // onLoad: (tweetWidgetEl: HTMLDivElement) => { 85 | // if (tweetWidgetEl.shadowRoot) { 86 | // const tweetEl: HTMLDivElement | null = tweetWidgetEl.shadowRoot.querySelector('.EmbeddedTweet') 87 | // if (tweetEl) { 88 | // tweetEl.style.border = '5px solid red' 89 | // } 90 | // } 91 | // } 92 | // } 93 | -------------------------------------------------------------------------------- /src/stories/TwitterVideoEmbed.stories.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Story, Meta } from '@storybook/react'; 3 | import TwitterVideoEmbed, { 4 | TwitterVideoEmbedProps 5 | } from './../components/TwitterVideoEmbed'; 6 | import { action } from '@storybook/addon-actions'; 7 | import './story.css'; 8 | 9 | export default { 10 | title: 'Twitter Video Embed', 11 | component: TwitterVideoEmbed, 12 | argTypes: { 13 | backgroundColor: { control: 'color' } 14 | } 15 | } as Meta; 16 | 17 | const Template: Story = (args) => ( 18 |
19 |
20 | 21 |
22 |
23 | ); 24 | 25 | export const TwitterVideoEmbedSimple = Template.bind({}); 26 | TwitterVideoEmbedSimple.args = { 27 | id: '560070183650213889' 28 | }; 29 | 30 | export const TwitterVideoEmbedWithTextPlaceholder = Template.bind({}); 31 | TwitterVideoEmbedWithTextPlaceholder.args = { 32 | id: '560070183650213889', 33 | placeholder: 'Loading' 34 | }; 35 | 36 | export const TwitterVideoEmbedWithCustomPlaceholder = Template.bind({}); 37 | TwitterVideoEmbedWithCustomPlaceholder.args = { 38 | id: '560070183650213889', 39 | placeholder: ( 40 |
48 | Hello I am custom placeholder 49 |
50 | ) 51 | }; 52 | 53 | export const TwitterVideoEmbedWithOnloadFunction = Template.bind({}); 54 | TwitterVideoEmbedWithOnloadFunction.args = { 55 | id: '560070183650213889', 56 | onLoad: action('Loaded successfully') 57 | }; 58 | -------------------------------------------------------------------------------- /src/stories/story.css: -------------------------------------------------------------------------------- 1 | .centerContent { 2 | display: flex; 3 | justify-content: center; 4 | align-content: center; 5 | height: stretch; 6 | } 7 | 8 | .selfCenter { 9 | align-self: center; 10 | } 11 | 12 | .selfCenter::-webkit-scrollbar { 13 | display: none; 14 | } 15 | 16 | .standardWidth { 17 | width: 250px; 18 | } 19 | 20 | .spaceBetween { 21 | padding: 10px; 22 | } 23 | -------------------------------------------------------------------------------- /src/styles.module.css: -------------------------------------------------------------------------------- 1 | /* add css module styles here (optional) */ 2 | 3 | .test { 4 | margin: 2em; 5 | padding: 0.5em; 6 | border: 2px solid #000; 7 | font-size: 2em; 8 | text-align: center; 9 | } 10 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterDMButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterDMButton from '../../../components/TwitterDMButton'; 4 | 5 | describe('Twitter DM Button', () => { 6 | it('should render direct message button with id', () => { 7 | mount(); 8 | cy.wait(1500); 9 | cy.getIframeBody().contains('Message'); 10 | }); 11 | 12 | it('should render direct message button with id and options', () => { 13 | mount(); 14 | cy.wait(1500); 15 | cy.getIframeBody().contains('Message'); 16 | }); 17 | 18 | it('should render direct message button with id and options', () => { 19 | mount( 20 | 25 | ); 26 | cy.wait(1500); 27 | cy.getIframeBody().contains('Message'); 28 | }); 29 | 30 | it('should render direct message button with custom placeholder', () => { 31 | mount( 32 | 43 | Hello I am custom placeholder 44 |
45 | } 46 | options={{ size: 'large' }} 47 | /> 48 | ); 49 | cy.contains('Hello I am custom placeholder'); 50 | cy.wait(1500); 51 | cy.getIframeBody().contains('Message'); 52 | }); 53 | 54 | it('should render direct message button with onLoad action', () => { 55 | const callback = cy.stub(); 56 | mount(); 57 | cy.wait(1500); 58 | cy.getIframeBody().contains('Message'); 59 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterFollowButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterFollowButton from '../../../components/TwitterFollowButton'; 4 | 5 | describe('Twitter Follow Button', () => { 6 | it('should render follow button with screenName', () => { 7 | mount(); 8 | cy.wait(3000); 9 | cy.getIframeBody().contains('Follow'); 10 | }); 11 | 12 | it('should render follow button with screenName and options', () => { 13 | mount( 14 | 18 | ); 19 | cy.wait(1500); 20 | cy.getIframeBody().contains('Follow'); 21 | }); 22 | 23 | it('should render follow button with placeholder', () => { 24 | mount( 25 | 30 | ); 31 | cy.contains('Loading'); 32 | cy.wait(1500); 33 | cy.getIframeBody().contains('Follow'); 34 | }); 35 | 36 | it('should render follow button with custom placeholder', () => { 37 | mount( 38 | 50 | Hello I am custom placeholder 51 |
52 | } 53 | /> 54 | ); 55 | cy.contains('Hello I am custom placeholder'); 56 | cy.wait(1500); 57 | cy.getIframeBody().contains('Follow'); 58 | }); 59 | 60 | it('should render follow button with onLoad', () => { 61 | const callback = cy.stub(); 62 | mount( 63 | 64 | ); 65 | cy.wait(1500); 66 | cy.getIframeBody().contains('Follow'); 67 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterHashtagButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterHashtagButton from '../../../components/TwitterHashtagButton'; 4 | 5 | describe('Twitter Hashtag Button', () => { 6 | it('should render hashtag button with tag', () => { 7 | mount(); 8 | cy.wait(4000); 9 | cy.getIframeBody().contains('cybersecurity'); 10 | }); 11 | 12 | it('should render hashtag button with options', () => { 13 | mount( 14 | 15 | ); 16 | cy.wait(1500); 17 | cy.getIframeBody().contains('cybersecurity'); 18 | }); 19 | 20 | it('should render hashtag button with placeholder', () => { 21 | mount( 22 | 27 | ); 28 | cy.contains('Loading'); 29 | cy.wait(1500); 30 | cy.getIframeBody().contains('cybersecurity'); 31 | }); 32 | 33 | it('should render hashtag button with custom placeholder', () => { 34 | mount( 35 | 47 | Hello I am custom placeholder 48 |
49 | } 50 | /> 51 | ); 52 | cy.contains('Hello I am custom placeholder'); 53 | cy.wait(1500); 54 | cy.getIframeBody().contains('cybersecurity'); 55 | }); 56 | 57 | it('should render hashtag button with onLoad', () => { 58 | const callback = cy.stub(); 59 | mount( 60 | 65 | ); 66 | cy.wait(1500); 67 | cy.getIframeBody().contains('cybersecurity'); 68 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 69 | }); 70 | }); 71 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterMentionButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterMentionButton from '../../../components/TwitterMentionButton'; 4 | 5 | describe('Twitter Mention Button', () => { 6 | it('should render mention button with screenName', () => { 7 | mount(); 8 | cy.wait(4000); 9 | cy.getIframeBody().contains('Tweet to'); 10 | }); 11 | 12 | it('should render mention button with option', () => { 13 | mount( 14 | 18 | ); 19 | cy.wait(1500); 20 | cy.getIframeBody().contains('Tweet to'); 21 | }); 22 | 23 | it('should render mention button with placeholder', () => { 24 | mount( 25 | 30 | ); 31 | cy.contains('Loading'); 32 | cy.wait(1500); 33 | cy.getIframeBody().contains('Tweet to'); 34 | }); 35 | 36 | it('should render mention button with custom placeholder', () => { 37 | mount( 38 | 50 | Hello I am custom placeholder 51 |
52 | } 53 | /> 54 | ); 55 | cy.contains('Hello I am custom placeholder'); 56 | cy.wait(1500); 57 | cy.getIframeBody().contains('Tweet to'); 58 | }); 59 | 60 | it('should render mention button with onLoad', () => { 61 | const callback = cy.stub(); 62 | mount( 63 | 68 | ); 69 | cy.wait(1500); 70 | cy.getIframeBody().contains('Tweet to'); 71 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterOnAirButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterOnAirButton from '../../../components/TwitterOnAirButton'; 4 | 5 | describe('Twitter On Air Button', () => { 6 | it('should render onAir button with username', () => { 7 | mount(); 8 | cy.wait(4000); 9 | cy.getIframeBody().contains('KatmaiNPS'); 10 | }); 11 | 12 | it('should render onAir button with options', () => { 13 | mount( 14 | 15 | ); 16 | cy.wait(1500); 17 | cy.getIframeBody().contains('KatmaiNPS'); 18 | }); 19 | 20 | it('should render onAir button with placeholder', () => { 21 | mount( 22 | 27 | ); 28 | cy.contains('Loading'); 29 | cy.wait(1500); 30 | cy.getIframeBody().contains('KatmaiNPS'); 31 | }); 32 | 33 | it('should render onAir button with custom placeholder', () => { 34 | mount( 35 | 47 | Hello I am custom placeholder 48 |
49 | } 50 | /> 51 | ); 52 | cy.contains('Hello I am custom placeholder'); 53 | cy.wait(1500); 54 | cy.getIframeBody().contains('KatmaiNPS'); 55 | }); 56 | 57 | it('should render onAir button with onLoad', () => { 58 | const callback = cy.stub(); 59 | mount(); 60 | cy.wait(1500); 61 | cy.getIframeBody().contains('KatmaiNPS'); 62 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterShareButton.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterShareButton from '../../../components/TwitterShareButton'; 4 | 5 | describe('Twitter Share Button', () => { 6 | it('should render Share button with url', () => { 7 | mount( 8 | 12 | ); 13 | cy.wait(4000); 14 | cy.getIframeBody().contains('Tweet'); 15 | }); 16 | 17 | it('should render Share button with large', () => { 18 | mount( 19 | 27 | ); 28 | cy.wait(1500); 29 | cy.getIframeBody().contains('Tweet'); 30 | }); 31 | 32 | it('should render Share button with placeholder', () => { 33 | mount( 34 | 43 | ); 44 | cy.contains('Loading'); 45 | cy.wait(1500); 46 | cy.getIframeBody().contains('Tweet'); 47 | }); 48 | 49 | it('should render Share button with placeholder', () => { 50 | mount( 51 | 67 | Hello I am custom placeholder 68 |
69 | } 70 | /> 71 | ); 72 | cy.contains('Hello I am custom placeholder'); 73 | cy.wait(1500); 74 | cy.getIframeBody().contains('Tweet'); 75 | }); 76 | 77 | it('should render Share button with onLoad', () => { 78 | const callback = cy.stub(); 79 | mount( 80 | 89 | ); 90 | cy.wait(1500); 91 | cy.getIframeBody().contains('Tweet'); 92 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 93 | }); 94 | }); 95 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterTimelineEmbed.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterTimelineEmbed from '../../../components/TwitterTimelineEmbed'; 4 | 5 | describe('Twitter Timeline', () => { 6 | it('should render timeline with screenName', () => { 7 | mount( 8 | 13 | ); 14 | cy.wait(4000); 15 | cy.getIframeBody().contains('Twitter'); 16 | cy.getIframeBody().contains('Tweets by '); 17 | }); 18 | 19 | it('should render timeline with userId', () => { 20 | mount( 21 | 26 | ); 27 | cy.wait(4000); 28 | cy.getIframeBody().contains('Twitter'); 29 | cy.getIframeBody().contains('Tweets by '); 30 | }); 31 | 32 | it('should render timeline likes with screenName', () => { 33 | mount( 34 | 39 | ); 40 | cy.wait(4000); 41 | cy.getIframeBody().contains('Twitter'); 42 | cy.getIframeBody().contains('liked by'); 43 | }); 44 | 45 | it('should render timeline likes with userId', () => { 46 | mount( 47 | 52 | ); 53 | cy.wait(4000); 54 | cy.getIframeBody().contains('Twitter'); 55 | cy.getIframeBody().contains('liked by'); 56 | }); 57 | 58 | it('should render timeline list with owner screen name slug', () => { 59 | mount( 60 | 66 | ); 67 | cy.wait(4000); 68 | cy.getIframeBody().contains('Twitter'); 69 | cy.getIframeBody().contains('BreakingNews'); 70 | cy.getIframeBody().contains('A Twitter list by'); 71 | }); 72 | 73 | it('should render timeline list with list id', () => { 74 | mount( 75 | 80 | ); 81 | cy.wait(4000); 82 | cy.getIframeBody().contains('Twitter'); 83 | cy.getIframeBody().contains('meetup-20100301'); 84 | cy.getIframeBody().contains('A Twitter list by'); 85 | }); 86 | 87 | it('should render timeline collection with id', () => { 88 | mount( 89 | 94 | ); 95 | cy.wait(4000); 96 | cy.getIframeBody().contains('Twitter'); 97 | cy.getIframeBody().contains('Dyeing the Chicago River'); 98 | cy.getIframeBody().contains('Curated Tweets by'); 99 | }); 100 | 101 | it('should render timeline collection with url', () => { 102 | mount( 103 | 108 | ); 109 | cy.wait(4000); 110 | cy.getIframeBody().contains('Twitter'); 111 | cy.getIframeBody().contains('Dyeing the Chicago River'); 112 | cy.getIframeBody().contains('Curated Tweets by'); 113 | }); 114 | 115 | it('should render timeline collection with profile url', () => { 116 | mount( 117 | 122 | ); 123 | cy.wait(4000); 124 | cy.getIframeBody().contains('Twitter'); 125 | cy.getIframeBody().contains('Rahul Kadam'); 126 | }); 127 | 128 | it('should render timeline collection with list url', () => { 129 | mount( 130 | 135 | ); 136 | cy.wait(4000); 137 | cy.getIframeBody().contains('Twitter'); 138 | cy.getIframeBody().contains('Social Media'); 139 | cy.getIframeBody().contains('A Twitter list by'); 140 | }); 141 | 142 | it('should render timeline collection with Likes url', () => { 143 | mount( 144 | 149 | ); 150 | cy.wait(4000); 151 | cy.getIframeBody().contains('Twitter'); 152 | cy.getIframeBody().contains('liked by'); 153 | }); 154 | 155 | it('should render timeline widget id', () => { 156 | mount( 157 | 162 | ); 163 | cy.wait(4000); 164 | cy.getIframeBody().contains('National Park Tweets'); 165 | cy.getIframeBody().contains('Curated Tweets by'); 166 | }); 167 | 168 | it('should render timeline widget with auto height', () => { 169 | mount( 170 |
171 | 176 |
177 | ); 178 | cy.wait(4000); 179 | cy.getIframeBody().contains('National Park Tweets'); 180 | cy.getIframeBody().contains('Curated Tweets by'); 181 | }); 182 | 183 | it('should render timeline widget with dark theme', () => { 184 | mount( 185 | 190 | ); 191 | cy.wait(4000); 192 | cy.getIframeBody().contains('National Park Tweets'); 193 | cy.getIframeBody().contains('Curated Tweets by'); 194 | }); 195 | 196 | it('should render timeline widget with light theme', () => { 197 | mount( 198 | 203 | ); 204 | cy.wait(4000); 205 | cy.getIframeBody().contains('National Park Tweets'); 206 | cy.getIframeBody().contains('Curated Tweets by'); 207 | }); 208 | 209 | it('should render timeline widget with custom border', () => { 210 | mount( 211 | 217 | ); 218 | cy.wait(4000); 219 | cy.getIframeBody().contains('National Park Tweets'); 220 | cy.getIframeBody().contains('Curated Tweets by'); 221 | }); 222 | 223 | it('should render timeline widget with no header', () => { 224 | mount( 225 | 232 | ); 233 | cy.wait(4000); 234 | cy.getIframeBody().contains('National Park Tweets'); 235 | cy.getIframeBody().contains('Curated Tweets by'); 236 | }); 237 | 238 | it('should render timeline widget with no footer', () => { 239 | mount( 240 | 247 | ); 248 | cy.wait(4000); 249 | cy.getIframeBody().contains('National Park Tweets'); 250 | cy.getIframeBody().contains('Curated Tweets by'); 251 | }); 252 | 253 | it('should render timeline widget with no header and no footer', () => { 254 | mount( 255 | 263 | ); 264 | cy.wait(4000); 265 | cy.getIframeBody().contains('National Park Tweets'); 266 | cy.getIframeBody().contains('Curated Tweets by'); 267 | }); 268 | 269 | it('should render timeline widget with no border', () => { 270 | mount( 271 | 278 | ); 279 | cy.wait(4000); 280 | cy.getIframeBody().contains('National Park Tweets'); 281 | cy.getIframeBody().contains('Curated Tweets by'); 282 | }); 283 | 284 | it('should render timeline widget with no scrollbar', () => { 285 | mount( 286 | 293 | ); 294 | cy.wait(4000); 295 | cy.getIframeBody().contains('National Park Tweets'); 296 | cy.getIframeBody().contains('Curated Tweets by'); 297 | }); 298 | 299 | it('should render timeline transparent background', () => { 300 | mount( 301 | 308 | ); 309 | cy.wait(4000); 310 | cy.getIframeBody().contains('National Park Tweets'); 311 | cy.getIframeBody().contains('Curated Tweets by'); 312 | }); 313 | 314 | it('should render timeline with custom language', () => { 315 | mount( 316 | 322 | ); 323 | cy.wait(4000); 324 | cy.getIframeBody().contains('National Park Tweets'); 325 | cy.getIframeBody().contains('द्वारा संजोए गए ट्वीट'); 326 | }); 327 | 328 | it('should render timeline with placeholder', () => { 329 | mount( 330 | 337 | ); 338 | cy.contains('Loading'); 339 | cy.wait(4000); 340 | cy.getIframeBody().contains('National Park Tweets'); 341 | cy.getIframeBody().contains('द्वारा संजोए गए ट्वीट'); 342 | }); 343 | 344 | it('should render timeline with custom placeholder', () => { 345 | mount( 346 | 360 | Hello I am custom placeholder 361 |
362 | } 363 | /> 364 | ); 365 | cy.contains('Hello I am custom placeholder'); 366 | cy.wait(4000); 367 | cy.getIframeBody().contains('National Park Tweets'); 368 | cy.getIframeBody().contains('द्वारा संजोए गए ट्वीट'); 369 | }); 370 | 371 | it('should render timeline with onLoadd', () => { 372 | const callback = cy.stub(); 373 | mount( 374 | 381 | ); 382 | cy.wait(4000); 383 | cy.getIframeBody().contains('National Park Tweets'); 384 | cy.getIframeBody().contains('द्वारा संजोए गए ट्वीट'); 385 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 386 | }); 387 | }); 388 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterTweetEmbed.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterTweetEmbed from '../../../components/TwitterTweetEmbed'; 4 | 5 | describe('Twitter Tweet Embed', () => { 6 | it('should render tweet embed with tweetId 1', () => { 7 | mount(); 8 | cy.wait(10000); 9 | cy.getIframeBody().contains('Joe Wright'); 10 | }); 11 | 12 | it('should render tweet embed with tweetId 2', () => { 13 | mount(); 14 | cy.wait(5000); 15 | cy.getIframeBody().contains('Cassidy'); 16 | }); 17 | 18 | it('should render tweet embed with placeholder', () => { 19 | mount( 20 | 21 | ); 22 | cy.contains('Loading'); 23 | cy.wait(5000); 24 | cy.getIframeBody().contains('Cassidy'); 25 | }); 26 | 27 | it('should render tweet embed with custom placeholder', () => { 28 | mount( 29 | 40 | Hello I am custom placeholder 41 |
42 | } 43 | /> 44 | ); 45 | cy.contains('Hello I am custom placeholder'); 46 | cy.wait(5000); 47 | cy.getIframeBody().contains('Cassidy'); 48 | }); 49 | 50 | it('should render tweet embed with onLoad', () => { 51 | const callback = cy.stub(); 52 | mount( 53 | 54 | ); 55 | cy.wait(5000); 56 | cy.getIframeBody().contains('Cassidy'); 57 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /src/tests/cypress/components/TwitterVideoEmbed.spec.tsx: -------------------------------------------------------------------------------- 1 | import { mount } from '@cypress/react'; 2 | import React from 'react'; 3 | import TwitterVideoEmbed from '../../../components/TwitterVideoEmbed'; 4 | 5 | describe('Twitter Video Embed', () => { 6 | it('should render video embed with id', () => { 7 | mount(); 8 | cy.wait(4000); 9 | cy.getIframeBody().contains('Twitter'); 10 | cy.getIframeBody().contains( 11 | "You can now shoot, edit and share video on Twitter. Capture life's most moving moments from your perspective." 12 | ); 13 | }); 14 | 15 | it('should render video embed with placeholder', () => { 16 | mount(); 17 | cy.contains('Loading'); 18 | cy.wait(4000); 19 | cy.getIframeBody().contains('Twitter'); 20 | cy.getIframeBody().contains( 21 | "You can now shoot, edit and share video on Twitter. Capture life's most moving moments from your perspective." 22 | ); 23 | }); 24 | 25 | it('should render video embed with custom placeholder', () => { 26 | mount( 27 | 38 | Hello I am custom placeholder 39 |
40 | } 41 | /> 42 | ); 43 | cy.contains('Hello I am custom placeholder'); 44 | cy.wait(4000); 45 | cy.getIframeBody().contains('Twitter'); 46 | cy.getIframeBody().contains( 47 | "You can now shoot, edit and share video on Twitter. Capture life's most moving moments from your perspective." 48 | ); 49 | }); 50 | 51 | it('should render video embed with onLoad', () => { 52 | const callback = cy.stub(); 53 | mount(); 54 | cy.wait(4000); 55 | cy.getIframeBody().contains('Twitter'); 56 | cy.getIframeBody().contains( 57 | "You can now shoot, edit and share video on Twitter. Capture life's most moving moments from your perspective." 58 | ); 59 | cy.waitUntil(() => expect(callback.callCount).to.eq(1)); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /src/typings.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Default CSS definition for typescript, 3 | * will be overridden with file-specific definitions by rollup 4 | */ 5 | declare module '*.css' { 6 | const content: { [className: string]: string }; 7 | export default content; 8 | } 9 | 10 | type SvgrComponent = React.StatelessComponent>; 11 | 12 | declare module '*.svg' { 13 | const svgUrl: string; 14 | const svgComponent: SvgrComponent; 15 | export default svgUrl; 16 | export { svgComponent as ReactComponent }; 17 | } 18 | 19 | declare namespace Cypress { 20 | interface Chainable { 21 | getIframeBody(): Chainable; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "dist", 4 | "module": "esnext", 5 | "lib": ["dom", "esnext"], 6 | "moduleResolution": "node", 7 | "jsx": "react", 8 | "sourceMap": true, 9 | "declaration": true, 10 | "esModuleInterop": true, 11 | "noImplicitReturns": true, 12 | "noImplicitThis": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "suppressImplicitAnyIndexErrors": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "allowSyntheticDefaultImports": true, 19 | "types": ["cypress", "cypress-wait-until"] 20 | }, 21 | "include": ["src"], 22 | "exclude": ["node_modules", "dist", "example"] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs" 5 | } 6 | } --------------------------------------------------------------------------------