├── docs ├── CNAME ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json ├── asset-manifest.json ├── static │ ├── css │ │ ├── main.2c906112.chunk.css │ │ └── main.2c906112.chunk.css.map │ └── js │ │ ├── runtime-main.4d7f5426.js │ │ ├── 2.82226bf5.chunk.js.LICENSE.txt │ │ ├── 3.764ffdad.chunk.js │ │ ├── 3.764ffdad.chunk.js.map │ │ └── runtime-main.4d7f5426.js.map └── index.html ├── packages ├── playground │ ├── docs │ │ ├── tour.md │ │ ├── popover.md │ │ ├── utils.md │ │ ├── intro.md │ │ └── mask.md │ ├── .env │ ├── public │ │ ├── robots.txt │ │ ├── favicon.ico │ │ ├── logo192.png │ │ ├── logo512.png │ │ ├── manifest.json │ │ └── index.html │ ├── src │ │ ├── components │ │ │ ├── Mask │ │ │ │ ├── index.js │ │ │ │ └── README.md │ │ │ ├── Popover │ │ │ │ └── index.js │ │ │ ├── TourProvider │ │ │ │ └── index.js │ │ │ ├── hooks.js │ │ │ └── styles.css │ │ └── styleguide │ │ │ └── Logo.js │ ├── README.md │ ├── .gitignore │ ├── tsconfig.json │ ├── styleguide │ │ ├── asset-manifest.json │ │ ├── index.html │ │ ├── static │ │ │ └── css │ │ │ │ ├── main.2b8cbd1d.chunk.css │ │ │ │ └── main.2b8cbd1d.chunk.css.map │ │ └── build │ │ │ ├── bundle.d27f85f7.js │ │ │ └── 2.052a1ca7.js.LICENSE.txt │ ├── styleguide.config.js │ └── package.json ├── mask │ ├── .gitignore │ ├── src │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── Mask.tsx │ ├── .github │ │ └── workflows │ │ │ ├── size.yml │ │ │ └── main.yml │ ├── test │ │ └── blah.test.tsx │ ├── LICENSE │ ├── package.json │ ├── tsconfig.json │ └── README.md ├── popover │ ├── .gitignore │ ├── src │ │ ├── index.tsx │ │ ├── styles.tsx │ │ └── Popover.tsx │ ├── .github │ │ └── workflows │ │ │ ├── size.yml │ │ │ └── main.yml │ ├── test │ │ └── blah.test.tsx │ ├── LICENSE │ ├── package.json │ ├── tsconfig.json │ └── README.md ├── tour │ ├── .gitignore │ ├── src │ │ ├── index.tsx │ │ ├── Badge.tsx │ │ ├── Content.tsx │ │ ├── Close.tsx │ │ ├── Context.tsx │ │ ├── Keyboard.tsx │ │ ├── types.tsx │ │ ├── styles.tsx │ │ ├── hooks.tsx │ │ ├── Navigation.tsx │ │ └── Tour.tsx │ ├── .github │ │ └── workflows │ │ │ ├── size.yml │ │ │ └── main.yml │ ├── test │ │ └── blah.test.tsx │ ├── LICENSE │ ├── tsconfig.json │ └── package.json └── utils │ ├── .gitignore │ ├── .github │ └── workflows │ │ ├── size.yml │ │ └── main.yml │ ├── test │ └── blah.test.tsx │ ├── src │ ├── smoothScroll.tsx │ ├── Portal.tsx │ ├── useRect.tsx │ ├── index.tsx │ └── Observables.tsx │ ├── LICENSE │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── demo ├── public │ ├── favicon.ico │ ├── logo192.png │ ├── logo512.png │ ├── robots.txt │ ├── manifest.json │ └── index.html ├── src │ ├── Row.js │ ├── Image.js │ ├── setupTests.js │ ├── Footer.js │ ├── Section.js │ ├── Scrollable.js │ ├── Box.js │ ├── reportWebVitals.js │ ├── Text.js │ ├── settings.js │ ├── Heading.js │ ├── index.js │ ├── Tooltip.js │ ├── Tabs.js │ ├── Button.js │ ├── Dropdown.js │ ├── styles.css │ ├── logo.svg │ ├── App.js │ └── Logo.js ├── .gitignore ├── package.json └── README.md ├── .eslintignore ├── .prettierrc.json ├── .babelrc ├── .gitignore ├── .eslintrc.json ├── index.js ├── package.json └── README.md /docs/CNAME: -------------------------------------------------------------------------------- 1 | reactour.js.org -------------------------------------------------------------------------------- /packages/playground/docs/tour.md: -------------------------------------------------------------------------------- 1 | # hola 2 | -------------------------------------------------------------------------------- /packages/playground/docs/popover.md: -------------------------------------------------------------------------------- 1 | # hola 2 | -------------------------------------------------------------------------------- /packages/playground/docs/utils.md: -------------------------------------------------------------------------------- 1 | # hola 2 | -------------------------------------------------------------------------------- /packages/playground/.env: -------------------------------------------------------------------------------- 1 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /packages/mask/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /packages/popover/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /packages/tour/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /packages/utils/.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | .DS_Store 3 | node_modules 4 | .cache 5 | dist 6 | -------------------------------------------------------------------------------- /docs/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/docs/favicon.ico -------------------------------------------------------------------------------- /docs/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/docs/logo192.png -------------------------------------------------------------------------------- /docs/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/docs/logo512.png -------------------------------------------------------------------------------- /docs/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /demo/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/demo/public/favicon.ico -------------------------------------------------------------------------------- /demo/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/demo/public/logo192.png -------------------------------------------------------------------------------- /demo/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/demo/public/logo512.png -------------------------------------------------------------------------------- /demo/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /docs 3 | /dist 4 | /lib 5 | .idea 6 | .DS_Store 7 | /webpack.config.babel.js 8 | -------------------------------------------------------------------------------- /packages/playground/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /packages/playground/src/components/Mask/index.js: -------------------------------------------------------------------------------- 1 | import { Mask } from '@reactour/mask' 2 | export default Mask 3 | -------------------------------------------------------------------------------- /packages/playground/src/components/Popover/index.js: -------------------------------------------------------------------------------- 1 | import { Popover } from '@reactour/popover' 2 | export default Popover 3 | -------------------------------------------------------------------------------- /packages/playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/packages/playground/public/favicon.ico -------------------------------------------------------------------------------- /packages/playground/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/packages/playground/public/logo192.png -------------------------------------------------------------------------------- /packages/playground/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hosein2398/reactour/master/packages/playground/public/logo512.png -------------------------------------------------------------------------------- /packages/playground/src/components/TourProvider/index.js: -------------------------------------------------------------------------------- 1 | import { TourProvider } from '@reactour/tour' 2 | export default TourProvider 3 | -------------------------------------------------------------------------------- /demo/src/Row.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | export default styled.div` 4 | display: flex; 5 | flex-wrap: wrap; 6 | ` 7 | -------------------------------------------------------------------------------- /packages/mask/src/index.tsx: -------------------------------------------------------------------------------- 1 | import Mask from './Mask' 2 | import { StylesObj } from './styles' 3 | export default Mask 4 | export { Mask, StylesObj as MaskStylesObj } 5 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "es5", 4 | "bracketSpacing": true, 5 | "tabWidth": 2, 6 | "printWidth": 80, 7 | "semi": false 8 | } 9 | -------------------------------------------------------------------------------- /demo/src/Image.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | export default styled.img` 4 | border: 0; 5 | display: block; 6 | max-width: 100%; 7 | width: 100%; 8 | ` 9 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/env", "@babel/react"], 3 | "plugins": [ 4 | "@babel/plugin-proposal-class-properties", 5 | "@babel/plugin-syntax-dynamic-import" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /packages/tour/src/index.tsx: -------------------------------------------------------------------------------- 1 | import Tour from './Tour' 2 | import TourContext, { TourProvider, useTour } from './Context' 3 | export default Tour 4 | export { Tour, TourContext, TourProvider, useTour } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | .env.local 4 | .env.development.local 5 | .env.test.local 6 | .env.production.local 7 | 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | 12 | .vscode/* -------------------------------------------------------------------------------- /demo/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /packages/playground/docs/intro.md: -------------------------------------------------------------------------------- 1 |

2 | Reactour 3 |

4 |

5 | An SVG mask that cover all the window contents except the one specified by certain position and sizes values 6 |

7 | -------------------------------------------------------------------------------- /packages/playground/docs/mask.md: -------------------------------------------------------------------------------- 1 |

2 | Reactour 3 |

4 |

5 | An SVG mask that cover all the window contents except the one specified by certain position and sizes values 6 |

7 | -------------------------------------------------------------------------------- /demo/src/Footer.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | import { themeColors } from './settings' 3 | 4 | export default styled.footer` 5 | width: 100%; 6 | background-color: ${themeColors.light}; 7 | text-align: center; 8 | padding-bottom: 3em; 9 | padding-top: 1em; 10 | ` 11 | -------------------------------------------------------------------------------- /demo/src/Section.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | export default styled.section` 4 | min-height: 100vh; 5 | max-width: 1024px; 6 | margin: auto; 7 | padding: 1em; 8 | text-align: ${props => 9 | props.center ? 'center' : props.align ? props.align : 'left'}; 10 | ` 11 | -------------------------------------------------------------------------------- /packages/popover/src/index.tsx: -------------------------------------------------------------------------------- 1 | import Popover from './Popover' 2 | import { PositionType, PositionProps } from './Popover' 3 | import { StylesObj } from './styles' 4 | export default Popover 5 | export { 6 | Popover, 7 | PositionType as Position, 8 | PositionProps, 9 | StylesObj as PopoverStylesObj, 10 | } 11 | -------------------------------------------------------------------------------- /packages/mask/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/popover/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/tour/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/utils/.github/workflows/size.yml: -------------------------------------------------------------------------------- 1 | name: size 2 | on: [pull_request] 3 | jobs: 4 | size: 5 | runs-on: ubuntu-latest 6 | env: 7 | CI_JOB_NUMBER: 1 8 | steps: 9 | - uses: actions/checkout@v1 10 | - uses: andresz1/size-limit-action@v1 11 | with: 12 | github_token: ${{ secrets.GITHUB_TOKEN }} 13 | -------------------------------------------------------------------------------- /packages/mask/test/blah.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/tour/test/blah.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/utils/test/blah.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/popover/test/blah.test.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as ReactDOM from 'react-dom'; 3 | import { Thing } from '../src'; 4 | 5 | describe('it', () => { 6 | it('renders without crashing', () => { 7 | const div = document.createElement('div'); 8 | ReactDOM.render(, div); 9 | ReactDOM.unmountComponentAtNode(div); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /demo/src/Scrollable.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | import { themeColors } from './settings' 3 | 4 | export default styled.aside` 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | bottom: 0; 9 | color: ${themeColors.dark}; 10 | width: 130px; 11 | overflow-y: scroll; 12 | -ms-overflow-style: none; 13 | &::-webkit-scrollbar { 14 | width: 0 !important; 15 | } 16 | ` 17 | -------------------------------------------------------------------------------- /demo/src/Box.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | export default styled.div` 4 | flex: 0 0 100%; 5 | padding: 1em; 6 | text-align: ${props => 7 | props.center ? 'center' : props.align ? props.align : 'left'}; 8 | @media (min-width: 40em) { 9 | flex: ${props => (props.width ? `0 0 ${props.width}` : 1)}; 10 | width: ${props => (props.width ? props.width : '100%')}; 11 | } 12 | ` 13 | -------------------------------------------------------------------------------- /packages/playground/README.md: -------------------------------------------------------------------------------- 1 |

2 | Reactour 3 |

4 |

5 | The playground where to play with the Tour and its parts 6 |

7 | 8 | This package uses [react-styleguidist](https://react-styleguidist.js.org/) to document and show examples of each part of `@reactour` ecosystem. Take a look [online](https://reactour.vercel.app/) 9 | -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /demo/src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /packages/playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /packages/tour/src/Badge.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from '@emotion/react' 3 | import React from 'react' 4 | import { StylesObj, stylesMatcher } from './styles' 5 | 6 | const Badge: React.FC = ({ styles = {}, children }) => { 7 | const getStyles = stylesMatcher(styles) 8 | return {children} 9 | } 10 | 11 | export type BadgeProps = { 12 | styles?: StylesObj 13 | } 14 | 15 | export default Badge 16 | -------------------------------------------------------------------------------- /demo/src/Text.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | import { fontFamily, themeColors } from './settings' 3 | 4 | export default styled.p` 5 | font-family: ${fontFamily}; 6 | margin: 0; 7 | color: ${props => 8 | props.color 9 | ? themeColors[props.color] 10 | ? themeColors[props.color] 11 | : props.color 12 | : themeColors.black}; 13 | line-height: 1.5; 14 | font-size: ${props => (props.size ? props.size : 'inherit')}; 15 | ` 16 | -------------------------------------------------------------------------------- /demo/src/settings.js: -------------------------------------------------------------------------------- 1 | export const fontFamily = `-apple-system, BlinkMacSystemFont, 2 | 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 3 | 'Helvetical Neue', sans-serif;` 4 | 5 | export const headingSizes = [ 6 | '2rem', 7 | '1.5rem', 8 | '1.25rem', 9 | '1rem', 10 | '.875rem', 11 | '.75rem', 12 | ] 13 | 14 | export const themeColors = { 15 | dark: '#1c8f9e', 16 | light: '#5cb7b7', 17 | black: '#2d2323', 18 | } 19 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "prettier" 4 | ], 5 | "rules": { 6 | "prettier/prettier": ["error"] 7 | }, 8 | "env": { 9 | "browser": true, 10 | "es6": true 11 | }, 12 | "parser": "babel-eslint", 13 | "parserOptions": { 14 | "ecmaVersion": 6, 15 | "sourceType": "module", 16 | "ecmaFeatures": { 17 | "jsx": true 18 | } 19 | }, 20 | "extends": [ 21 | "prettier" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /demo/src/Heading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from '@emotion/styled' 3 | import { fontFamily, headingSizes, themeColors } from './settings' 4 | 5 | export default styled(({ h, ...props }) => { 6 | const H = `h${h}` 7 | return 8 | })` 9 | font-size: ${props => headingSizes[props.h - 1]}; 10 | font-family: ${fontFamily}; 11 | font-weight: 300; 12 | color: ${props => themeColors[props.color] || themeColors.dark}; 13 | letter-spacing: 1px; 14 | line-height: 1.375; 15 | ` 16 | -------------------------------------------------------------------------------- /demo/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import './styles.css' 4 | import App from './App' 5 | import reportWebVitals from './reportWebVitals' 6 | 7 | ReactDOM.render( 8 | 9 | 10 | , 11 | document.getElementById('root') 12 | ) 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals() 18 | -------------------------------------------------------------------------------- /docs/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demo/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /packages/tour/src/Content.tsx: -------------------------------------------------------------------------------- 1 | import React, { Dispatch } from 'react' 2 | 3 | const Content: React.FC = ({ 4 | content, 5 | setCurrentStep, 6 | transition, 7 | currentStep, 8 | setIsOpen, 9 | }) => { 10 | return typeof content === 'function' 11 | ? content({ setCurrentStep, transition, currentStep, setIsOpen }) 12 | : content 13 | } 14 | 15 | type ContentProps = { 16 | content: any 17 | setCurrentStep: Dispatch> 18 | setIsOpen?: Dispatch> 19 | currentStep: number 20 | transition?: boolean 21 | } 22 | 23 | export default Content 24 | -------------------------------------------------------------------------------- /packages/playground/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /demo/src/Tooltip.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | import { themeColors } from './settings' 3 | 4 | export default styled.span` 5 | position: relative; 6 | color: ${themeColors.dark}; 7 | 8 | &:after { 9 | content: attr(data-tooltip); 10 | position: absolute; 11 | transition: 0.3s; 12 | background-color: ${themeColors.dark}; 13 | color: white; 14 | left: 0; 15 | bottom: 100%; 16 | box-shadow: 0 0.5em 3em rgba(255, 255, 255, 0.3); 17 | opacity: 0; 18 | padding: 0.2em 0.5em; 19 | border-radius: 2px; 20 | font-size: 0.7em; 21 | } 22 | 23 | &:hover:after { 24 | opacity: 1; 25 | } 26 | ` 27 | -------------------------------------------------------------------------------- /packages/playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/playground/styleguide/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.2b8cbd1d.chunk.css", 4 | "main.js": "/build/main.06097c1a.js", 5 | "runtime-main.js": "/build/bundle.d27f85f7.js", 6 | "build/2.052a1ca7.js": "/build/2.052a1ca7.js", 7 | "build/2.052a1ca7.js.LICENSE.txt": "/build/2.052a1ca7.js.LICENSE.txt", 8 | "index.html": "/index.html", 9 | "static/css/main.2b8cbd1d.chunk.css.map": "/static/css/main.2b8cbd1d.chunk.css.map" 10 | }, 11 | "entrypoints": [ 12 | "build/bundle.d27f85f7.js", 13 | "build/2.052a1ca7.js", 14 | "static/css/main.2b8cbd1d.chunk.css", 15 | "build/main.06097c1a.js" 16 | ] 17 | } -------------------------------------------------------------------------------- /packages/playground/styleguide/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Reactour Playground Style Guide 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/playground/styleguide/static/css/main.2b8cbd1d.chunk.css: -------------------------------------------------------------------------------- 1 | .icon{width:70px;height:70px}.wrapper{display:-webkit-flex;display:flex;-webkit-flex-wrap:wrap;flex-wrap:wrap;-webkit-justify-content:space-between;justify-content:space-between}.scroll-demo{width:100px}.scroll-demo .icon{margin-bottom:200px}.custom-btn{padding:0;border:0;background:none;font-size:.6em;text-align:left;line-height:.5;cursor:pointer;margin-right:1em}.custom-btn:hover{text-decoration:underline}.custom-btn p:after,.custom-btn p:before{content:'"'}.open-button{border:0;background-color:#8e949a;color:#fff;padding:.5em 1em;font:inherit;font-size:.85em;border-radius:2em;margin-bottom:1em;cursor:pointer}.open-button:hover{background-color:#92a8b4} 2 | /*# sourceMappingURL=main.2b8cbd1d.chunk.css.map */ -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const launchit = require('launchit') 2 | 3 | const apps = { 4 | '@reactour/utils': { 5 | path: '.', 6 | script: 'yarn workspace @reactour/utils start', 7 | }, 8 | '@reactour/mask': { 9 | path: '.', 10 | script: 'yarn workspace @reactour/mask start', 11 | waitBefore: 5000, 12 | }, 13 | '@reactour/popover': { 14 | path: '.', 15 | script: 'yarn workspace @reactour/popover start', 16 | waitBefore: 5000, 17 | }, 18 | '@reactour/tour': { 19 | path: '.', 20 | script: 'yarn workspace @reactour/tour start', 21 | waitBefore: 5000, 22 | }, 23 | '@reactour/playground': { 24 | path: '.', 25 | script: 'yarn workspace @reactour/playground start', 26 | waitBefore: 8000, 27 | }, 28 | } 29 | 30 | launchit({ apps }) 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "workspaces": { 4 | "packages": [ 5 | "packages/*" 6 | ], 7 | "nohoist": [ 8 | "playground/react/**" 9 | ] 10 | }, 11 | "prettier": { 12 | "printWidth": 80, 13 | "semi": false, 14 | "singleQuote": true, 15 | "trailingComma": "es5" 16 | }, 17 | "scripts": { 18 | "utils": "yarn workspace @reactour/utils start", 19 | "mask": "yarn workspace @reactour/mask start", 20 | "popover": "yarn workspace @reactour/popover start", 21 | "tour": "yarn workspace @reactour/tour start", 22 | "playground": "yarn workspace @reactour/playground start", 23 | "start": "node index" 24 | }, 25 | "dependencies": { 26 | "concurrently": "6.2.1" 27 | }, 28 | "devDependencies": { 29 | "launchit": "1.1.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/playground/styleguide.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | skipComponentsWithoutExample: true, 5 | propsParser: require('react-docgen-typescript').withCustomConfig( 6 | './tsconfig.json' 7 | ).parse, 8 | styleguideComponents: { 9 | LogoRenderer: path.join(__dirname, 'src/styleguide/Logo.js'), 10 | }, 11 | styles: { 12 | StyleGuide: { 13 | '@global body': { 14 | fontFamily: 'Roboto', 15 | }, 16 | }, 17 | }, 18 | template: { 19 | head: { 20 | links: [ 21 | { 22 | rel: 'stylesheet', 23 | href: 'https://fonts.googleapis.com/css?family=Roboto', 24 | }, 25 | ], 26 | }, 27 | }, 28 | theme: { 29 | fontFamily: { 30 | base: '"Roboto", sans-serif', 31 | }, 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /packages/mask/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/tour/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/popover/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/utils/.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push] 3 | jobs: 4 | build: 5 | name: Build, lint, and test on Node ${{ matrix.node }} and ${{ matrix.os }} 6 | 7 | runs-on: ${{ matrix.os }} 8 | strategy: 9 | matrix: 10 | node: ['10.x', '12.x', '14.x'] 11 | os: [ubuntu-latest, windows-latest, macOS-latest] 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v2 16 | 17 | - name: Use Node ${{ matrix.node }} 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: ${{ matrix.node }} 21 | 22 | - name: Install deps and build (with cache) 23 | uses: bahmutov/npm-install@v1 24 | 25 | - name: Lint 26 | run: yarn lint 27 | 28 | - name: Test 29 | run: yarn test --ci --coverage --maxWorkers=2 30 | 31 | - name: Build 32 | run: yarn build 33 | -------------------------------------------------------------------------------- /packages/utils/src/smoothScroll.tsx: -------------------------------------------------------------------------------- 1 | // https://stackoverflow.com/questions/46795955/how-to-know-scroll-to-element-is-done-in-javascript 2 | export function smoothScroll( 3 | elem: Element | null, 4 | options: ScrollIntoViewOptions 5 | ) { 6 | return new Promise(resolve => { 7 | if (!(elem instanceof Element)) { 8 | throw new TypeError('Argument 1 must be an Element') 9 | } 10 | let same = 0 11 | let lastPos: undefined | null | number = null 12 | const scrollOptions = Object.assign({ behavior: 'smooth' }, options) 13 | 14 | elem.scrollIntoView(scrollOptions) 15 | requestAnimationFrame(check) 16 | 17 | function check() { 18 | const newPos = elem?.getBoundingClientRect().top 19 | if (newPos === lastPos) { 20 | if (same++ > 2) { 21 | return resolve(null) 22 | } 23 | } else { 24 | same = 0 25 | lastPos = newPos 26 | } 27 | requestAnimationFrame(check) 28 | } 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /packages/playground/src/components/hooks.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | 3 | export function useIntersectionObserver( 4 | elementRef, 5 | { threshold = 0, root = null, rootMargin = '0%', freezeOnceVisible = false } 6 | ) { 7 | // https://usehooks-typescript.com/react-hook/use-intersection-observer 8 | const [entry, setEntry] = useState() 9 | const frozen = entry && entry.isIntersecting && freezeOnceVisible 10 | const updateEntry = ([entry]) => { 11 | setEntry(entry) 12 | } 13 | useEffect(() => { 14 | const node = elementRef.current // DOM Ref 15 | const hasIOSupport = !!window.IntersectionObserver 16 | if (!hasIOSupport || frozen || !node) return 17 | const observerParams = { threshold, root, rootMargin } 18 | const observer = new IntersectionObserver(updateEntry, observerParams) 19 | observer.observe(node) 20 | return () => observer.disconnect() 21 | }, [elementRef, threshold, root, rootMargin, frozen]) 22 | return entry 23 | } 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | reactour 3 |

4 |

5 | Tourist Guide and a set of Assistants to travel into your React Components 6 |

7 | 8 | ### Packages 9 | 10 | [@reactour/mask](/packages/mask) a customizable Component to highlight certain element or area of the viewport 11 | 12 | [@reactour/popover](/packages/popover) a customizable Component to attach to an element or position of the viewport to show conten 13 | 14 | [@reactour/tour](/packages/tour) the main responsible package, which uses the other ones to highlight parts of your application from an array of steps 15 | 16 | [@reactour/utils](/packages/utils) a set of helper functions used by the other packages 17 | 18 | [@reactour/playground](/packages/playground) the place where all the stuff is visible working 19 | 20 | #### License 21 | 22 | MIT © [Lionel Tzatzkin](https://lionel.tzatzk.in) 23 | -------------------------------------------------------------------------------- /packages/playground/src/components/styles.css: -------------------------------------------------------------------------------- 1 | .icon { 2 | width: 70px; 3 | height: 70px; 4 | } 5 | 6 | .wrapper { 7 | display: flex; 8 | flex-wrap: wrap; 9 | justify-content: space-between; 10 | } 11 | 12 | .scroll-demo { 13 | width: 100px; 14 | } 15 | 16 | .scroll-demo .icon { 17 | margin-bottom: 200px; 18 | } 19 | 20 | .custom-btn { 21 | padding: 0; 22 | border: 0; 23 | background: none; 24 | font-size: 0.6em; 25 | text-align: left; 26 | line-height: 0.5; 27 | cursor: pointer; 28 | margin-right: 1em; 29 | } 30 | .custom-btn:hover { 31 | text-decoration: underline; 32 | } 33 | .custom-btn p:before, 34 | .custom-btn p:after { 35 | content: '"'; 36 | } 37 | 38 | .open-button { 39 | border: 0; 40 | background-color: #8e949a; 41 | color: #fff; 42 | padding: 0.5em 1em; 43 | font: inherit; 44 | font-size: 0.85em; 45 | border-radius: 2em; 46 | margin-bottom: 1em; 47 | cursor: pointer; 48 | } 49 | 50 | .open-button:hover { 51 | background-color: #92a8b4; 52 | } 53 | -------------------------------------------------------------------------------- /packages/tour/src/Close.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from '@emotion/react' 3 | import React from 'react' 4 | import { StylesObj, stylesMatcher } from './styles' 5 | 6 | const Close: React.FC = ({ 7 | styles = {}, 8 | onClick, 9 | disabled, 10 | ...props 11 | }) => { 12 | const getStyles = stylesMatcher(styles) 13 | return ( 14 | 26 | ) 27 | } 28 | 29 | type CloseProps = { 30 | styles?: StylesObj 31 | onClick?: () => void 32 | disabled?: boolean 33 | } 34 | 35 | export default Close 36 | -------------------------------------------------------------------------------- /docs/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.2c906112.chunk.css", 4 | "main.js": "/static/js/main.94ccda81.chunk.js", 5 | "main.js.map": "/static/js/main.94ccda81.chunk.js.map", 6 | "runtime-main.js": "/static/js/runtime-main.4d7f5426.js", 7 | "runtime-main.js.map": "/static/js/runtime-main.4d7f5426.js.map", 8 | "static/js/2.82226bf5.chunk.js": "/static/js/2.82226bf5.chunk.js", 9 | "static/js/2.82226bf5.chunk.js.map": "/static/js/2.82226bf5.chunk.js.map", 10 | "static/js/3.764ffdad.chunk.js": "/static/js/3.764ffdad.chunk.js", 11 | "static/js/3.764ffdad.chunk.js.map": "/static/js/3.764ffdad.chunk.js.map", 12 | "index.html": "/index.html", 13 | "static/css/main.2c906112.chunk.css.map": "/static/css/main.2c906112.chunk.css.map", 14 | "static/js/2.82226bf5.chunk.js.LICENSE.txt": "/static/js/2.82226bf5.chunk.js.LICENSE.txt" 15 | }, 16 | "entrypoints": [ 17 | "static/js/runtime-main.4d7f5426.js", 18 | "static/js/2.82226bf5.chunk.js", 19 | "static/css/main.2c906112.chunk.css", 20 | "static/js/main.94ccda81.chunk.js" 21 | ] 22 | } -------------------------------------------------------------------------------- /packages/popover/src/styles.tsx: -------------------------------------------------------------------------------- 1 | export type StylesKeys = 'popover' 2 | 3 | export type StylesObj = { 4 | [key in StylesKeys]?: StyleFn 5 | } 6 | 7 | export type StyleFn = ( 8 | props: { [key: string]: any }, 9 | state?: { [key: string]: any } 10 | ) => React.CSSProperties 11 | 12 | export type Styles = { 13 | popover: StyleFn 14 | } 15 | 16 | export type StyleKey = keyof Styles 17 | 18 | export const defaultStyles: Styles = { 19 | popover: () => ({ 20 | position: 'fixed', 21 | maxWidth: 353, 22 | // minWidth: 150, 23 | backgroundColor: '#fff', 24 | padding: '24px 30px', 25 | boxShadow: '0 0.5em 3em rgba(0, 0, 0, 0.3)', 26 | color: 'inherit', 27 | zIndex: 100000, 28 | transition: 'transform 0.3s', 29 | top: 0, 30 | left: 0, 31 | }), 32 | } 33 | 34 | export type getStylesType = (key: StylesKeys, extra?: any) => {} 35 | 36 | export function stylesMatcher(styles: StylesObj) { 37 | return (key: StyleKey, state: {}): {} => { 38 | const base = defaultStyles[key](state) 39 | const custom = styles[key] 40 | return custom ? custom(base, state) : base 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/mask/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lionel Tzatzkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/tour/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lionel Tzatzkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/popover/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lionel Tzatzkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /packages/utils/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Lionel Tzatzkin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /demo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/styled": "11.3.0", 7 | "@reactour/tour": "0.2.0", 8 | "@testing-library/jest-dom": "^5.11.4", 9 | "@testing-library/react": "^11.1.0", 10 | "@testing-library/user-event": "^12.1.10", 11 | "body-scroll-lock": "4.0.0-beta.0", 12 | "modaaals": "0.9.0", 13 | "react": "^17.0.2", 14 | "react-dom": "^17.0.2", 15 | "react-scripts": "4.0.3", 16 | "web-vitals": "^1.0.1" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build && rm -rf ../docs && mv build ../docs", 21 | "test": "react-scripts test", 22 | "eject": "react-scripts eject" 23 | }, 24 | "eslintConfig": { 25 | "extends": [ 26 | "react-app", 27 | "react-app/jest" 28 | ] 29 | }, 30 | "browserslist": { 31 | "production": [ 32 | ">0.2%", 33 | "not dead", 34 | "not op_mini all" 35 | ], 36 | "development": [ 37 | "last 1 chrome version", 38 | "last 1 firefox version", 39 | "last 1 safari version" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/Tabs.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import PropTypes from 'prop-types' 3 | import styled from '@emotion/styled' 4 | import { themeColors } from './settings' 5 | 6 | const Trigger = styled.a` 7 | cursor: pointer; 8 | margin-right: 5px; 9 | color: ${themeColors.light}; 10 | ` 11 | 12 | const Tabs = ({ children }) => { 13 | const [active, setActive] = useState(0) 14 | 15 | return ( 16 | <> 17 |
18 | {children.map((_, i) => { 19 | return ( 20 | setActive(i)}> 21 | tab{i} 22 | 23 | ) 24 | })} 25 |
26 |
27 | {children.map((child, i) => 28 | React.cloneElement(child, { key: i, active: i === active }) 29 | )} 30 |
31 | 32 | ) 33 | } 34 | 35 | Tabs.propTypes = { 36 | children: PropTypes.node.isRequired, 37 | } 38 | 39 | Tabs.Tab = ({ children, active }) => { 40 | return active ? children : null 41 | } 42 | 43 | Tabs.Tab.propTypes = { 44 | children: PropTypes.node.isRequired, 45 | active: PropTypes.bool, 46 | } 47 | 48 | export default Tabs 49 | -------------------------------------------------------------------------------- /packages/utils/src/Portal.tsx: -------------------------------------------------------------------------------- 1 | import React, { useLayoutEffect, useState, useRef, ReactChild } from 'react' 2 | import { createPortal } from 'react-dom' 3 | 4 | const Portal: React.FC = ({ 5 | children, 6 | type = 'reactour-portal', 7 | }) => { 8 | let mountNode = useRef(null) 9 | let portalNode = useRef(null) 10 | let [, forceUpdate] = useState({}) 11 | 12 | useLayoutEffect(() => { 13 | if (!mountNode.current) return 14 | 15 | const ownerDocument = mountNode.current!.ownerDocument 16 | portalNode.current = ownerDocument?.createElement(type)! 17 | ownerDocument!.body.appendChild(portalNode.current) 18 | forceUpdate({}) 19 | 20 | return () => { 21 | if (portalNode.current && portalNode.current.ownerDocument) { 22 | portalNode.current.ownerDocument.body.removeChild(portalNode.current) 23 | } 24 | } 25 | }, [type]) 26 | 27 | return portalNode.current ? ( 28 | createPortal(children, portalNode.current) 29 | ) : ( 30 | 31 | ) 32 | } 33 | 34 | export type PortalProps = { 35 | children?: ReactChild 36 | type?: string 37 | } 38 | 39 | export default Portal 40 | -------------------------------------------------------------------------------- /docs/static/css/main.2c906112.chunk.css: -------------------------------------------------------------------------------- 1 | html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}body{margin:0;padding:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","Roboto","Oxygen","Ubuntu","Cantarell","Fira Sans","Droid Sans","Helvetical Neue",sans-serif}.helper,body{color:#2d2323}.helper{line-height:1.3}.mask{color:#021d1d;opacity:.7}[data-tour-elem=controls]{justify-content:center}.CustomHelper__wrapper{display:flex;background-color:#f8f8f8;max-width:490px;border-radius:6px}.CustomHelper__sidebar{background-color:#5cb7b7;flex:1 1;display:flex;justify-content:space-around;align-items:center;flex-direction:column;border-top-left-radius:6px;border-bottom-left-radius:6px;color:#fff;min-width:120px;min-height:200px;padding-top:2em;padding-bottom:2em}.CustomHelper__sidebar_img{width:80px;height:80px;margin-bottom:1em}.CustomHelper__sidebar_step{font-size:10px;font-weight:600;background-color:rgba(0,0,0,.1);text-transform:uppercase;padding:.5em 1em;border-radius:1em}.CustomHelper__content{flex:2 1;padding:2em 2em 5em;position:relative;background-color:#ffffe4;border-top-right-radius:6px;border-bottom-right-radius:6px}.CustomHelper__controls{position:absolute;bottom:2em;left:0;right:0}.CustomHelper__navArrow{margin-right:10px;margin-left:10px} 2 | /*# sourceMappingURL=main.2c906112.chunk.css.map */ -------------------------------------------------------------------------------- /demo/src/Button.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | import { fontFamily, headingSizes, themeColors } from './settings' 4 | 5 | const styles = ` 6 | border: 0; 7 | border-radius: 4px; 8 | color: white; 9 | padding: .5em 1em; 10 | font-family: ${fontFamily}; 11 | margin-left: .25em; 12 | margin-right: .25em; 13 | 14 | &:hover { 15 | opacity: .9; 16 | } 17 | ` 18 | const Button = styled.button` 19 | ${styles}; 20 | ` 21 | const Link = styled.a` 22 | ${styles}; 23 | ` 24 | 25 | const StyledButton = styled(Button)` 26 | font-size: ${props => (props.h ? headingSizes[props.h - 1] : 'inherit')}; 27 | background-color: ${props => themeColors[props.color] || themeColors.dark}; 28 | ` 29 | 30 | const StyledLink = styled(Link)` 31 | text-decoration: none; 32 | font-size: ${props => (props.h ? headingSizes[props.h - 1] : 'inherit')}; 33 | background: ${props => (props.bg ? themeColors[props.bg] : 'none')}; 34 | color: ${props => 35 | props.bg 36 | ? 'white' 37 | : props.color 38 | ? themeColors[props.color] 39 | : themeColors.black}; 40 | ${props => 41 | props.nospaces && 42 | ` 43 | display: inline-block; 44 | padding: 0; 45 | margin: 0; 46 | `}; 47 | ` 48 | 49 | export { StyledButton as Button } 50 | export { StyledLink as Link } 51 | -------------------------------------------------------------------------------- /packages/playground/styleguide/static/css/main.2b8cbd1d.chunk.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack://src/components/styles.css"],"names":[],"mappings":"AAAA,MACE,UAAW,CACX,WACF,CAEA,SACE,oBAAa,CAAb,YAAa,CACb,sBAAe,CAAf,cAAe,CACf,qCAA8B,CAA9B,6BACF,CAEA,aACE,WACF,CAEA,mBACE,mBACF,CAEA,YACE,SAAU,CACV,QAAS,CACT,eAAgB,CAChB,cAAgB,CAChB,eAAgB,CAChB,cAAgB,CAChB,cAAe,CACf,gBACF,CACA,kBACE,yBACF,CACA,yCAEE,WACF,CAEA,aACE,QAAS,CACT,wBAAyB,CACzB,UAAW,CACX,gBAAkB,CAClB,YAAa,CACb,eAAiB,CACjB,iBAAkB,CAClB,iBAAkB,CAClB,cACF,CAEA,mBACE,wBACF","file":"main.2b8cbd1d.chunk.css","sourcesContent":[".icon {\n width: 70px;\n height: 70px;\n}\n\n.wrapper {\n display: flex;\n flex-wrap: wrap;\n justify-content: space-between;\n}\n\n.scroll-demo {\n width: 100px;\n}\n\n.scroll-demo .icon {\n margin-bottom: 200px;\n}\n\n.custom-btn {\n padding: 0;\n border: 0;\n background: none;\n font-size: 0.6em;\n text-align: left;\n line-height: 0.5;\n cursor: pointer;\n margin-right: 1em;\n}\n.custom-btn:hover {\n text-decoration: underline;\n}\n.custom-btn p:before,\n.custom-btn p:after {\n content: '\"';\n}\n\n.open-button {\n border: 0;\n background-color: #8e949a;\n color: #fff;\n padding: 0.5em 1em;\n font: inherit;\n font-size: 0.85em;\n border-radius: 2em;\n margin-bottom: 1em;\n cursor: pointer;\n}\n\n.open-button:hover {\n background-color: #92a8b4;\n}\n"]} -------------------------------------------------------------------------------- /packages/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@reactour/playground", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@emotion/styled": "11.3.0", 7 | "@reactour/mask": "*", 8 | "@reactour/popover": "*", 9 | "@reactour/tour": "*", 10 | "@reactour/utils": "*", 11 | "@testing-library/jest-dom": "^5.14.1", 12 | "@testing-library/react": "^12.0.0", 13 | "@testing-library/user-event": "^13.2.1", 14 | "body-scroll-lock": "4.0.0-beta.0", 15 | "framer-motion": "4.1.17", 16 | "modaaals": "0.9.0", 17 | "react": "^17.0.2", 18 | "react-docgen-typescript": "2.1.0", 19 | "react-dom": "^17.0.2", 20 | "react-modal": "3.14.3", 21 | "react-router-dom": "5.2.1", 22 | "react-scripts": "4.0.3", 23 | "react-styleguidist": "11.1.7", 24 | "web-vitals": "^2.1.0" 25 | }, 26 | "scripts": { 27 | "start": "styleguidist server", 28 | "build": "styleguidist build", 29 | "styleguide": "styleguidist server", 30 | "styleguide:build": "styleguidist build" 31 | }, 32 | "eslintConfig": { 33 | "extends": [ 34 | "react-app" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/tour/src/Context.tsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react' 2 | import { useState } from 'react' 3 | import Tour from './Tour' 4 | import { ProviderProps, TourProps } from './types' 5 | 6 | const defaultState = { 7 | isOpen: false, 8 | setIsOpen: () => false, 9 | currentStep: 0, 10 | setCurrentStep: () => 0, 11 | steps: [], 12 | setSteps: () => [], 13 | disabledActions: false, 14 | setDisabledActions: () => false, 15 | } 16 | 17 | const TourContext = React.createContext(defaultState) 18 | 19 | const TourProvider: React.FC = ({ 20 | children, 21 | defaultOpen = false, 22 | startAt = 0, 23 | steps: defaultSteps, 24 | ...props 25 | }) => { 26 | const [isOpen, setIsOpen] = useState(defaultOpen) 27 | const [currentStep, setCurrentStep] = useState(startAt) 28 | const [steps, setSteps] = useState(defaultSteps) 29 | const [disabledActions, setDisabledActions] = useState(false) 30 | 31 | const value = { 32 | isOpen, 33 | setIsOpen, 34 | currentStep, 35 | setCurrentStep, 36 | steps, 37 | setSteps, 38 | disabledActions, 39 | setDisabledActions, 40 | ...props, 41 | } 42 | 43 | return ( 44 | 45 | {children} 46 | {isOpen ? : null} 47 | 48 | ) 49 | } 50 | 51 | export { TourProvider } 52 | 53 | export default TourContext 54 | 55 | export function useTour() { 56 | return useContext(TourContext) 57 | } 58 | -------------------------------------------------------------------------------- /demo/src/Dropdown.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from '@emotion/styled' 3 | import { fontFamily, headingSizes, themeColors } from './settings' 4 | 5 | const DropdownWrapper = styled.div` 6 | position: relative; 7 | display: flex; 8 | justify-content: center; 9 | align-items: center; 10 | ` 11 | 12 | const DropdownContent = styled.div` 13 | position: absolute; 14 | background-color: white; 15 | padding: 15px; 16 | box-shadow: 0 0 3px 0 rgba(0, 0, 0, 0.75); 17 | color: black; 18 | font-family: ${fontFamily}; 19 | font-size: 14px; 20 | line-height: 1.5; 21 | right: 0; 22 | top: 40px; 23 | border-radius: 5px; 24 | text-align: left; 25 | width: 300px; 26 | ` 27 | 28 | const DropdownTrigger = styled.div` 29 | border-radius: 50%; 30 | background-color: ${themeColors.light}; 31 | color: white; 32 | font-size: ${headingSizes[3]}; 33 | display: inline-block; 34 | width: ${headingSizes[1]}; 35 | height: ${headingSizes[1]}; 36 | text-align: center; 37 | margin-left: 5px; 38 | cursor: pointer; 39 | ` 40 | 41 | export default function Dropdown({ children }) { 42 | const [visible, setVisible] = useState(false) 43 | 44 | return ( 45 | 46 | setVisible(!visible)}>? 47 | {visible && ( 48 | 49 | {children} 50 | 51 | )} 52 | 53 | ) 54 | } 55 | -------------------------------------------------------------------------------- /packages/mask/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test --passWithNoTests", 16 | "lint": "tsdx lint", 17 | "prepare": "tsdx build", 18 | "size": "size-limit", 19 | "analyze": "size-limit --why" 20 | }, 21 | "peerDependencies": { 22 | "react": "16.x || 17.x" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "name": "@reactour/mask", 30 | "author": "Lionel Tzatzkin", 31 | "module": "dist/reactour-mask.esm.js", 32 | "size-limit": [ 33 | { 34 | "path": "dist/reactour-mask.cjs.production.min.js", 35 | "limit": "10 KB" 36 | }, 37 | { 38 | "path": "dist/reactour-mask.esm.js", 39 | "limit": "10 KB" 40 | } 41 | ], 42 | "devDependencies": { 43 | "@size-limit/preset-small-lib": "5.0.3", 44 | "@types/react": "17.0.19", 45 | "@types/react-dom": "17.0.9", 46 | "husky": "7.0.2", 47 | "react": "17.0.2", 48 | "react-dom": "17.0.2", 49 | "size-limit": "5.0.3", 50 | "tsdx": "0.14.1", 51 | "tslib": "2.3.1", 52 | "typescript": "4.4.2" 53 | }, 54 | "dependencies": { 55 | "@emotion/react": "11.4.1", 56 | "@reactour/utils": "*" 57 | }, 58 | "keywords": [ 59 | "react", 60 | "mask", 61 | "svg", 62 | "emotion", 63 | "highligted area" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /packages/popover/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test --passWithNoTests", 16 | "lint": "tsdx lint", 17 | "prepare": "tsdx build", 18 | "size": "size-limit", 19 | "analyze": "size-limit --why" 20 | }, 21 | "peerDependencies": { 22 | "react": "16.x || 17.x" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "name": "@reactour/popover", 30 | "author": "Lionel Tzatzkin", 31 | "module": "dist/reactour-popover.esm.js", 32 | "size-limit": [ 33 | { 34 | "path": "dist/reactour-popover.cjs.production.min.js", 35 | "limit": "10 KB" 36 | }, 37 | { 38 | "path": "dist/reactour-popover.esm.js", 39 | "limit": "10 KB" 40 | } 41 | ], 42 | "devDependencies": { 43 | "@size-limit/preset-small-lib": "5.0.3", 44 | "@types/react": "17.0.19", 45 | "@types/react-dom": "17.0.9", 46 | "husky": "7.0.2", 47 | "react": "17.0.2", 48 | "react-dom": "17.0.2", 49 | "size-limit": "5.0.3", 50 | "tsdx": "0.14.1", 51 | "tslib": "2.3.1", 52 | "typescript": "4.4.2" 53 | }, 54 | "dependencies": { 55 | "@emotion/react": "11.4.1", 56 | "@reactour/utils": "*" 57 | }, 58 | "keywords": [ 59 | "react", 60 | "popover", 61 | "tooltip", 62 | "helper", 63 | "emotion" 64 | ] 65 | } 66 | -------------------------------------------------------------------------------- /packages/mask/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/popover/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/tour/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // see https://www.typescriptlang.org/tsconfig to better understand tsconfigs 3 | "include": ["src", "types"], 4 | "compilerOptions": { 5 | "module": "esnext", 6 | "lib": ["dom", "esnext"], 7 | "importHelpers": true, 8 | // output .d.ts declaration files for consumers 9 | "declaration": true, 10 | // output .js.map sourcemap files for consumers 11 | "sourceMap": true, 12 | // match output dir to input dir. e.g. dist/index instead of dist/src/index 13 | "rootDir": "./src", 14 | // stricter type-checking for stronger correctness. Recommended by TS 15 | "strict": true, 16 | // linter checks for common issues 17 | "noImplicitReturns": true, 18 | "noFallthroughCasesInSwitch": true, 19 | // noUnused* overlap with @typescript-eslint/no-unused-vars, can disable if duplicative 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | // use Node's module resolution algorithm, instead of the legacy TS one 23 | "moduleResolution": "node", 24 | // transpile JSX to React.createElement 25 | "jsx": "react", 26 | // interop between ESM and CJS modules. Recommended by TS 27 | "esModuleInterop": true, 28 | // significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS 29 | "skipLibCheck": true, 30 | // error out if import and file system have a casing mismatch. Recommended by TS 31 | "forceConsistentCasingInFileNames": true, 32 | // `tsdx build` ignores this option, but it is commonly used when type-checking separately with `tsc` 33 | "noEmit": true, 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test --passWithNoTests", 16 | "lint": "tsdx lint", 17 | "prepare": "tsdx build", 18 | "size": "size-limit", 19 | "analyze": "size-limit --why" 20 | }, 21 | "peerDependencies": { 22 | "react": "16.x || 17.x" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "name": "@reactour/utils", 30 | "author": "Lionel Tzatzkin", 31 | "module": "dist/reactour-utils.esm.js", 32 | "size-limit": [ 33 | { 34 | "path": "dist/reactour-utils.cjs.production.min.js", 35 | "limit": "10 KB" 36 | }, 37 | { 38 | "path": "dist/reactour-utils.esm.js", 39 | "limit": "10 KB" 40 | } 41 | ], 42 | "devDependencies": { 43 | "@size-limit/preset-small-lib": "5.0.3", 44 | "@types/react": "17.0.19", 45 | "@types/react-dom": "17.0.9", 46 | "husky": "7.0.2", 47 | "react": "17.0.2", 48 | "react-dom": "17.0.2", 49 | "size-limit": "5.0.3", 50 | "tsdx": "0.14.1", 51 | "tslib": "2.3.1", 52 | "typescript": "4.4.2" 53 | }, 54 | "dependencies": { 55 | "@rooks/use-mutation-observer": "4.11.2", 56 | "resize-observer-polyfill": "1.5.1" 57 | }, 58 | "keywords": [ 59 | "react", 60 | "portal", 61 | "utilities", 62 | "observables", 63 | "resize observer", 64 | "mutatio observer", 65 | "smooth scroll", 66 | "get bounding client rect" 67 | ] 68 | } 69 | -------------------------------------------------------------------------------- /packages/tour/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "license": "MIT", 4 | "main": "dist/index.js", 5 | "typings": "dist/index.d.ts", 6 | "files": [ 7 | "dist" 8 | ], 9 | "engines": { 10 | "node": ">=10" 11 | }, 12 | "scripts": { 13 | "start": "tsdx watch", 14 | "build": "tsdx build", 15 | "test": "tsdx test --passWithNoTests", 16 | "lint": "tsdx lint", 17 | "prepare": "tsdx build", 18 | "size": "size-limit", 19 | "analyze": "size-limit --why" 20 | }, 21 | "peerDependencies": { 22 | "react": "16.x || 17.x" 23 | }, 24 | "husky": { 25 | "hooks": { 26 | "pre-commit": "tsdx lint" 27 | } 28 | }, 29 | "name": "@reactour/tour", 30 | "author": "Lionel Tzatzkin", 31 | "module": "dist/reactour-tour.esm.js", 32 | "size-limit": [ 33 | { 34 | "path": "dist/reactour-tour.cjs.production.min.js", 35 | "limit": "10 KB" 36 | }, 37 | { 38 | "path": "dist/reactour-tour.esm.js", 39 | "limit": "10 KB" 40 | } 41 | ], 42 | "devDependencies": { 43 | "@size-limit/preset-small-lib": "5.0.3", 44 | "@types/react": "17.0.19", 45 | "@types/react-dom": "17.0.9", 46 | "husky": "7.0.2", 47 | "react": "17.0.2", 48 | "react-dom": "17.0.2", 49 | "size-limit": "5.0.3", 50 | "tsdx": "0.14.1", 51 | "tslib": "2.3.1", 52 | "typescript": "4.4.2" 53 | }, 54 | "dependencies": { 55 | "@emotion/react": "11.4.1", 56 | "@react-aria/focus": "3.4.1", 57 | "@reactour/mask": "*", 58 | "@reactour/popover": "*", 59 | "@reactour/utils": "*" 60 | }, 61 | "keywords": [ 62 | "react", 63 | "tour", 64 | "intro", 65 | "guide", 66 | "introduction", 67 | "step-by-step", 68 | "guide", 69 | "walkthrough", 70 | "joyride", 71 | "tourist-guide", 72 | "product-intro", 73 | "presentation", 74 | "emotion" 75 | ] 76 | } 77 | -------------------------------------------------------------------------------- /demo/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/playground/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/playground/styleguide/build/bundle.d27f85f7.js: -------------------------------------------------------------------------------- 1 | !function(e){function webpackJsonpCallback(r){for(var n,o,u=r[0],c=r[1],p=r[2],i=0,l=[];i = ({ 5 | disableKeyboardNavigation, 6 | setCurrentStep, 7 | setIsOpen, 8 | stepsLength, 9 | disable, 10 | rtl, 11 | }) => { 12 | function keyDownHandler(e: any) { 13 | e.stopPropagation() 14 | 15 | if (disableKeyboardNavigation === true || disable) { 16 | return 17 | } 18 | let isEscDisabled, isRightDisabled, isLeftDisabled 19 | if (disableKeyboardNavigation) { 20 | isEscDisabled = disableKeyboardNavigation.includes('esc') 21 | isRightDisabled = disableKeyboardNavigation.includes('right') 22 | isLeftDisabled = disableKeyboardNavigation.includes('left') 23 | } 24 | function next() { 25 | setCurrentStep(c => Math.min(c + 1, stepsLength - 1)) 26 | } 27 | 28 | function prev() { 29 | setCurrentStep(c => Math.max(c - 1, 0)) 30 | } 31 | if (e.keyCode === 27 && !isEscDisabled) { 32 | e.preventDefault() 33 | setIsOpen(false) 34 | } 35 | if (e.keyCode === 39 && !isRightDisabled) { 36 | e.preventDefault() 37 | if (rtl) { 38 | prev() 39 | } else { 40 | next() 41 | } 42 | } 43 | if (e.keyCode === 37 && !isLeftDisabled) { 44 | e.preventDefault() 45 | if (rtl) { 46 | next() 47 | } else { 48 | prev() 49 | } 50 | } 51 | } 52 | 53 | useEffect(() => { 54 | window.addEventListener('keydown', keyDownHandler, false) 55 | return () => { 56 | window.removeEventListener('keydown', keyDownHandler) 57 | } 58 | }, [disable]) 59 | 60 | return null 61 | } 62 | 63 | export type KeyboardProps = { 64 | disableKeyboardNavigation?: boolean | KeyboardParts[] 65 | setCurrentStep: Dispatch> 66 | setIsOpen: Dispatch> 67 | stepsLength: number 68 | disable?: boolean 69 | rtl?: boolean 70 | } 71 | 72 | export default Keyboard 73 | -------------------------------------------------------------------------------- /packages/utils/src/useRect.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useCallback, useState } from 'react' 2 | 3 | export function getRect( 4 | element?: T | undefined | null 5 | ): RectResult { 6 | let rect: RectResult = { 7 | bottom: 0, 8 | height: 0, 9 | left: 0, 10 | right: 0, 11 | top: 0, 12 | width: 0, 13 | } 14 | if (element) { 15 | const domRect: DOMRect = element.getBoundingClientRect() 16 | rect = { 17 | bottom: domRect.bottom, 18 | height: domRect.height, 19 | left: domRect.left, 20 | right: domRect.right, 21 | top: domRect.top, 22 | width: domRect.width, 23 | } 24 | } 25 | return rect 26 | } 27 | 28 | export function useRect( 29 | ref: React.RefObject | undefined, 30 | refresher?: any 31 | ): RectResult { 32 | const [dimensions, setdDimensions] = useState(initialState) 33 | const handleResize = useCallback(() => { 34 | if (!ref?.current) return 35 | setdDimensions(getRect(ref?.current)) 36 | }, [ref?.current]) 37 | 38 | useEffect(() => { 39 | handleResize() 40 | window.addEventListener('resize', handleResize) 41 | return () => window.removeEventListener('resize', handleResize) 42 | }, [ref?.current, refresher]) 43 | 44 | return dimensions 45 | } 46 | 47 | export function useElemRect( 48 | elem: Element | undefined, 49 | refresher?: any 50 | ): RectResult { 51 | const [dimensions, setdDimensions] = useState(initialState) 52 | const handleResize = useCallback(() => { 53 | if (!elem) return 54 | setdDimensions(getRect(elem)) 55 | }, [elem]) 56 | 57 | useEffect(() => { 58 | handleResize() 59 | window.addEventListener('resize', handleResize) 60 | return () => window.removeEventListener('resize', handleResize) 61 | }, [elem, refresher]) 62 | 63 | return dimensions 64 | } 65 | 66 | const initialState = { 67 | bottom: 0, 68 | height: 0, 69 | left: 0, 70 | right: 0, 71 | top: 0, 72 | width: 0, 73 | } 74 | 75 | export type RectResult = { 76 | bottom: number 77 | height: number 78 | left: number 79 | right: number 80 | top: number 81 | width: number 82 | } 83 | -------------------------------------------------------------------------------- /packages/mask/src/styles.tsx: -------------------------------------------------------------------------------- 1 | export type StylesKeys = 2 | | 'maskWrapper' 3 | | 'maskArea' 4 | | 'maskRect' 5 | | 'clickArea' 6 | | 'highlightedArea' 7 | 8 | export type StylesObj = { 9 | [key in StylesKeys]?: StyleFn 10 | } 11 | 12 | export type StyleFn = ( 13 | props: { [key: string]: any }, 14 | state?: { [key: string]: any } 15 | ) => React.CSSProperties 16 | 17 | export type Styles = { 18 | maskWrapper: StyleFn 19 | maskArea: StyleFn 20 | maskRect: StyleFn 21 | clickArea: StyleFn 22 | highlightedArea: StyleFn 23 | } 24 | 25 | export type StyleKey = keyof Styles 26 | 27 | export const defaultStyles: Styles = { 28 | maskWrapper: () => ({ 29 | opacity: 0.7, 30 | left: 0, 31 | top: 0, 32 | position: 'fixed', 33 | zIndex: 99999, 34 | pointerEvents: 'none', 35 | color: '#000', 36 | }), 37 | maskArea: ({ x, y, width, height }) => ({ 38 | x, 39 | y, 40 | width, 41 | height, 42 | fill: 'black', 43 | rx: 0, 44 | }), 45 | maskRect: ({ windowWidth, windowHeight, maskID }) => ({ 46 | x: 0, 47 | y: 0, 48 | width: windowWidth, 49 | height: windowHeight, 50 | fill: 'currentColor', 51 | mask: `url(#${maskID})`, 52 | }), 53 | clickArea: ({ windowWidth, windowHeight, top, left, width, height }) => ({ 54 | x: 0, 55 | y: 0, 56 | width: windowWidth, 57 | height: windowHeight, 58 | fill: 'currentcolor', 59 | pointerEvents: 'auto', 60 | clipPath: `polygon(0 0, 0 ${windowHeight}px, ${left}px ${windowHeight}px, ${left}px ${top}px, ${left + 61 | width}px ${top}px, ${left + width}px ${top + height}px, ${left}px ${top + 62 | height}px, ${left}px ${windowHeight}px, ${windowWidth}px ${windowHeight}px, ${windowWidth}px 0)`, 63 | }), 64 | highlightedArea: ({ x, y, width, height }) => ({ 65 | x, 66 | y, 67 | width, 68 | height, 69 | pointerEvents: 'auto', 70 | fill: 'transparent', 71 | display: 'none', 72 | }), 73 | } 74 | 75 | export type getStylesType = (key: StylesKeys, extra?: any) => {} 76 | 77 | export function stylesMatcher(styles: StylesObj) { 78 | return (key: StyleKey, state: {}): {} => { 79 | const base = defaultStyles[key](state) 80 | const custom = styles[key] 81 | return custom ? custom(base, state) : base 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /docs/static/js/runtime-main.4d7f5426.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=r[2],s=0,p=[];s= 0 + threshold && 32 | left >= 0 + threshold && 33 | bottom <= windowHeight - threshold && 34 | right <= windowWidth - threshold 35 | ) 36 | } 37 | 38 | type InViewArgs = RectResult & { 39 | threshold?: number 40 | } 41 | 42 | export const isHoriz = (pos: string) => /(left|right)/.test(pos) 43 | export const isOutsideX = (val: number, windowWidth: number): boolean => { 44 | return val > windowWidth 45 | } 46 | export const isOutsideY = (val: number, windowHeight: number): boolean => { 47 | return val > windowHeight 48 | } 49 | 50 | export function bestPositionOf(positions: PositionsObjectType): string[] { 51 | return Object.keys(positions) 52 | .map(p => { 53 | return { 54 | position: p, 55 | value: positions[p], 56 | } 57 | }) 58 | .sort((a, b) => b.value - a.value) 59 | .map(p => p.position) 60 | } 61 | 62 | const defaultPadding = 10 63 | 64 | export function getPadding( 65 | padding: number | [number, number] = defaultPadding 66 | ) { 67 | if (Array.isArray(padding)) { 68 | return padding[0] 69 | ? [padding[0], padding[1] ? padding[1] : padding[0]] 70 | : [defaultPadding, defaultPadding] 71 | } 72 | return [padding, padding] 73 | } 74 | 75 | export type PositionsType = 'left' | 'right' | 'top' | 'bottom' 76 | 77 | export type PositionsObjectType = { 78 | [position: string]: number 79 | } 80 | 81 | export type CoordType = number[] 82 | 83 | export type CoordsObjectType = { 84 | [position: string]: CoordType 85 | } 86 | 87 | export { 88 | Portal, 89 | Observables, 90 | useRect, 91 | useElemRect, 92 | RectResult, 93 | getRect, 94 | smoothScroll, 95 | } 96 | -------------------------------------------------------------------------------- /demo/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/popover/README.md: -------------------------------------------------------------------------------- 1 |

2 | Reactour 3 |

4 |

5 | A popover positioned based on certain values 6 |

7 | 8 | ## Install 9 | 10 | ```zsh 11 | npm i -S @reactour/popover 12 | # or 13 | yarn add @reacmask/popover 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```js 19 | import { Popover } from '@reactour/popover' 20 | 21 | function App() { 22 | const sizes = { 23 | bottom: 0, 24 | left: 0, 25 | } 26 | 27 | return ( 28 | <> 29 | {/* ... */} 30 | 31 | 32 | ) 33 | } 34 | ``` 35 | 36 | ## `Popover` 37 | 38 | ### `sizes: RectResult` 39 | 40 |
41 | Type details 42 | 43 | ```ts 44 | type RectResult = { 45 | width?: number 46 | height?: number 47 | top?: number 48 | left?: number 49 | bottom?: number 50 | right?: number 51 | } 52 | ``` 53 | 54 |
55 | 56 | Object containing size and position informations of where to position the _Popover_ 57 | 58 | ### `position?: Position` 59 | 60 |
61 | Type details 62 | 63 | ```ts 64 | type Position = 65 | | 'top' 66 | | 'right' 67 | | 'bottom' 68 | | 'left' 69 | | 'center' 70 | | [number, number] 71 | | ((postionsProps: PositionProps) => Position) 72 | 73 | type PositionProps = { 74 | bottom: number 75 | height: number 76 | left: number 77 | right: number 78 | top: number 79 | width: number 80 | windowWidth: number 81 | windowHeight: number 82 | } 83 | ``` 84 | 85 |
86 | 87 | The position for the _Popover_, fixed in case of `[number, number]`, calculated prefered position in case of `string` 88 | 89 | ### `padding?: number | [number, number]` 90 | 91 | Extra space to add in _Popover_ position calculations. Useful when calculating space from `Element` bounding rect and want to add more space. 92 | 93 | ### `styles?: StylesObj` 94 | 95 | Prop to customize styles for the different parts of the _Mask_ using a function that allows to extend the base styles an take advantage of some state props. 96 | 97 | ### `className?: string` 98 | 99 | [Class](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) to apply to the _Popover_ 100 | 101 | #### Style keys and props available 102 | 103 | | key | props | 104 | | --------- | ----- | 105 | | `popover` | | 106 | 107 | #### Example 108 | 109 | ```js 110 | const styles = { 111 | popover: base => ({ 112 | ...base, 113 | boxShadow: '0 0 3em rgba(0, 0, 0, 0.5)', 114 | backgroundColor: '#dedede', 115 | }), 116 | } 117 | ``` 118 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | React App
-------------------------------------------------------------------------------- /packages/utils/src/Observables.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, useState } from 'react' 2 | import useMutationObserver from '@rooks/use-mutation-observer' 3 | import ResizeObserver from 'resize-observer-polyfill' 4 | 5 | const Observables: React.FC = ({ 6 | mutationObservables, 7 | resizeObservables, 8 | refresh, 9 | }) => { 10 | const [mutationsCounter, setMutationsCounter] = useState(0) 11 | const ref = useRef(document.documentElement || document.body) 12 | 13 | function refreshHighlightedRegionIfObservable(nodes: NodeList) { 14 | const posibleNodes = Array.from(nodes) 15 | for (const node of posibleNodes) { 16 | if (mutationObservables) { 17 | if (!(node as Element).attributes) { 18 | continue 19 | } 20 | const found = mutationObservables.find((observable: string) => 21 | (node as Element).matches(observable) 22 | ) 23 | 24 | if (found) { 25 | refresh(true) 26 | } 27 | } 28 | } 29 | } 30 | 31 | function incrementMutationsCounterIfObservable(nodes: NodeList) { 32 | const posibleNodes = Array.from(nodes) 33 | for (const node of posibleNodes) { 34 | if (resizeObservables) { 35 | if (!(node as Element).attributes) { 36 | continue 37 | } 38 | const found = resizeObservables.find((observable: string) => 39 | (node as Element).matches(observable) 40 | ) 41 | 42 | if (found) setMutationsCounter(mutationsCounter + 1) 43 | } 44 | } 45 | } 46 | 47 | useMutationObserver( 48 | ref, 49 | (mutationList: MutationRecord[]) => { 50 | for (const mutation of mutationList) { 51 | if (mutation.addedNodes.length !== 0) { 52 | refreshHighlightedRegionIfObservable(mutation.addedNodes) 53 | incrementMutationsCounterIfObservable(mutation.addedNodes) 54 | } 55 | 56 | if (mutation.removedNodes.length !== 0) { 57 | refreshHighlightedRegionIfObservable(mutation.removedNodes) 58 | incrementMutationsCounterIfObservable(mutation.removedNodes) 59 | } 60 | } 61 | }, 62 | { childList: true, subtree: true } 63 | ) 64 | 65 | useEffect(() => { 66 | if (!resizeObservables) { 67 | return 68 | } 69 | 70 | const resizeObserver: ResizeObserver = new ResizeObserver(() => { 71 | refresh() 72 | }) 73 | 74 | for (const observable of resizeObservables) { 75 | const element = document.querySelector(observable) 76 | if (element) { 77 | resizeObserver.observe(element) 78 | } 79 | } 80 | 81 | return () => { 82 | resizeObserver.disconnect() 83 | } 84 | }, [resizeObservables, mutationsCounter]) 85 | 86 | return null 87 | } 88 | 89 | type ObservablesProps = { 90 | mutationObservables?: string[] 91 | resizeObservables?: string[] 92 | refresh?: any 93 | } 94 | 95 | export default Observables 96 | -------------------------------------------------------------------------------- /packages/mask/src/Mask.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from '@emotion/react' 3 | import React, { MouseEventHandler } from 'react' 4 | import { StylesObj, stylesMatcher } from './styles' 5 | import { safe, getWindow, getPadding, RectResult } from '@reactour/utils' 6 | const maskID = 'tour__mask' 7 | 8 | const Mask: React.FC = ({ 9 | padding = 10, 10 | onClick, 11 | onClickHighlighted, 12 | styles = {}, 13 | sizes, 14 | className, 15 | highlightedAreaClassName, 16 | }) => { 17 | const getStyles = stylesMatcher(styles) 18 | const [px, py] = getPadding(padding) 19 | const { w: windowWidth, h: windowHeight } = getWindow() 20 | const width = safe(sizes?.width + px * 2) 21 | const height = safe(sizes?.height + py * 2) 22 | const top = safe(sizes?.top - py) 23 | const left = safe(sizes?.left - px) 24 | 25 | return ( 26 |
31 | 36 | 37 | 38 | 45 | 53 | 54 | 55 | {/* The actual Mask */} 56 | 63 | {/* The clickable area */} 64 | 74 | 84 | 85 |
86 | ) 87 | } 88 | 89 | export type MaskProps = { 90 | children?: React.ReactNode 91 | sizes: RectResult 92 | styles?: StylesObj 93 | className?: string 94 | highlightedAreaClassName?: string 95 | padding?: number | [number, number] 96 | onClick?: MouseEventHandler 97 | onClickHighlighted?: MouseEventHandler 98 | } 99 | 100 | export default Mask 101 | -------------------------------------------------------------------------------- /packages/mask/README.md: -------------------------------------------------------------------------------- 1 |

2 | Reactour 3 |

4 |

5 | An SVG mask that cover all the window contents except the one specified by certain position and sizes values 6 |

7 | 8 | ## Install 9 | 10 | ```zsh 11 | npm i -S @reactour/mask 12 | # or 13 | yarn add @reacmask/mask 14 | ``` 15 | 16 | ## Usage 17 | 18 | ```js 19 | import { Mask } from '@reactour/mask' 20 | 21 | function App() { 22 | const sizes = { 23 | width: 100, 24 | height: 100, 25 | top: 100, 26 | left: 100, 27 | } 28 | 29 | return ( 30 | <> 31 | {/* ... */} 32 | 33 | 34 | ) 35 | } 36 | ``` 37 | 38 | ## `Mask` 39 | 40 | ### `sizes: RectResult` 41 | 42 |
43 | Type details 44 | 45 | ```ts 46 | type RectResult = { 47 | width: number 48 | height: number 49 | top: number 50 | left: number 51 | bottom?: number 52 | right?: number 53 | } 54 | ``` 55 | 56 |
57 | 58 | Object containing size and position informations of where to position the _Mask_ 59 | 60 | ### `className?: string` 61 | 62 | [Class](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) to apply to the _Mask_ wrapper 63 | 64 | ### `highlightedAreaClassName?: string` 65 | 66 | [Class](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/class) to apply to the _Highlighted_ area `rect` 67 | 68 | ### `padding?: number | [number, number]` 69 | 70 | Extra space to add in _Mask_ calculations. Useful when calculating space from `Element` bounding rect and want to add more space. 71 | 72 | Single number sets sema space for `x` and `y`, otherwise, an Array sets `[x, y]`. 73 | 74 | ### `onClick?: MouseEventHandler` 75 | 76 | Click handler for the _Mask_ except the highlighted area. 77 | 78 | ### `onClickHighlighted?: MouseEventHandler` 79 | 80 | Click handler for the _Highlighted_ area. 81 | 82 | ### `styles?: StylesObj` 83 | 84 | Prop to customize styles for the different parts of the _Mask_ using a function that allows to extend the base styles an take advantage of some state props. 85 | 86 | #### Style keys and props available 87 | 88 | | key | props | 89 | | ----------------- | --------------------------------------------------------------- | 90 | | `maskArea` | `x`, `y`, `width`, `height` | 91 | | `maskRect` | `windowWidth`, `windowHeight`, `maskID` | 92 | | `clickArea` | `windowWidth`, `windowHeight`, `top`, `left`, `width`, `height` | 93 | | `highlightedArea` | `x`, `y`, `width`, `height` | 94 | 95 | #### Example 96 | 97 | ```js 98 | const styles = { 99 | maskWrapper: base => ({ 100 | ...base, 101 | color: 'red', 102 | }), 103 | highlightedArea: (base, { x, y }) => ({ 104 | ...base, 105 | x: x + 10, 106 | y: y + 10, 107 | }), 108 | } 109 | ``` 110 | -------------------------------------------------------------------------------- /packages/tour/src/types.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, MouseEventHandler } from 'react' 2 | import { Position, PopoverStylesObj } from '@reactour/popover' 3 | import { MaskStylesObj } from '@reactour/mask' 4 | import { StylesObj } from './styles' 5 | 6 | type SharedProps = { 7 | steps: StepType[] 8 | styles?: StylesObj & PopoverStylesObj & MaskStylesObj 9 | padding?: Padding 10 | position?: Position 11 | disableInteraction?: boolean 12 | disableFocusLock?: boolean 13 | disableDotsNavigation?: boolean 14 | disableKeyboardNavigation?: boolean | KeyboardParts[] 15 | className?: string 16 | maskClassName?: string 17 | highlightedMaskClassName?: string 18 | nextButton?: (props: BtnFnProps) => void 19 | prevButton?: (props: BtnFnProps) => void 20 | afterOpen?: (target: Element | null) => void 21 | beforeClose?: (target: Element | null) => void 22 | onClickMask?: (clickProps: ClickProps) => void 23 | onClickHighlighted?: MouseEventHandler 24 | badgeContent?: (badgeProps: BadgeProps) => any 25 | showNavigation?: boolean 26 | showPrevNextButtons?: boolean 27 | showCloseButton?: boolean 28 | showBagde?: boolean 29 | scrollSmooth?: boolean 30 | inViewThreshold?: number 31 | accessibilityOptions?: A11yOptions 32 | rtl?: boolean 33 | } 34 | 35 | type A11yOptions = { 36 | ariaLabelledBy: string 37 | closeButtonAriaLabel: string 38 | showNavigationScreenReaders: boolean 39 | } 40 | 41 | type ComponentPadding = number | [number, number] 42 | export type Padding = 43 | | number 44 | | { mask?: ComponentPadding; popover?: ComponentPadding } 45 | 46 | export type KeyboardParts = 'esc' | 'left' | 'right' 47 | 48 | type ClickProps = { 49 | setIsOpen: Dispatch> 50 | setCurrentStep: Dispatch> 51 | currentStep: number 52 | } 53 | 54 | export type TourProps = SharedProps & 55 | ClickProps & { 56 | isOpen: Boolean 57 | setSteps: Dispatch> 58 | disabledActions: boolean 59 | setDisabledActions: Dispatch> 60 | } 61 | 62 | type BadgeProps = { 63 | totalSteps: number 64 | currentStep: number 65 | transition: boolean 66 | } 67 | 68 | export type ProviderProps = SharedProps & { 69 | children: React.ReactNode 70 | defaultOpen?: Boolean 71 | startAt?: number 72 | } 73 | 74 | export type ContentProps = { 75 | setCurrentStep: Dispatch> 76 | transition: boolean 77 | currentStep: number 78 | setIsOpen: Dispatch> 79 | } 80 | 81 | export type StepType = { 82 | selector: string 83 | content: string | ((props: ContentProps) => void) 84 | position?: Position 85 | highlightedSelectors?: string[] 86 | mutationObservables?: string[] 87 | resizeObservables?: string[] 88 | navDotAriaLabel?: string 89 | stepInteraction?: boolean 90 | action?: (elem: Element | null) => void 91 | disableActions?: boolean 92 | padding?: Padding 93 | styles?: StylesObj & PopoverStylesObj & MaskStylesObj 94 | } 95 | 96 | export type BtnFnProps = { 97 | Button: React.FC 98 | setCurrentStep: Dispatch> 99 | stepsLength: number 100 | currentStep: number 101 | setIsOpen: Dispatch> 102 | } 103 | 104 | export type NavButtonProps = { 105 | onClick?: () => void 106 | kind?: 'next' | 'prev' 107 | hideArrow?: boolean 108 | } 109 | -------------------------------------------------------------------------------- /demo/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /packages/tour/src/styles.tsx: -------------------------------------------------------------------------------- 1 | export type StylesKeys = 2 | | 'badge' 3 | | 'controls' 4 | | 'navigation' 5 | | 'button' 6 | | 'arrow' 7 | | 'dot' 8 | | 'close' 9 | 10 | export type StylesObj = { 11 | [key in StylesKeys]?: StyleFn 12 | } 13 | 14 | export type StyleFn = ( 15 | props: { [key: string]: any }, 16 | state?: { [key: string]: any } 17 | ) => React.CSSProperties 18 | 19 | export type Styles = { 20 | badge: StyleFn 21 | controls: StyleFn 22 | navigation: StyleFn 23 | button: StyleFn 24 | arrow: StyleFn 25 | dot: StyleFn 26 | close: StyleFn 27 | } 28 | 29 | export type StyleKey = keyof Styles 30 | 31 | export const defaultStyles: Styles = { 32 | badge: () => ({ 33 | position: 'absolute', 34 | fontFamily: 'monospace', 35 | background: 'var(--reactour-accent,#007aff)', 36 | height: '1.875em', 37 | lineHeight: 2, 38 | paddingLeft: '0.8125em', 39 | paddingRight: '0.8125em', 40 | fontSize: '1em', 41 | borderRadius: '1.625em', 42 | color: 'white', 43 | textAlign: 'center', 44 | boxShadow: '0 0.25em 0.5em rgba(0, 0, 0, 0.3)', 45 | top: '-0.8125em', 46 | left: '-0.8125em', 47 | }), 48 | controls: () => ({ 49 | display: 'flex', 50 | marginTop: 24, 51 | alignItems: 'center', 52 | justifyContent: 'space-between', 53 | }), 54 | navigation: () => ({ 55 | counterReset: 'dot', 56 | display: 'flex', 57 | justifyContent: 'space-between', 58 | alignItems: 'center', 59 | flexWrap: 'wrap', 60 | }), 61 | button: ({ disabled }) => ({ 62 | display: 'block', 63 | padding: 0, 64 | border: 0, 65 | background: 'none', 66 | cursor: disabled ? 'not-allowed' : 'pointer', 67 | }), 68 | arrow: ({ disabled }) => ({ 69 | color: disabled ? '#caccce' : '#646464', 70 | width: 16, 71 | height: 12, 72 | flex: '0 0 16px', 73 | '&:hover': { 74 | color: disabled ? '#caccce' : '#000', 75 | }, 76 | }), 77 | dot: ({ current, disabled, showNumber }) => ({ 78 | counterIncrement: 'dot', 79 | width: 8, 80 | height: 8, 81 | border: current ? '0' : '1px solid #caccce', 82 | borderRadius: '100%', 83 | padding: 0, 84 | display: 'block', 85 | margin: 4, 86 | transition: 'opacity 0.3s, transform 0.3s', 87 | cursor: disabled ? 'not-allowed' : 'pointer', 88 | transform: `scale(${current ? 1.25 : 1})`, 89 | color: current ? 'var(--reactour-accent, #007aff)' : '#caccce', 90 | background: current ? 'var(--reactour-accent, #007aff)' : 'none', 91 | '&:before': { 92 | content: 'counter(dot)', 93 | position: 'absolute', 94 | bottom: 'calc(100% + 0.25em)', 95 | left: '50%', 96 | opacity: 0, 97 | transform: 'translate(-50%, 1em)', 98 | transition: '0.3s', 99 | display: showNumber ? 'block' : 'none', 100 | }, 101 | '&:hover': { 102 | backgroundColor: 'currentColor', 103 | '&:before': { 104 | opacity: 0.5, 105 | transform: 'translate(-50%, -2px)', 106 | }, 107 | }, 108 | }), 109 | close: ({ disabled }) => ({ 110 | position: 'absolute', 111 | top: 22, 112 | right: 22, 113 | width: 9, 114 | height: 9, 115 | color: disabled ? '#caccce' : '#5e5e5e', 116 | '&:hover': { 117 | color: disabled ? '#caccce' : '#000', 118 | }, 119 | }), 120 | } 121 | 122 | export type getStylesType = (key: StylesKeys, extra?: any) => {} 123 | 124 | export function stylesMatcher(styles: StylesObj) { 125 | return (key: StyleKey, state: {}): {} => { 126 | const base = defaultStyles[key](state) 127 | const custom = styles[key] 128 | return custom ? custom(base, state) : base 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /packages/playground/styleguide/build/2.052a1ca7.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /* 2 | object-assign 3 | (c) Sindre Sorhus 4 | @license MIT 5 | */ 6 | 7 | /*! 8 | * @overview es6-promise - a tiny implementation of Promises/A+. 9 | * @copyright Copyright (c) 2014 Yehuda Katz, Tom Dale, Stefan Penner and contributors (Conversion to ES6 API by Jake Archibald) 10 | * @license Licensed under MIT license 11 | * See https://raw.githubusercontent.com/stefanpenner/es6-promise/master/LICENSE 12 | * @version v4.2.8+1e68dce6 13 | */ 14 | 15 | /*! 16 | * The buffer module from node.js, for the browser. 17 | * 18 | * @author Feross Aboukhadijeh 19 | * @license MIT 20 | */ 21 | 22 | /*! 23 | * The buffer module from node.js, for the browser. 24 | * 25 | * @author Feross Aboukhadijeh 26 | * @license MIT 27 | */ 28 | 29 | /*! 30 | * regjsgen 0.5.2 31 | * Copyright 2014-2020 Benjamin Tan 32 | * Available under the MIT license 33 | */ 34 | 35 | /*! ***************************************************************************** 36 | Copyright (c) Microsoft Corporation. 37 | 38 | Permission to use, copy, modify, and/or distribute this software for any 39 | purpose with or without fee is hereby granted. 40 | 41 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 42 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 43 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 44 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 45 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 46 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 47 | PERFORMANCE OF THIS SOFTWARE. 48 | ***************************************************************************** */ 49 | 50 | /*! clipboard-copy. MIT License. Feross Aboukhadijeh */ 51 | 52 | /*! https://mths.be/regenerate v1.4.2 by @mathias | MIT license */ 53 | 54 | /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh */ 55 | 56 | /** 57 | * A better abstraction over CSS. 58 | * 59 | * @copyright Oleg Isonen (Slobodskoi) / Isonen 2014-present 60 | * @website https://github.com/cssinjs/jss 61 | * @license MIT 62 | */ 63 | 64 | /** 65 | * Prism: Lightweight, robust, elegant syntax highlighting 66 | * 67 | * @license MIT 68 | * @author Lea Verou 69 | * @namespace 70 | * @public 71 | */ 72 | 73 | /** @license React v0.20.2 74 | * scheduler.production.min.js 75 | * 76 | * Copyright (c) Facebook, Inc. and its affiliates. 77 | * 78 | * This source code is licensed under the MIT license found in the 79 | * LICENSE file in the root directory of this source tree. 80 | */ 81 | 82 | /** @license React v16.13.1 83 | * react-is.production.min.js 84 | * 85 | * Copyright (c) Facebook, Inc. and its affiliates. 86 | * 87 | * This source code is licensed under the MIT license found in the 88 | * LICENSE file in the root directory of this source tree. 89 | */ 90 | 91 | /** @license React v17.0.2 92 | * react-dom.production.min.js 93 | * 94 | * Copyright (c) Facebook, Inc. and its affiliates. 95 | * 96 | * This source code is licensed under the MIT license found in the 97 | * LICENSE file in the root directory of this source tree. 98 | */ 99 | 100 | /** @license React v17.0.2 101 | * react-jsx-runtime.production.min.js 102 | * 103 | * Copyright (c) Facebook, Inc. and its affiliates. 104 | * 105 | * This source code is licensed under the MIT license found in the 106 | * LICENSE file in the root directory of this source tree. 107 | */ 108 | 109 | /** @license React v17.0.2 110 | * react.production.min.js 111 | * 112 | * Copyright (c) Facebook, Inc. and its affiliates. 113 | * 114 | * This source code is licensed under the MIT license found in the 115 | * LICENSE file in the root directory of this source tree. 116 | */ 117 | -------------------------------------------------------------------------------- /docs/static/js/3.764ffdad.chunk.js: -------------------------------------------------------------------------------- 1 | (this.webpackJsonpdocs=this.webpackJsonpdocs||[]).push([[3],{65:function(t,e,n){"use strict";n.r(e),n.d(e,"getCLS",(function(){return p})),n.d(e,"getFCP",(function(){return S})),n.d(e,"getFID",(function(){return F})),n.d(e,"getLCP",(function(){return k})),n.d(e,"getTTFB",(function(){return C}));var i,a,r,o,u=function(t,e){return{name:t,value:void 0===e?-1:e,delta:0,entries:[],id:"v1-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(t,e){try{if(PerformanceObserver.supportedEntryTypes.includes(t)){if("first-input"===t&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(t){return t.getEntries().map(e)}));return n.observe({type:t,buffered:!0}),n}}catch(t){}},s=function(t,e){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(t(i),e&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},f=function(t){addEventListener("pageshow",(function(e){e.persisted&&t(e)}),!0)},d="function"==typeof WeakSet?new WeakSet:new Set,m=function(t,e,n){var i;return function(){e.value>=0&&(n||d.has(e)||"hidden"===document.visibilityState)&&(e.delta=e.value-(i||0),(e.delta||void 0===i)&&(i=e.value,t(e)))}},p=function(t,e){var n,i=u("CLS",0),a=function(t){t.hadRecentInput||(i.value+=t.value,i.entries.push(t),n())},r=c("layout-shift",a);r&&(n=m(t,i,e),s((function(){r.takeRecords().map(a),n()})),f((function(){i=u("CLS",0),n=m(t,i,e)})))},v=-1,l=function(){return"hidden"===document.visibilityState?0:1/0},h=function(){s((function(t){var e=t.timeStamp;v=e}),!0)},g=function(){return v<0&&(v=l(),h(),f((function(){setTimeout((function(){v=l(),h()}),0)}))),{get timeStamp(){return v}}},S=function(t,e){var n,i=g(),a=u("FCP"),r=function(t){"first-contentful-paint"===t.name&&(s&&s.disconnect(),t.startTime=0&&a1e12?new Date:performance.now())-t.timeStamp;"pointerdown"==t.type?function(t,e){var n=function(){w(t,e),a()},i=function(){a()},a=function(){removeEventListener("pointerup",n,y),removeEventListener("pointercancel",i,y)};addEventListener("pointerup",n,y),addEventListener("pointercancel",i,y)}(e,t):w(e,t)}},b=function(t){["mousedown","keydown","touchstart","pointerdown"].forEach((function(e){return t(e,T,y)}))},F=function(t,e){var n,r=g(),p=u("FID"),v=function(t){t.startTime { 31 | if (!target) return 32 | setdDimensions(getHighlightedRect(target, step?.highlightedSelectors)) 33 | }, [target]) 34 | 35 | useEffect(() => { 36 | handleResize() 37 | window.addEventListener('resize', handleResize) 38 | return () => window.removeEventListener('resize', handleResize) 39 | }, [target, refresher]) 40 | 41 | useEffect(() => { 42 | const isInView = inView({ 43 | ...dimensions, 44 | threshold: scrollOptions.inViewThreshold, 45 | }) 46 | if (!isInView) { 47 | setTransition(true) 48 | smoothScroll(target, scrollOptions) 49 | .then(() => { 50 | if (!observing) setRefresher(Date.now()) 51 | }) 52 | .finally(() => { 53 | setTransition(false) 54 | }) 55 | } 56 | }, [dimensions]) 57 | 58 | function observableRefresher() { 59 | setObserving(true) 60 | setdDimensions(getHighlightedRect(target, step?.highlightedSelectors)) 61 | setObserving(false) 62 | } 63 | 64 | return { sizes: dimensions, transition, target, observableRefresher } 65 | } 66 | 67 | function getHighlightedRect( 68 | node: Element | null, 69 | highlightedSelectors: string[] = [], 70 | bypassElem = true 71 | ) { 72 | const { w: windowWidth, h: windowHeight } = getWindow() 73 | if (!highlightedSelectors) { 74 | return { 75 | ...getRect(node), 76 | windowWidth, 77 | windowHeight, 78 | } 79 | } 80 | 81 | let attrs = getRect(node) 82 | let altAttrs = { 83 | bottom: 0, 84 | height: 0, 85 | left: windowWidth, 86 | right: 0, 87 | top: windowHeight, 88 | width: 0, 89 | } 90 | 91 | for (const selector of highlightedSelectors) { 92 | const element = document.querySelector(selector) as HTMLElement 93 | if ( 94 | !element || 95 | element.style.display === 'none' || 96 | element.style.visibility === 'hidden' 97 | ) { 98 | continue 99 | } 100 | 101 | const rect = getRect(element) 102 | 103 | if (bypassElem) { 104 | if (rect.top < altAttrs.top) { 105 | altAttrs.top = rect.top 106 | } 107 | 108 | if (rect.right > altAttrs.right) { 109 | altAttrs.right = rect.right 110 | } 111 | 112 | if (rect.bottom > altAttrs.bottom) { 113 | altAttrs.bottom = rect.bottom 114 | } 115 | 116 | if (rect.left < altAttrs.left) { 117 | altAttrs.left = rect.left 118 | } 119 | 120 | altAttrs.width = altAttrs.right - altAttrs.left 121 | altAttrs.height = altAttrs.bottom - altAttrs.top 122 | } else { 123 | if (rect.top < attrs.top) { 124 | attrs.top = rect.top 125 | } 126 | 127 | if (rect.right > attrs.right) { 128 | attrs.right = rect.right 129 | } 130 | 131 | if (rect.bottom > attrs.bottom) { 132 | attrs.bottom = rect.bottom 133 | } 134 | 135 | if (rect.left < attrs.left) { 136 | attrs.left = rect.left 137 | } 138 | 139 | attrs.width = attrs.right - attrs.left 140 | attrs.height = attrs.bottom - attrs.top 141 | } 142 | } 143 | 144 | const bypassable = bypassElem 145 | ? altAttrs.width > 0 && altAttrs.height > 0 146 | : false 147 | 148 | return { 149 | left: (bypassable ? altAttrs : attrs).left, 150 | top: (bypassable ? altAttrs : attrs).top, 151 | right: (bypassable ? altAttrs : attrs).right, 152 | bottom: (bypassable ? altAttrs : attrs).bottom, 153 | width: (bypassable ? altAttrs : attrs).width, 154 | height: (bypassable ? altAttrs : attrs).height, 155 | windowWidth, 156 | windowHeight, 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /packages/tour/src/Navigation.tsx: -------------------------------------------------------------------------------- 1 | /** @jsx jsx */ 2 | import { jsx } from '@emotion/react' 3 | import React, { Dispatch } from 'react' 4 | import { StylesObj, stylesMatcher } from './styles' 5 | import { StepType, BtnFnProps, NavButtonProps } from './types' 6 | 7 | const Navigation: React.FC = ({ 8 | styles = {}, 9 | steps, 10 | setCurrentStep, 11 | currentStep, 12 | setIsOpen, 13 | nextButton, 14 | prevButton, 15 | disableDots, 16 | hideButtons, 17 | disableAll, 18 | rtl, 19 | }) => { 20 | const stepsLength = steps.length 21 | const getStyles = stylesMatcher(styles) 22 | 23 | const Button: React.FC = ({ 24 | onClick, 25 | kind = 'next', 26 | children, 27 | hideArrow, 28 | }) => { 29 | function clickHandler() { 30 | if (!disableAll) { 31 | if (onClick && typeof onClick === 'function') { 32 | onClick() 33 | } else { 34 | if (kind === 'next') { 35 | setCurrentStep(Math.min(currentStep + 1, stepsLength - 1)) 36 | } else { 37 | setCurrentStep(Math.max(currentStep - 1, 0)) 38 | } 39 | } 40 | } 41 | } 42 | 43 | return ( 44 | 70 | ) 71 | } 72 | 73 | return ( 74 |
75 | {!hideButtons ? ( 76 | prevButton && typeof prevButton === 'function' ? ( 77 | prevButton({ 78 | Button, 79 | setCurrentStep, 80 | currentStep, 81 | stepsLength, 82 | setIsOpen, 83 | }) 84 | ) : ( 85 |
107 | {!hideButtons ? ( 108 | nextButton && typeof nextButton === 'function' ? ( 109 | nextButton({ 110 | Button, 111 | setCurrentStep, 112 | currentStep, 113 | stepsLength, 114 | setIsOpen, 115 | }) 116 | ) : ( 117 |