├── .babelrc
├── .browserslistrc
├── .eslintignore
├── .eslintrc
├── .gitignore
├── .npmignore
├── .npmingore
├── .size-snapshot.json
├── .travis.yml
├── CHANGELOG.md
├── LICENSE
├── README.md
├── cypress.json
├── cypress
├── .eslintrc
├── fixtures
│ └── example.json
├── integration
│ └── scroll.spec.js
├── plugins
│ └── index.js
└── support
│ ├── commands.js
│ └── index.js
├── example
├── .gitignore
├── gatsby-config.js
├── package-lock.json
├── package.json
└── src
│ └── pages
│ ├── CustomButton.css
│ ├── index.js
│ └── normalize.css
├── index.js
├── package-lock.json
├── package.json
├── rollup.config.js
├── src
└── react-scroll-up-button.js
└── tests
├── server
├── index.html
├── testing.js
└── webpack.config.js
└── specs
├── .eslintrc
├── custom prototype.js
├── named exports.js
├── pre-styled children.js
└── unit-intergation.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [["@babel/preset-env", { "loose": true }], "@babel/preset-react"]
3 | }
--------------------------------------------------------------------------------
/.browserslistrc:
--------------------------------------------------------------------------------
1 | last 2 versions
2 | ie >= 11
3 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | /dist/
2 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "es6": true,
5 | "browser": true
6 | },
7 | "rules": {
8 | "max-len": ["error", { "ignoreComments": true, "code": 120, "ignoreTemplateLiterals": true }],
9 | "linebreak-style": 0,
10 | "semi": 0,
11 | "no-shadow": 0,
12 | "no-use-before-define": ["error", { "variables": false }],
13 | "no-nested-ternary": 0,
14 | "comma-dangle": ["error", {
15 | "arrays": "always-multiline",
16 | "objects": "always-multiline",
17 | "imports": "always-multiline",
18 | "exports": "always-multiline",
19 | "functions": "ignore"
20 | }],
21 | "react/no-did-update-set-state": 0,
22 | "react/jsx-one-expression-per-line": 0,
23 | "react/jsx-uses-react": "error",
24 | "react/jsx-uses-vars": "error",
25 | "react/jsx-filename-extension": 0,
26 | "react/prop-types": ["error",{ "skipUndeclared" : true }],
27 | "react/prefer-stateless-function": 0,
28 | "import/no-named-as-default": 0,
29 | "import/no-named-as-default-member": 0,
30 | "jsx-a11y/label-has-associated-control": ["error", { "assert": "either" }],
31 | "jsx-a11y/label-has-for": [ 2, {"required": {"every": [ "id" ]}}]
32 | },
33 | "plugins": [
34 | "react",
35 | "import"
36 | ],
37 | "parser": "babel-eslint",
38 | "parserOptions": {
39 | "ecmaFeatures": {
40 | "jsx": true
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | coverage/
4 | dist/
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | src
2 | *.log
3 | node_modules
4 | npm-debug.log
5 | UITesting
6 | coverage
7 |
--------------------------------------------------------------------------------
/.npmingore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | npm-debug.log
3 | coverage/
4 | dist/
5 | example/
6 |
--------------------------------------------------------------------------------
/.size-snapshot.json:
--------------------------------------------------------------------------------
1 | {
2 | "dist/esm/react-scroll-up-button.js": {
3 | "bundled": 13634,
4 | "minified": 8157,
5 | "gzipped": 2377,
6 | "treeshaked": {
7 | "rollup": {
8 | "code": 5284,
9 | "import_statements": 285
10 | },
11 | "webpack": {
12 | "code": 6558
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "8"
4 | script:
5 | - npm run eslint
6 | - npm run test
7 | - npm run test:cypress
8 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | #1.6.4
2 | * **Dev:** Removed Babel-Polyfill in favor spread/babel compile (I should have noticed this before hand).
3 | * **React:** Added custom propType validator to check StopPosition prop is less then ShowAtPosition prop.
4 | * **React:** Fixed mispelled word ShowAtPosition, was ShowAtPostion.
5 | * **Dev:** Deployed demo site https://react-scroll-up-button.com
6 |
7 | #1.6.3
8 | * **Dev:** Removed Babel-Polyfill in favor of Object-Assign.
9 |
10 | #1.6.2
11 | * **React:** Added Transform attribute to default button for better styling.
12 | * **Dev:** Added Babel-polyfill for object.assign polyfill, support for ie11.
13 |
14 | #1.6.1
15 | * **React:** Added catch if tween function tried to set scroll position past the stop position.
16 | * **Test:** Updated all tests, and added e2e testing.
17 | * **Dev:** Added cypress e2e testing and multiple package scripts for development and testing.
18 | * **NPM:** Moved destination directory from lib/ to dist/
19 |
20 | #1.6.0
21 | * **React:** Moved themed buttons to exports
22 |
23 | #1.5.11
24 | * **React:** Added className overrides to the default button.
25 |
26 | #1.5.10
27 | * **NPM:** Replaced babel-preset-es2015 for babel-preset-env
28 | * **NPM:** Updated React libraries
29 | * **NPM:** Added enzyme-adapter-react
30 | * **NPM:** Replaced react-addons-test-utils for react-test-renderer
31 | * **NPM:** Added Node 8 for unit testing
32 |
33 | #1.5.9
34 |
35 | * **React:** Added Passive Events into listeners.
36 | * **React:** Replaced React.PropTypes with Proptypes See https://facebook.github.io/react/docs/typechecking-with-proptypes.html
37 | * **NPM:** Updated dependencies
38 | * **NPM:** Added detect-passive-events
39 | * **NPM:** Added prop-types
40 |
41 | #1.5.6
42 |
43 | * **NPM:** Updated README
44 | * **GITHUB:** Added build coverage
45 |
46 | #1.5.5
47 |
48 | * **NPM:** Update README, minor variable scoping.
49 | * **GITHUB:** Added build testing
50 |
51 | #1.5.4
52 |
53 | * **NPM:** Published 1.5.4
54 | * **GITHUB:** Updated README.md fixed arrow up example
55 |
56 | #1.5.2
57 |
58 | * **NPM:** Removed un-needed dependency
59 |
60 | #1.5.1
61 |
62 | * **Compatibility:** no longer requires a style-loader
63 | * **NPM:** Removed .css file
64 | * **GITHUB:** Added CHANGELOG.md
65 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://badge.fury.io/js/react-scroll-up-button)
2 | []()
3 | [](https://react-scroll-up-button.com)
4 | [](https://travis-ci.org/dirtyredz/react-scroll-up-button)
5 | [](https://coveralls.io/github/dirtyredz/react-scroll-up-button?branch=master)
6 |
7 | # react-scroll-up-button
8 | React Component for a fixed scroll to top button.
9 | The button can use the default button, or can be customized to look like any button you want.
10 | The component contains all the code neccassary to scroll to the top of the page (Or desired position) Utilizing Easing effects.
11 |
12 |
13 |
14 | I am also adding many different style buttons that you can copy and paste into this component to quickly acheive a style you can appreciate and fits your websites look and feel.
15 |
16 | ## Demo
17 |
18 | https://react-scroll-up-button.com
19 |
20 |
21 | ## Install
22 |
23 | ```npm
24 | npm install react-scroll-up-button
25 | ```
26 |
27 | ----
28 |
29 | ## Usage
30 |
31 | ### Default Button: 
32 |
33 | ```jsx
34 | import React from "react";
35 | import ScrollUpButton from "react-scroll-up-button"; //Add this line Here
36 |
37 | export default class Index extends React.Component {
38 | render() {
39 | return (
40 |
41 |
42 | //This is all you need to get the default view working
43 |
44 | );
45 | }
46 | }
47 |
48 | ```
49 |
50 | Setting Custom Classes to the default button.
51 | This will apply the class names you specify to the scroll-up-button.
52 | ```jsx
53 |
54 | ```
55 |
56 | Setting inline styles to the default button.
57 | This will apply the styles to the scroll-up-button.
58 | ```jsx
59 |
60 | ```
61 |
62 | ----
63 |
64 | ### Custom Button:
65 | ```jsx
66 | import React from "react";
67 | import ScrollUpButton from "react-scroll-up-button";
68 |
69 | export default class Index extends React.Component {
70 | render() {
71 | return (
72 |
73 |
74 | // Here you can add any react component or jsx
75 | // ScrollButton will apply the classnames given to the container of whatever you put here.
76 | // Changing appearence this way will only work when importing the default ScrollUpButton, importing any of the specific buttons do not except children
77 |
78 |
79 | );
80 | }
81 | }
82 | ```
83 | ----
84 |
85 | ### Configuration:
86 | ```javascript
87 |
97 | ```
98 | StopPosition -- PageYOffset in which you want the page to stop at when scrolling up.
99 |
100 | ShowAtPosition -- PageYOffset position at which the button will show up.
101 |
102 | EasingType -- Easing option see : (https://www.npmjs.com/package/tween-functions) for available options.
103 |
104 | AnimationDuration -- Milisecond duration of scrolling up.
105 |
106 | ContainerClassName -- Class name applied to the container when NOT using the default view.
107 |
108 | TransitionClassName -- Class name applied to the container to show the button when NOT using the default view.
109 |
110 | style -- style the container directly with inline styleing, can be used with any imported button.
111 |
112 | ToggledStyle -- Style the toggled state of the container directly, can be used with any imported button.
113 |
114 | ----
115 |
116 | ### Themed Buttons:
117 | Click on a button to see its code.
118 |
119 | Vertical Button | Circle Arrow Button | Tiny Up Button
120 | :---: | :---: | :---:
121 | [](#vertical-button) | [](#circle-arrow-button) | [](#tiny-up-button)
122 |
123 |
124 | ----
125 |
126 |
127 | #### Vertical Button:
128 | 
129 |
130 | Vertical_Button | React:
131 | ```
132 | import React from "react";
133 | import {VerticleButton as ScrollUpButton} from "react-scroll-up-button"; //Add this line Here
134 |
135 | export default class Index extends React.Component {
136 | render() {
137 | return (
138 |
139 |
140 |
141 | );
142 | }
143 | }
144 | ```
145 |
146 | ----
147 |
148 | #### Circle Arrow Button:
149 | 
150 |
151 | Circle_Arrow_Button | React:
152 | ```
153 | import React from "react";
154 | import {CircleArrow as ScrollUpButton} from "react-scroll-up-button"; //Add this line Here
155 |
156 | export default class Index extends React.Component {
157 | render() {
158 | return (
159 |
160 |
161 |
162 | );
163 | }
164 | }
165 | ```
166 |
167 | #### Tiny Up Button:
168 | 
169 |
170 | Tiny_Up_Button | React:
171 | ```
172 | import React from "react";
173 | import {TinyButton as ScrollUpButton} from "react-scroll-up-button"; //Add this line Here
174 |
175 | export default class Index extends React.Component {
176 | render() {
177 | return (
178 |
179 |
180 |
181 | );
182 | }
183 | }
184 | ```
185 |
186 | ### More To Come!!
187 |
188 | ----
189 |
190 | ### Credit
191 | I was inspired by: https://github.com/milosjanda/react-scroll-up
192 | With the base recource of the repo i was able to create my own in my style with additional functionality.
193 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "video": false
3 | }
4 |
--------------------------------------------------------------------------------
/cypress/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "es6": true,
5 | "browser": true
6 | },
7 | "rules": {
8 | "no-unused-vars": 0,
9 | "semi": 0,
10 | "linebreak-style": 0,
11 | "no-undef": 0,
12 | "no-console": 0
13 | },
14 | "plugins": [
15 | "import"
16 | ]
17 | }
--------------------------------------------------------------------------------
/cypress/fixtures/example.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Using fixtures to represent data",
3 | "email": "hello@cypress.io",
4 | "body": "Fixtures are a great way to mock data for responses to routes"
5 | }
--------------------------------------------------------------------------------
/cypress/integration/scroll.spec.js:
--------------------------------------------------------------------------------
1 | beforeEach(() => {
2 | cy.visit('http://localhost:8000')
3 | })
4 | describe('E2E testing', () => {
5 | it('Applies Transition class!', () => {
6 | cy.get('[data-testid="react-scroll-up-button"]')
7 | .should('have.class', 'ScrollUpButton__Container')
8 | cy.get('[data-testid="react-scroll-up-button"]')
9 | .should('not.have.class', 'ScrollUpButton__Toggled')
10 | cy.scrollTo('bottom')
11 | cy.get('[data-testid="react-scroll-up-button"]')
12 | .should('have.class', 'ScrollUpButton__Toggled')
13 | })
14 |
15 | it('can be clicked!', () => {
16 | cy.scrollTo('bottom')
17 | cy.get('[data-testid="react-scroll-up-button"]')
18 | .should('have.class', 'ScrollUpButton__Toggled')
19 | cy.get('[data-testid="react-scroll-up-button"]').click()
20 | cy.get('[data-testid="react-scroll-up-button"]')
21 | .should('not.have.class', 'ScrollUpButton__Toggled')
22 | cy.window().its('pageYOffset').should('eq', 0)
23 | })
24 |
25 | it('Transitions to the top after clicking', () => {
26 | cy.window().its('pageYOffset').should('eq', 0)
27 | cy.scrollTo('bottom')
28 | cy.window().its('pageYOffset').should('not.eq', 0)
29 | cy.get('[data-testid="react-scroll-up-button"]').click()
30 | cy.window().its('pageYOffset').should('eq', 0)
31 | })
32 |
33 | it('should be visible', () => {
34 | cy.get('[data-testid="react-scroll-up-button"]')
35 | .should('not.be.visible')
36 | cy.scrollTo('bottom')
37 | cy.get('[data-testid="react-scroll-up-button"]')
38 | .should('be.visible')
39 | })
40 | })
41 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example plugins/index.js can be used to load plugins
3 | //
4 | // You can change the location of this file or turn off loading
5 | // the plugins file with the 'pluginsFile' configuration option.
6 | //
7 | // You can read more here:
8 | // https://on.cypress.io/plugins-guide
9 | // ***********************************************************
10 |
11 | // This function is called when a project is opened or re-opened (e.g. due to
12 | // the project's config changing)
13 |
14 | module.exports = (on, config) => {
15 | // `on` is used to hook into various events Cypress emits
16 | // `config` is the resolved Cypress config
17 | }
18 |
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add("login", (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This is will overwrite an existing command --
25 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 |
19 | // Alternatively you can use CommonJS syntax:
20 | // require('./commands')
21 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Project dependencies
2 | .cache
3 | node_modules
4 | yarn-error.log
5 |
6 | # Build directory
7 | /public
8 | .DS_Store
9 |
--------------------------------------------------------------------------------
/example/gatsby-config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | siteMetadata: {
3 | title: 'react-scroll-up-button',
4 | },
5 | plugins: [
6 | 'gatsby-plugin-styled-components',
7 | ],
8 | }
9 |
--------------------------------------------------------------------------------
/example/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gatsby-starter-default",
3 | "description": "Gatsby default starter",
4 | "version": "1.0.0",
5 | "author": "Kyle Mathews ",
6 | "dependencies": {
7 | "babel-plugin-styled-components": "^1.8.0",
8 | "gatsby": "^2.0.0",
9 | "gatsby-plugin-styled-components": "^3.0.4",
10 | "react": "^16.5.1",
11 | "react-dom": "^16.5.1",
12 | "react-numeric-input": "^2.2.3",
13 | "react-select": "^2.1.2",
14 | "react-syntax-highlighter": "^10.1.1",
15 | "styled-components": "^4.1.2",
16 | "tween-functions": "^1.2.0"
17 | },
18 | "keywords": [
19 | "gatsby"
20 | ],
21 | "license": "MIT",
22 | "scripts": {
23 | "build": "gatsby build",
24 | "develop": "gatsby develop"
25 | },
26 | "devDependencies": {},
27 | "repository": {
28 | "type": "git",
29 | "url": "https://github.com/gatsbyjs/gatsby-starter-default"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/example/src/pages/CustomButton.css:
--------------------------------------------------------------------------------
1 | .AnyClassForContainer {
2 | position: fixed;
3 | right: -100px;
4 | bottom: 150px;
5 | transition: right 0.5s;
6 | cursor: pointer;
7 | background-color: white;
8 | font-size: 20px;
9 | padding: 10px;
10 | }
11 |
12 | .AnyClassForTransition {
13 | right: 20px;
14 | }
15 |
--------------------------------------------------------------------------------
/example/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import styled, { createGlobalStyle } from 'styled-components' // eslint-disable-line
3 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'; // eslint-disable-line
4 | import { darcula as codeBlockStyle } from 'react-syntax-highlighter/dist/esm/styles/prism'; // eslint-disable-line
5 | import TweenFunctions from 'tween-functions';
6 | import Select from 'react-select'; // eslint-disable-line
7 | import NumericInput from 'react-numeric-input'; // eslint-disable-line
8 | import './normalize.css'
9 | import './CustomButton.css'
10 |
11 | let ScrollUpButtons = []
12 | const TweenOptions = Object.keys(TweenFunctions).map(tween => ({ value: tween, label: tween }))
13 |
14 | if (process.env.NODE_ENV === 'development') {
15 | ScrollUpButtons = require('../../../src/react-scroll-up-button') // eslint-disable-line
16 | } else {
17 | ScrollUpButtons = require('../../../dist/cjs/react-scroll-up-button') // eslint-disable-line
18 | }
19 |
20 | const CustomScrollUp = props => (
21 |
26 | Custom
27 | )
28 |
29 | ScrollUpButtons.Custom = CustomScrollUp
30 |
31 | export default class Example extends React.Component {
32 | constructor() {
33 | super();
34 | const { defaultProps } = ScrollUpButtons.default
35 | this.state = {
36 | currentBtn: 'default',
37 | EasingType: TweenOptions.filter(tween => tween.label === defaultProps.EasingType)[0],
38 | ShowAtPosition: defaultProps.ShowAtPosition,
39 | StopPosition: defaultProps.StopPosition,
40 | AnimationDuration: defaultProps.AnimationDuration,
41 | ShowAtPositionMaxValue: 3000,
42 | }
43 | this.EasingTypeOnChange = this.EasingTypeOnChange.bind(this)
44 | this.StopPositionOnChange = this.StopPositionOnChange.bind(this)
45 | this.ShowAtPositionOnChange = this.ShowAtPositionOnChange.bind(this)
46 | this.AnimationDurationOnChange = this.AnimationDurationOnChange.bind(this)
47 | }
48 |
49 | componentDidMount() {
50 | this.setState({ ShowAtPositionMaxValue: document.documentElement.offsetHeight - window.innerHeight - 1 })
51 | }
52 |
53 | EasingTypeOnChange(EasingType) {
54 | this.setState({ EasingType })
55 | }
56 |
57 | StopPositionOnChange(StopPosition) {
58 | this.setState({ StopPosition })
59 | }
60 |
61 | ShowAtPositionOnChange(ShowAtPosition) {
62 | const { StopPosition } = this.state
63 | const max = document.documentElement.offsetHeight - window.innerHeight - 1
64 | const min = StopPosition + 1
65 | if (ShowAtPosition > max) {
66 | this.setState({ ShowAtPosition: max })
67 | return
68 | }
69 | if (ShowAtPosition < min) {
70 | this.setState({ ShowAtPosition: min })
71 | return
72 | }
73 | this.setState({ ShowAtPosition })
74 | }
75 |
76 | AnimationDurationOnChange(AnimationDuration) {
77 | this.setState({ AnimationDuration })
78 | }
79 |
80 | handleClick(btn) {
81 | this.setState({ currentBtn: btn });
82 | }
83 |
84 | render() {
85 | const {
86 | currentBtn,
87 | EasingType,
88 | ShowAtPosition,
89 | StopPosition,
90 | AnimationDuration,
91 | ShowAtPositionMaxValue,
92 | } = this.state
93 | const { defaultProps } = ScrollUpButtons.default
94 | const EasingTypeProp = defaultProps.EasingType !== EasingType.value && `EasingType="${EasingType.value}"`
95 | const StopPositionProp = defaultProps.StopPosition !== StopPosition && `StopPosition={${StopPosition}}`
96 | const ShowAtPositionProp = defaultProps.ShowAtPosition !== ShowAtPosition && `ShowAtPosition={${ShowAtPosition}}`
97 | const AnimationDurationProp = defaultProps.AnimationDuration !== AnimationDuration && `AnimationDuration={${AnimationDuration}}`
98 |
99 | let ExampleProps = [
100 | EasingTypeProp,
101 | StopPositionProp,
102 | ShowAtPositionProp,
103 | AnimationDurationProp,
104 | ]
105 | ExampleProps = ExampleProps.filter(prop => prop)
106 |
107 | return (
108 |
109 |
110 | {Object.keys(ScrollUpButtons).map((btn) => {
111 | const CurBtn = ScrollUpButtons[btn]
112 | if (currentBtn === btn) {
113 | return (
114 |
121 | )
122 | }
123 | return null
124 | })}
125 |
126 |
127 | react-scroll-up-button
128 |
129 |
130 |
131 | An off-canvas button React component with pre styled buttons allowing "Scroll To Top" functionality.
132 |
133 |
134 | Inspired by
135 | react-scroll-up
136 | by milosjand, and
137 | react-burger-menu
138 | by negomi.
139 |
140 |
141 | {Object.keys(ScrollUpButtons).map(btn => (
142 |
148 | {btn}
149 |
150 | ))}
151 |
152 |
201 |
202 | Scroll Down
203 | Too see the component
204 |
205 | {Object.keys(ScrollUpButtons).map((btn) => {
206 | const str = `import React from "react";
207 | import ${btn === 'default' || btn === 'Custom'
208 | ? 'ScrollUpButton'
209 | : `{${btn} as ScrollUpButton}`} from "react-scroll-up-button";${btn === 'Custom'
210 | ? `
211 | import ./myCssFile.css;`
212 | : ''}
213 |
214 | export default class Index extends React.Component {
215 | render() {
216 | return (
217 |
218 | ${btn === 'Custom' ? ` 0
221 | ? `
222 | ${ExampleProps.map(prop => prop).join(`
223 | `)}}`
224 | : ''}
225 | >
226 |
227 | `
228 | : ExampleProps.length > 0 ? ` prop).join(`
230 | `)}
231 | />` : ' '
232 |
233 | }
234 |
235 | );
236 | }
237 | }`
238 |
239 | const CustomCss = `.AnyClassForContainer {
240 | position: fixed;
241 | right: -100px;
242 | bottom: 150px;
243 | transition: right 0.5s;
244 | cursor: pointer;
245 | background-color: white;
246 | font-size: 20px;
247 | padding: 10px;
248 | }
249 |
250 | .AnyClassForTransition {
251 | right: 20px;
252 | }`
253 | if (currentBtn === btn) {
254 | if (btn === 'Custom') {
255 | return (
256 |
257 | {str}
258 | {CustomCss}
259 |
260 | )
261 | }
262 | return {str}
263 | }
264 | return null
265 | })}
266 |
267 |
268 | );
269 | }
270 | }
271 |
272 | const Wrapper = styled.div`
273 | background: #b30000;
274 | width: 100%;
275 | height: 2000px;
276 | text-align: center;
277 | min-width: 300px;
278 |
279 | & a{
280 | color: #f5f5f5;
281 | text-decoration: none;
282 |
283 | &:visited{
284 | color: #f5f5f5;
285 | }
286 | &:hover{
287 | color: #8a8a8a;
288 | }
289 | }
290 | `
291 | const Button = styled.button`
292 | display: inline-block;
293 | margin: 0.75em;
294 | padding: 1.35em 1.1em;
295 | background: #252525;
296 | color: white;
297 | text-transform: uppercase;
298 | letter-spacing: 1px;
299 | font-weight: 800;
300 | border-top-left-radius: 20px 50px;
301 | border-top-right-radius: 20px 50px;
302 | border-bottom-right-radius: 20px 50px;
303 | border-bottom-left-radius: 20px 50px;
304 | cursor: pointer;
305 | border: none;
306 |
307 | &.selected{
308 | opacity: 0.6;
309 | }
310 | &:not(.selected):hover, &:not(.selected):focus{
311 | color: black;
312 | }
313 | `
314 |
315 | const Title = styled.h1`
316 | margin: 0;
317 | font-size: 4em;
318 | `
319 |
320 | const Description = styled.h2`
321 | max-width: 500px;
322 | margin: 1.2em auto 1em;
323 | `
324 |
325 | const ScrollDown = styled.h2`
326 | color: #00c1b1;
327 | `
328 |
329 | const CodeBlock = styled.div`
330 | max-width: 750px;
331 | margin: auto;
332 | `
333 | const Form = styled.div`
334 | text-align: left;
335 | width: 225px;
336 | margin: auto;
337 | display: flex;
338 | justify-content: center;
339 | `
340 |
341 | const FormChild = styled.div`
342 | padding: 20px;
343 | `
344 |
345 | const Dropdown = styled(Select)`
346 | width: 229px;
347 | display: inline-block;
348 | `
349 | const MyLabel = styled.label`
350 | display: inline-block;
351 | padding-top: 10px;
352 | padding-bottom: 5px;
353 | padding-left: 6px;
354 | `
355 |
356 | const NumberInput = styled(NumericInput)`
357 | border-top-left-radius: 20px 50px;
358 | border-top-right-radius: 20px 50px;
359 | border-bottom-right-radius: 20px 50px;
360 | border-bottom-left-radius: 20px 50px;
361 | padding-right: 3ex;
362 | box-sizing: border-box;
363 | font-size: inherit;
364 | height: 38px;
365 | border: none;
366 | padding-left: 10px;
367 | display: block;
368 | -webkit-appearance: none;
369 | line-height: normal;
370 | background: #252525;
371 | color: white;
372 | `
373 |
374 | const GlobalStyle = createGlobalStyle`
375 | .react-numeric-input {
376 | position: relative;
377 | display: inline-block;
378 | }
379 | .react-numeric-input b {
380 | position: absolute;
381 | right: 8px;
382 | width: 2.26ex;
383 | border-color: rgba(0, 0, 0, 0.1);
384 | border-style: solid;
385 | text-align: center;
386 | cursor: default;
387 | transition: all 0.1s ease 0s;
388 | background: rgba(247, 241, 241, 0.1);
389 | box-shadow: rgba(0,0,0,0.1) -1px -1px 3px inset, rgba(255,255,255,0.7) 1px 1px 3px inset;
390 | top: 2px;
391 | bottom: 50%;
392 | border-radius: 2px 2px 0px 0px;
393 | border-width: 1px 1px 0px;
394 | }
395 | .react-numeric-input b:last-child {
396 | top: 50%;
397 | bottom: 2px;
398 | border-radius: 0px 0px 2px 2px;
399 | border-width: 0px 1px 1px
400 | }
401 | .react-numeric-input i {
402 | position: absolute;
403 | top: 50%;
404 | left: 50%;
405 | width: 0px;
406 | height: 0px;
407 | border-width: 0px 0.6ex 0.6ex;
408 | border-color: transparent transparent rgba(241, 241, 241, 0.58);
409 | border-style: solid;
410 | margin: -0.3ex 0px 0px -0.56ex;
411 | }
412 | .react-numeric-input b:last-child > i {
413 | border-width: 0.6ex 0.6ex 0px;
414 | border-color: rgba(241, 241, 241, 0.58) transparent transparent;
415 | }
416 |
417 | .select > .select__control {
418 | background-color: #252525;
419 | border-color: #252525;
420 | border-top-left-radius: 20px 50px;
421 | border-top-right-radius: 20px 50px;
422 | border-bottom-right-radius: 20px 50px;
423 | border-bottom-left-radius: 20px 50px;
424 | }
425 | .select .select__single-value {
426 | color: white;
427 | }
428 | `
429 |
--------------------------------------------------------------------------------
/example/src/pages/normalize.css:
--------------------------------------------------------------------------------
1 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block;}audio,canvas,video{display:inline-block;}audio:not([controls]){display:none;height:0;}[hidden]{display:none;}html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%;}body{margin:0;}a:focus{outline:thin dotted;}a:active,a:hover{outline:0;}h1{font-size:2em;margin:0.67em 0;}abbr[title]{border-bottom:1px dotted;}b,strong{font-weight:bold;}dfn{font-style:italic;}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0;}mark{background:#ff0;color:#000;}code,kbd,pre,samp{font-family:monospace,serif;font-size:1em;}pre{white-space:pre-wrap;}q{quotes:"\201C" "\201D" "\2018" "\2019";}small{font-size:80%;}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}sup{top:-0.5em;}sub{bottom:-0.25em;}img{border:0;}svg:not(:root){overflow:hidden;}figure{margin:0;}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em;}legend{border:0;padding:0;}button,input,select,textarea{font-family:inherit;font-size:100%;margin:0;}button,input{line-height:normal;}button,select{text-transform:none;}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer;}button[disabled],html input[disabled]{cursor:default;}input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0;}input[type="search"]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box;}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}textarea{overflow:auto;vertical-align:top;}table{border-collapse:collapse;border-spacing:0;}
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | if (process.env.NODE_ENV === "production") {
4 | module.exports = require("./dist/cjs/react-scroll-up-button.min.js");
5 | } else {
6 | module.exports = require("./dist/cjs/react-scroll-up-button.js");
7 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-scroll-up-button",
3 | "version": "1.6.4",
4 | "main": "index.js",
5 | "module": "dist/esm/react-scroll-up-button.js",
6 | "sideEffects": false,
7 | "description": "React component Scroll up button, fixed button to scroll up to desired position",
8 | "files": [
9 | "react-scroll-up-button.js",
10 | "cjs",
11 | "es",
12 | "esm",
13 | "index.js"
14 | ],
15 | "scripts": {
16 | "compile": "npx del-cli dist && rollup -c",
17 | "example:install": "cd example && npm install",
18 | "example:build": "npm run compile && npm run example:install && cd example && npm run build",
19 | "example:develop": "npm run example:install && cd example && npm run develop",
20 | "cypress:open": "cypress open",
21 | "cypress:run": "npx cypress run",
22 | "dev:server": "npm run example:develop",
23 | "dev:cypress": "start-server-and-test dev:server http://localhost:8000 cypress:open",
24 | "dev:test": "npx jest --no-cache --coverage --watch",
25 | "test": "npx jest --coverage --coverageReporters=text-lcov | coveralls",
26 | "test:server": "npm run compile && npx webpack-dev-server --config ./tests/server/webpack.config.js",
27 | "test:cypress": "start-server-and-test test:server http://localhost:8000 cypress:run",
28 | "prepublishOnly": "npm run compile",
29 | "eslint:example": "eslint example/src",
30 | "eslint:cypress": "eslint cypress",
31 | "eslint:tests": "eslint tests",
32 | "eslint:src": "eslint src",
33 | "eslint": "npm run compile && npm run eslint:src && npm run eslint:cypress && npm run eslint:tests && npm run eslint:example"
34 | },
35 | "jest": {
36 | "verbose": true,
37 | "testRegex": "(/tests/specs/.*)",
38 | "globals": {
39 | "window": true
40 | },
41 | "coverageThreshold": {
42 | "global": {
43 | "branches": 80,
44 | "functions": 80,
45 | "lines": 80,
46 | "statements": -10
47 | }
48 | },
49 | "collectCoverageFrom": [
50 | "src/*.{js,jsx}"
51 | ]
52 | },
53 | "author": "Dirtyredz | David McCLain ",
54 | "license": "MIT",
55 | "repository": {
56 | "type": "git",
57 | "url": "https://github.com/dirtyredz/react-scroll-up-button.git"
58 | },
59 | "keywords": [
60 | "scroll",
61 | "scrollUp",
62 | "scrollToTop",
63 | "animation",
64 | "effects",
65 | "react",
66 | "react-component"
67 | ],
68 | "devDependencies": {
69 | "@babel/core": "7.1.6",
70 | "@babel/plugin-transform-runtime": "7.1.0",
71 | "@babel/preset-env": "7.1.6",
72 | "@babel/preset-react": "7.0.0",
73 | "babel-core": "7.0.0-bridge.0",
74 | "babel-eslint": "10.0.1",
75 | "babel-jest": "23.6.0",
76 | "babel-loader": "8.0.4",
77 | "coveralls": "^3.0.2",
78 | "del-cli": "1.1.0",
79 | "eslint": "5.9.0",
80 | "eslint-config-airbnb": "^17.1.0",
81 | "eslint-plugin-import": "^2.14.0",
82 | "eslint-plugin-jsx-a11y": "6.1.2",
83 | "eslint-plugin-prettier": "3.0.0",
84 | "eslint-plugin-react": "^7.11.1",
85 | "jest": "^23.6.0",
86 | "jest-dom": "2.1.1",
87 | "react": "16.6.3",
88 | "react-dom": "16.6.3",
89 | "react-testing-library": "5.3.0",
90 | "rollup": "0.67.3",
91 | "rollup-plugin-babel": "4.0.3",
92 | "rollup-plugin-commonjs": "9.2.0",
93 | "rollup-plugin-node-resolve": "3.4.0",
94 | "rollup-plugin-replace": "2.1.0",
95 | "rollup-plugin-size-snapshot": "0.7.0",
96 | "rollup-plugin-terser": "3.0.0",
97 | "start-server-and-test": "1.7.11",
98 | "webpack": "4.26.1",
99 | "webpack-cli": "3.1.2",
100 | "webpack-dev-server": ">=3.1.11"
101 | },
102 | "peerDependencies": {
103 | "react": "^15.5.4 || ^16.0.0"
104 | },
105 | "dependencies": {
106 | "@babel/runtime": "7.1.5",
107 | "cypress": "^3.1.3",
108 | "detect-passive-events": "^1.0.4",
109 | "prop-types": "^15.6.2",
110 | "tween-functions": "^1.2.0"
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from 'rollup-plugin-babel';
2 | import replace from 'rollup-plugin-replace';
3 | import { sizeSnapshot } from 'rollup-plugin-size-snapshot';
4 | import { terser } from 'rollup-plugin-terser';
5 |
6 | import pkg from './package.json';
7 |
8 | const input = 'src/react-scroll-up-button.js';
9 |
10 | const babelOptionsCJS = {
11 | exclude: /node_modules/,
12 | };
13 |
14 | const babelOptionsESM = {
15 | exclude: /node_modules/,
16 | runtimeHelpers: true,
17 | plugins: [['@babel/transform-runtime', { useESModules: true }]],
18 | };
19 |
20 | const external = id => !id.startsWith('.') && !id.startsWith('/');
21 |
22 | export default [
23 | {
24 | input,
25 | output: { file: `dist/cjs/${pkg.name}.js`, format: 'cjs' },
26 | external,
27 | plugins: [
28 | babel(babelOptionsCJS),
29 | replace({ 'process.env.NODE_ENV': JSON.stringify('development') }),
30 | ],
31 | },
32 |
33 | {
34 | input,
35 | output: { file: `dist/cjs/${pkg.name}.min.js`, format: 'cjs' },
36 | external,
37 | plugins: [
38 | babel(babelOptionsCJS),
39 | replace({ 'process.env.NODE_ENV': JSON.stringify('production') }),
40 | terser(),
41 | ],
42 | },
43 |
44 | {
45 | input,
46 | output: { file: `dist/esm/${pkg.name}.js`, format: 'esm' },
47 | external,
48 | plugins: [babel(babelOptionsESM), sizeSnapshot()],
49 | },
50 | ];
51 |
--------------------------------------------------------------------------------
/src/react-scroll-up-button.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import TweenFunctions from 'tween-functions';
3 | import PropTypes from 'prop-types';
4 | import detectPassiveEvents from 'detect-passive-events';
5 |
6 | class ScrollUpButton extends React.Component {
7 | constructor(props) {
8 | super(props)
9 | this.state = { ToggleScrollUp: '' };
10 | this.Animation = {
11 | StartPosition: 0,
12 | CurrentAnimationTime: 0,
13 | StartTime: null,
14 | AnimationFrame: null,
15 | }
16 | this.HandleScroll = this.HandleScroll.bind(this)
17 | this.StopScrollingFrame = this.StopScrollingFrame.bind(this)
18 | this.ScrollingFrame = this.ScrollingFrame.bind(this)
19 | this.HandleClick = this.HandleClick.bind(this)
20 | }
21 |
22 | componentDidMount() {
23 | this.HandleScroll(); // run HandleScroll() at mount incase we are already scrolled down
24 | window.addEventListener('scroll', this.HandleScroll);
25 | window.addEventListener(
26 | 'wheel',
27 | this.StopScrollingFrame,
28 | detectPassiveEvents.hasSupport
29 | ? { passive: true }
30 | : false
31 | ); // Stop animation if user mouse wheels during animation.
32 | window.addEventListener(
33 | 'touchstart',
34 | this.StopScrollingFrame,
35 | detectPassiveEvents.hasSupport
36 | ? { passive: true }
37 | : false
38 | ); // Stop animation if user touches the screen during animation.
39 | }
40 |
41 | componentWillUnmount() {
42 | // Remove all events, since component is no longer mounted.
43 | window.removeEventListener('scroll', this.HandleScroll);
44 | window.removeEventListener('wheel', this.StopScrollingFrame, false);
45 | window.removeEventListener('touchstart', this.StopScrollingFrame, false);
46 | }
47 |
48 | HandleScroll() {
49 | const { ShowAtPosition, TransitionClassName } = this.props
50 | // window.pageYOffset = current scroll position
51 | // ShowAtPosition = position at which we want the button to show.
52 | if (window.pageYOffset > ShowAtPosition) {
53 | // styles.Toggled = the class name we want applied to transition the button in.
54 | this.setState({ ToggleScrollUp: TransitionClassName });
55 | } else {
56 | // remove the class name
57 | this.setState({ ToggleScrollUp: '' });
58 | }
59 | }
60 |
61 | HandleClick() {
62 | // Is this needed?
63 | // const { ShowAtPosition } = this.props
64 | // // For some reason the user was able to click the button.
65 | // if (window.pageYOffset < ShowAtPosition) {
66 | // event.preventDefault()
67 | // this.HandleScroll()
68 | // }
69 | // Scroll to StopPosition
70 | this.StopScrollingFrame();// Stoping all AnimationFrames
71 | this.Animation.StartPosition = window.pageYOffset;// current scroll position
72 | this.Animation.CurrentAnimationTime = 0;
73 | this.Animation.StartTime = null;
74 | // Start the scrolling animation.
75 | this.Animation.AnimationFrame = window.requestAnimationFrame(this.ScrollingFrame);
76 | }
77 |
78 | ScrollingFrame() {
79 | const { StopPosition, EasingType, AnimationDuration } = this.props
80 | const timestamp = Math.floor(Date.now());
81 | // If StartTime has not been assigned a value, assign it the start timestamp.
82 | if (!this.Animation.StartTime) {
83 | this.Animation.StartTime = timestamp;
84 | }
85 |
86 | // set CurrentAnimationTime every iteration of ScrollingFrame()
87 | this.Animation.CurrentAnimationTime = timestamp - this.Animation.StartTime;
88 | // if we hit the StopPosition, StopScrollingFrame()
89 | if (window.pageYOffset <= StopPosition) {
90 | this.StopScrollingFrame();
91 | } else {
92 | // Otherwise continue ScrollingFrame to the StopPosition.
93 | // Does not support horizontal ScrollingFrame.
94 | // Let TweenFunctions handle the math to give us a new position based on AnimationDuration and EasingType type
95 | let YPos = TweenFunctions[EasingType](
96 | this.Animation.CurrentAnimationTime,
97 | this.Animation.StartPosition,
98 | StopPosition,
99 | AnimationDuration
100 | );
101 | if (YPos <= StopPosition) {
102 | YPos = StopPosition
103 | }
104 | window.scrollTo(0, YPos);
105 | // Request another frame to be painted
106 | this.Animation.AnimationFrame = window.requestAnimationFrame(this.ScrollingFrame);
107 | }
108 | }
109 |
110 | StopScrollingFrame() {
111 | // Stop the Animation Frames.
112 | window.cancelAnimationFrame(this.Animation.AnimationFrame);
113 | }
114 |
115 | render() {
116 | const styles = {
117 | MainStyle: {
118 | backgroundColor: 'rgba(50, 50, 50, 0.5)',
119 | height: 50,
120 | position: 'fixed',
121 | bottom: 20,
122 | width: 50,
123 | WebkitTransition: 'all 0.5s ease-in-out',
124 | transition: 'all 0.5s ease-in-out',
125 | transitionProperty: 'opacity, right',
126 | cursor: 'pointer',
127 | opacity: 0,
128 | right: -50,
129 | zIndex: 1000,
130 | },
131 | SvgStyle: {
132 | display: 'inline-block',
133 | width: '100%',
134 | height: '100%',
135 | strokeWidth: 0,
136 | stroke: 'white',
137 | fill: 'white',
138 | },
139 | ToggledStyle: {
140 | opacity: 1,
141 | right: 20,
142 | },
143 | }
144 | const {
145 | children,
146 | style,
147 | ToggledStyle,
148 | ContainerClassName,
149 | } = this.props
150 | const { ToggleScrollUp } = this.state
151 | if (children) {
152 | const childrenWithProps = React.Children.map(children,
153 | child => React.cloneElement(child, {
154 | className: this.className,
155 | }));
156 | return (
157 |
171 | {childrenWithProps}
172 |
173 | );
174 | }
175 | return (
176 |
208 | );
209 | }
210 | }
211 | export default ScrollUpButton
212 |
213 | export const TinyButton = (props) => {
214 | const styles = {
215 | MainStyle: {
216 | backgroundColor: 'rgb(87, 86, 86)',
217 | height: 30,
218 | position: 'fixed',
219 | bottom: 20,
220 | width: 30,
221 | WebkitTransition: 'all 0.5s ease-in-out',
222 | transition: 'all 0.5s ease-in-out',
223 | transitionProperty: 'opacity, right',
224 | cursor: 'pointer',
225 | opacity: 0,
226 | right: -75,
227 | zIndex: 1000,
228 | fill: '#292929',
229 | paddingBottom: 1,
230 | paddingLeft: 1,
231 | paddingRight: 1,
232 | },
233 | ToggledStyle: {
234 | opacity: 1,
235 | right: 30,
236 | },
237 | }
238 | const { style, ToggledStyle } = props
239 | return (
240 |
251 |
259 |
262 |
263 |
264 | );
265 | }
266 |
267 | export const CircleArrow = (props) => {
268 | const styles = {
269 | MainStyle: {
270 | backgroundColor: 'rgb(255, 255, 255)',
271 | borderRadius: '50%',
272 | border: '5px solid black',
273 | height: 50,
274 | position: 'fixed',
275 | bottom: 20,
276 | width: 50,
277 | WebkitTransition: 'all 0.5s ease-in-out',
278 | transition: 'all 0.5s ease-in-out',
279 | transitionProperty: 'opacity, right',
280 | cursor: 'pointer',
281 | opacity: 0,
282 | right: -75,
283 | },
284 | ToggledStyle: {
285 | opacity: 1,
286 | right: 20,
287 | },
288 | }
289 | const { style, ToggledStyle } = props
290 | return (
291 |
302 |
303 |
306 |
307 |
308 | );
309 | }
310 |
311 | export const VerticleButton = (props) => {
312 | const styles = {
313 | MainStyle: {
314 | backgroundColor: 'rgb(58, 56, 56)',
315 | position: 'fixed',
316 | bottom: 40,
317 | padding: '5px 10px',
318 | WebkitTransition: 'all 0.5s ease-in-out',
319 | transition: 'all 0.5s ease-in-out',
320 | transitionProperty: 'opacity, right',
321 | cursor: 'pointer',
322 | opacity: 0,
323 | right: -75,
324 | transform: 'rotate(-90deg)',
325 | },
326 | ToggledStyle: {
327 | opacity: 1,
328 | right: 10,
329 | },
330 | }
331 | const { style, ToggledStyle } = props
332 | return (
333 |
344 | UP →
345 |
346 | );
347 | }
348 |
349 | ScrollUpButton.defaultProps = {
350 | ContainerClassName: 'ScrollUpButton__Container',
351 | StopPosition: 0,
352 | ShowAtPosition: 150,
353 | EasingType: 'easeOutCubic',
354 | AnimationDuration: 500,
355 | TransitionClassName: 'ScrollUpButton__Toggled',
356 | style: {},
357 | ToggledStyle: {},
358 | children: null,
359 | }
360 |
361 | function LessThanShowAtPosition(props, propName, componentName) {
362 | const { ShowAtPosition } = props;
363 | if (props[propName]) { // eslint-disable-line
364 | const value = props[propName];
365 | if (typeof value === 'number') {
366 | if (value >= ShowAtPosition) { // Validate the incoming prop value againt the ShowAtPosition prop
367 | return new Error(`${propName} (${value}) in ${componentName} must be less then prop: ShowAtPosition (${ShowAtPosition})`);
368 | }
369 | return null
370 | }
371 | return new Error(`${propName} in ${componentName} must be a number.`);
372 | }
373 | return null;
374 | }
375 |
376 | ScrollUpButton.propTypes = {
377 | children: PropTypes.oneOfType([
378 | PropTypes.arrayOf(PropTypes.node),
379 | PropTypes.node,
380 | ]),
381 | StopPosition: LessThanShowAtPosition,
382 | ShowAtPosition: PropTypes.number, // show button under this position,
383 | EasingType: PropTypes.oneOf(['linear', 'easeInQuad', 'easeOutQuad', 'easeInOutQuad', 'easeInCubic',
384 | 'easeOutCubic', 'easeInOutCubic', 'easeInQuart', 'easeOutQuart', 'easeInOutQuart', 'easeInQuint',
385 | 'easeOutQuint', 'easeInOutQuint', 'easeInSine', 'easeOutSine', 'easeInOutSine', 'easeInExpo', 'easeOutExpo',
386 | 'easeInOutExpo', 'easeInCirc', 'easeOutCirc', 'easeInOutCirc', 'easeInElastic', 'easeOutElastic',
387 | 'easeInOutElastic', 'easeInBack', 'easeOutBack', 'easeInOutBack', 'easeInBounce', 'easeOutBounce',
388 | 'easeInOutBounce']),
389 | AnimationDuration: PropTypes.number, // seconds
390 | style: PropTypes.object, // eslint-disable-line react/forbid-prop-types
391 | ToggledStyle: PropTypes.object, // eslint-disable-line react/forbid-prop-types
392 | ContainerClassName: PropTypes.string,
393 | TransitionClassName: PropTypes.string,
394 | }
395 |
--------------------------------------------------------------------------------
/tests/server/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | React-Scroll-Up-Button
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/tests/server/testing.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import ScrollUpButton from '../../dist/esm/react-scroll-up-button';
4 |
5 | const rootEl = document.getElementById('ReactRoot');
6 | ReactDOM.render(
7 |
8 |
9 |
,
10 | rootEl
11 | );
12 |
--------------------------------------------------------------------------------
/tests/server/webpack.config.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 |
3 | module.exports = {
4 | mode: 'production',
5 | module: {
6 | rules: [
7 | {
8 | test: /\.js$/,
9 | exclude: /node_modules/,
10 | use: {
11 | loader: 'babel-loader',
12 | },
13 | },
14 | ],
15 | },
16 | entry: {
17 | index: './tests/server/testing',
18 | },
19 | output: {
20 | path: __dirname,
21 | filename: 'react.js',
22 | },
23 | plugins: [
24 | new webpack.HotModuleReplacementPlugin(),
25 | ],
26 | devServer: {
27 | hot: true,
28 | contentBase: __dirname,
29 | port: 8000,
30 | },
31 | };
32 |
--------------------------------------------------------------------------------
/tests/specs/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "airbnb",
3 | "env": {
4 | "es6": true,
5 | "browser": true
6 | },
7 | "rules": {
8 | "no-unused-vars": 0,
9 | "semi": 0,
10 | "linebreak-style": 0,
11 | "no-undef": 0,
12 | "react/jsx-filename-extension": 0,
13 | "no-console": 0
14 | },
15 | "plugins": [
16 | "react",
17 | "import"
18 | ],
19 | "parserOptions": {
20 | "ecmaFeatures": {
21 | "jsx": true
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/tests/specs/custom prototype.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render,
4 | cleanup,
5 | } from 'react-testing-library'
6 | import 'jest-dom/extend-expect'
7 | import detectPassiveEvents from 'detect-passive-events'
8 | import ScrollUpButton from '../../src/react-scroll-up-button'
9 |
10 | jest.mock('detect-passive-events')
11 |
12 | afterEach(() => {
13 | cleanup()
14 | })
15 |
16 | describe('custom proptype validators', () => {
17 | console.error = (err) => { throw new Error(err); };
18 | console.warn = (warning) => { throw new Error(warning); }
19 |
20 | test('LessThanShowAtPosition thows when StopPosition is not a number.', () => {
21 | expect(() => {
22 | render( )
23 | }).toThrow()
24 | })
25 |
26 | test('LessThanShowAtPosition throws when StopPosition is greater than ShowAtPosition.', () => {
27 | expect(() => {
28 | render( )
29 | }).toThrow()
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/tests/specs/named exports.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render,
4 | cleanup,
5 | } from 'react-testing-library'
6 | import 'jest-dom/extend-expect'
7 | import detectPassiveEvents from 'detect-passive-events';
8 | import { CircleArrow, VerticleButton, TinyButton } from '../../src/react-scroll-up-button';
9 |
10 | jest.mock('detect-passive-events')
11 |
12 | afterEach(() => {
13 | cleanup()
14 | })
15 | beforeEach(() => {
16 | window.pageYOffset = 0
17 | global.scrollTo = jest.fn((x, y) => {
18 | window.pageXOffset = x
19 | window.pageYOffset = y
20 | window.dispatchEvent(new window.UIEvent('scroll'));
21 | })
22 | })
23 |
24 | describe('Named exports render', () => {
25 | test('CircleArrow is rendered', async () => {
26 | const {
27 | container,
28 | getByTestId,
29 | } = render( )
30 | const Aside = getByTestId('react-scroll-up-button')
31 | expect(container).toContainElement(Aside)
32 | });
33 | test('VerticleButton is rendered', async () => {
34 | const {
35 | container,
36 | getByTestId,
37 | } = render( )
38 | const Aside = getByTestId('react-scroll-up-button')
39 | expect(container).toContainElement(Aside)
40 | });
41 | test('TinyButton is rendered', async () => {
42 | const {
43 | container,
44 | getByTestId,
45 | } = render( )
46 | const Aside = getByTestId('react-scroll-up-button')
47 | expect(container).toContainElement(Aside)
48 | });
49 | });
50 |
--------------------------------------------------------------------------------
/tests/specs/pre-styled children.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render,
4 | cleanup,
5 | fireEvent,
6 | } from 'react-testing-library'
7 | import 'jest-dom/extend-expect'
8 | import detectPassiveEvents from 'detect-passive-events';
9 | import { CircleArrow } from '../../src/react-scroll-up-button';
10 |
11 | jest.mock('detect-passive-events')
12 |
13 | afterEach(() => {
14 | cleanup()
15 | })
16 | beforeEach(() => {
17 | window.pageYOffset = 0
18 | global.scrollTo = jest.fn((x, y) => {
19 | window.pageXOffset = x
20 | window.pageYOffset = y
21 | window.dispatchEvent(new window.UIEvent('scroll'));
22 | })
23 | })
24 |
25 | describe('unit/intergration testing on children', () => {
26 | test('loses transition class after scrolling up', async (done) => {
27 | const {
28 | getByTestId,
29 | } = render( )
30 | const Aside = getByTestId('react-scroll-up-button')
31 | expect(Aside).not.toHaveClass('ScrollUpButton__Toggled')
32 |
33 | window.pageYOffset = 160
34 | window.dispatchEvent(new window.UIEvent('scroll'));
35 |
36 | expect(Aside).toHaveClass('ScrollUpButton__Toggled')
37 | fireEvent.click(Aside)
38 | setTimeout(() => {
39 | expect(Aside).not.toHaveClass('ScrollUpButton__Toggled')
40 | done();
41 | }, 1000)
42 | });
43 | });
44 |
--------------------------------------------------------------------------------
/tests/specs/unit-intergation.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | render,
4 | cleanup,
5 | fireEvent,
6 | } from 'react-testing-library'
7 | import 'jest-dom/extend-expect'
8 | import detectPassiveEvents from 'detect-passive-events';
9 | import ScrollUpButton from '../../src/react-scroll-up-button';
10 |
11 | jest.mock('detect-passive-events')
12 |
13 | afterEach(() => {
14 | cleanup()
15 | })
16 | beforeEach(() => {
17 | window.pageYOffset = 0
18 | global.scrollTo = jest.fn((x, y) => {
19 | window.pageXOffset = x
20 | window.pageYOffset = y
21 | window.dispatchEvent(new window.UIEvent('scroll'));
22 | })
23 | })
24 | expect.extend({
25 | toBeWithinRange(received, floor, ceiling) {
26 | const pass = received >= floor && received <= ceiling;
27 | if (pass) {
28 | return {
29 | message: () => `expected ${received} not to be within range ${floor} - ${ceiling}`,
30 | pass: true,
31 | };
32 | }
33 | return {
34 | message: () => `expected ${received} to be within range ${floor} - ${ceiling}`,
35 | pass: false,
36 | };
37 | },
38 | });
39 |
40 | describe('React unit/intergration testing', () => {
41 | test('Aside to be rendered', async () => {
42 | const {
43 | container,
44 | getByTestId,
45 | } = render( )
46 | const Aside = getByTestId('react-scroll-up-button')
47 | expect(container).toContainElement(Aside)
48 | });
49 |
50 | test('mock detect-passive-events, branch testing', async () => {
51 | const {
52 | rerender,
53 | } = render( )
54 | detectPassiveEvents.hasSupport = true
55 | rerender( )
56 | });
57 |
58 | test('transition class applied on scroll', async () => {
59 | const {
60 | getByTestId,
61 | } = render( )
62 | const Aside = getByTestId('react-scroll-up-button')
63 |
64 | expect(Aside).toHaveClass('ScrollUpButton__Container')
65 | window.pageYOffset = 160
66 | window.dispatchEvent(new window.UIEvent('scroll'));
67 |
68 | expect(Aside).toHaveClass('ScrollUpButton__Toggled')
69 | });
70 |
71 | test('loses transition class after scrolling up', async (done) => {
72 | const {
73 | getByTestId,
74 | } = render( )
75 | const Aside = getByTestId('react-scroll-up-button')
76 | expect(Aside).not.toHaveClass('ScrollUpButton__Toggled')
77 |
78 | window.pageYOffset = 160
79 | window.dispatchEvent(new window.UIEvent('scroll'));
80 |
81 | expect(Aside).toHaveClass('ScrollUpButton__Toggled')
82 | fireEvent.click(Aside)
83 | setTimeout(() => {
84 | expect(Aside).not.toHaveClass('ScrollUpButton__Toggled')
85 | done();
86 | }, 1000)
87 | });
88 |
89 | test('stopPosition props stops scrolling at desired position', async (done) => {
90 | const {
91 | getByTestId,
92 | } = render( )
93 | const Aside = getByTestId('react-scroll-up-button')
94 | window.pageYOffset = 300
95 | window.dispatchEvent(new window.UIEvent('scroll'));
96 |
97 | expect(Aside).toHaveClass('ScrollUpButton__Toggled')
98 | fireEvent.click(Aside)
99 | setTimeout(() => {
100 | expect(Aside).not.toHaveClass('ScrollUpButton__Toggled')
101 | expect(global.scrollTo).toHaveBeenLastCalledWith(0, 30)
102 | done();
103 | }, 1000)
104 | });
105 |
106 | test('can accept props', async () => {
107 | render( )
117 | });
118 | });
119 |
--------------------------------------------------------------------------------